diff options
Diffstat (limited to 'core')
86 files changed, 2813 insertions, 1150 deletions
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 427ecce..435d5ab 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -108,7 +108,7 @@ public class AnimatorInflater { float pathErrorScale) throws NotFoundException { final ConfigurationBoundResourceCache<Animator> animatorCache = resources .getAnimatorCache(); - Animator animator = animatorCache.get(id, theme); + Animator animator = animatorCache.getInstance(id, theme); if (animator != null) { if (DBG_ANIMATOR_INFLATER) { Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id)); @@ -157,7 +157,7 @@ public class AnimatorInflater { final ConfigurationBoundResourceCache<StateListAnimator> cache = resources .getStateListAnimatorCache(); final Theme theme = context.getTheme(); - StateListAnimator animator = cache.get(id, theme); + StateListAnimator animator = cache.getInstance(id, theme); if (animator != null) { return animator; } diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 5790682..cdd72be 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * This class enables automatic animations on layout changes in ViewGroup objects. To enable @@ -757,7 +758,7 @@ public class LayoutTransition { // reset the inter-animation delay, in case we use it later staggerDelay = 0; - final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup + final ViewTreeObserver observer = parent.getViewTreeObserver(); if (!observer.isAlive()) { // If the observer's not in a good state, skip the transition return; @@ -790,21 +791,9 @@ public class LayoutTransition { // This is the cleanup step. When we get this rendering event, we know that all of // the appropriate animations have been set up and run. Now we can clear out the // layout listeners. - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - parent.getViewTreeObserver().removeOnPreDrawListener(this); - int count = layoutChangeListenerMap.size(); - if (count > 0) { - Collection<View> views = layoutChangeListenerMap.keySet(); - for (View view : views) { - View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); - view.removeOnLayoutChangeListener(listener); - } - } - layoutChangeListenerMap.clear(); - return true; - } - }); + CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent); + observer.addOnPreDrawListener(callback); + parent.addOnAttachStateChangeListener(callback); } /** @@ -1499,4 +1488,50 @@ public class LayoutTransition { View view, int transitionType); } + /** + * Utility class to clean up listeners after animations are setup. Cleanup happens + * when either the OnPreDrawListener method is called or when the parent is detached, + * whichever comes first. + */ + private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener, + View.OnAttachStateChangeListener { + + final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap; + final ViewGroup parent; + + CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) { + this.layoutChangeListenerMap = listenerMap; + this.parent = parent; + } + + private void cleanup() { + parent.getViewTreeObserver().removeOnPreDrawListener(this); + parent.removeOnAttachStateChangeListener(this); + int count = layoutChangeListenerMap.size(); + if (count > 0) { + Collection<View> views = layoutChangeListenerMap.keySet(); + for (View view : views) { + View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); + view.removeOnLayoutChangeListener(listener); + } + layoutChangeListenerMap.clear(); + } + } + + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + cleanup(); + } + + @Override + public boolean onPreDraw() { + cleanup(); + return true; + } + }; + } diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 94e3b66..4d34349 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -33,6 +33,7 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.Window; import android.widget.SpinnerAdapter; import java.lang.annotation.Retention; @@ -1373,5 +1374,13 @@ public abstract class ActionBar { * version of the SDK an app can end up statically linking to the new MarginLayoutParams * overload, causing a crash when running on older platform versions with no other changes. */ + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("gravity", gravity); + } } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7260d10..9968dbb 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -89,7 +89,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index da6d8c5..f16406a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -38,6 +38,7 @@ import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; @@ -4186,9 +4187,14 @@ public final class ActivityThread { if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { return; } - configDiff = mConfiguration.diff(config); - mConfiguration.updateFrom(config); + + configDiff = mConfiguration.updateFrom(config); config = applyCompatConfiguration(mCurDefaultDisplayDpi); + + final Theme systemTheme = getSystemContext().getTheme(); + if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { + systemTheme.rebase(); + } } ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config); diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 2939322..968c956 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -476,9 +476,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { tempRect.set(0, 0, width, height); view.getMatrix().mapRect(tempRect); - ViewGroup parent = (ViewGroup) view.getParent(); - left = leftInParent - tempRect.left + parent.getScrollX(); - top = topInParent - tempRect.top + parent.getScrollY(); + left = leftInParent - tempRect.left; + top = topInParent - tempRect.top; right = left + width; bottom = top + height; } @@ -506,7 +505,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { ViewGroup parent = (ViewGroup) view.getParent(); Matrix matrix = new Matrix(); parent.transformMatrixToLocal(matrix); - + matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); mSharedElementParentMatrices.add(matrix); } } @@ -521,6 +520,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { // Find the location in the view's parent ViewGroup parent = (ViewGroup) viewParent; parent.transformMatrixToLocal(matrix); + matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); } } else { // The indices of mSharedElementParentMatrices matches the diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 75e4bab..1174387 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -18,6 +18,7 @@ package android.app; import java.util.ArrayList; +import android.annotation.CallSuper; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; @@ -89,6 +90,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { * service, or receiver in a process. * If you override this method, be sure to call super.onCreate(). */ + @CallSuper public void onCreate() { } @@ -98,9 +100,11 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { * removed by simply killing them; no user code (including this callback) * is executed when doing so. */ + @CallSuper public void onTerminate() { } + @CallSuper public void onConfigurationChanged(Configuration newConfig) { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { @@ -110,6 +114,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + @CallSuper public void onLowMemory() { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { @@ -119,6 +124,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + @CallSuper public void onTrimMemory(int level) { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index d049104..f6e0e1e 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -48,7 +48,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 46da025..391131a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -91,7 +91,6 @@ import android.os.IPowerManager; import android.os.IUserManager; import android.os.PowerManager; import android.os.Process; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemVibrator; import android.os.UserHandle; @@ -111,7 +110,7 @@ import android.telephony.TelephonyManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import android.view.PhoneLayoutInflater; +import com.android.internal.policy.PhoneLayoutInflater; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8009b6c..355f298 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1653,9 +1653,9 @@ public class DevicePolicyManager { } /** - * Queries whether {@link #DO_NOT_ASK_CREDENTIALS_ON_BOOT} flag is set. + * Queries whether {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT} flag is set. * - * @return true if DO_NOT_ASK_CREDENTIALS_ON_BOOT flag is set. + * @return true if RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT flag is set. * @hide */ public boolean getDoNotAskCredentialsOnBoot() { @@ -1753,7 +1753,7 @@ public class DevicePolicyManager { * is ignored. Once the flag is set, it cannot be reverted back without resetting the * device to factory defaults. */ - public static final int DO_NOT_ASK_CREDENTIALS_ON_BOOT = 0x0002; + public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 0x0002; /** * Force a new device unlock password (the password needed to access the @@ -1779,7 +1779,7 @@ public class DevicePolicyManager { * * @param password The new password for the user. Null or empty clears the password. * @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and - * {@link #DO_NOT_ASK_CREDENTIALS_ON_BOOT}. + * {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}. * @return Returns true if the password was applied, or false if it is * not acceptable for the current constraints. */ diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 0a77868..ec6f18d 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -28,14 +28,11 @@ import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Context; +import android.os.Binder; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; -import android.app.ActivityThread; -import android.os.SystemProperties; -import android.provider.Settings; -import android.os.Binder; import android.util.Log; import android.util.Pair; @@ -1255,7 +1252,7 @@ public final class BluetoothAdapter { * @return true if chipset supports on-chip filtering */ public boolean isOffloadedFilteringSupported() { - if (getState() != STATE_ON) return false; + if (!getLeAccess()) return false; try { return mService.isOffloadedFilteringSupported(); } catch (RemoteException e) { @@ -1270,7 +1267,7 @@ public final class BluetoothAdapter { * @return true if chipset supports on-chip scan batching */ public boolean isOffloadedScanBatchingSupported() { - if (getState() != STATE_ON) return false; + if (!getLeAccess()) return false; try { return mService.isOffloadedScanBatchingSupported(); } catch (RemoteException e) { @@ -1286,7 +1283,7 @@ public final class BluetoothAdapter { * @hide */ public boolean isHardwareTrackingFiltersAvailable() { - if (getState() != STATE_ON) return false; + if (!getLeAccess()) return false; try { IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); if (iGatt == null) { diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 525059f..8d96f5c 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -785,6 +785,7 @@ public final class AssetManager implements AutoCloseable { private native final void deleteTheme(long theme); /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force); /*package*/ native static final void copyTheme(long dest, long source); + /*package*/ native static final void clearTheme(long theme); /*package*/ native static final int loadThemeAttributeValue(long theme, int ident, TypedValue outValue, boolean resolve); diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java index cde7e84..fecda87 100644 --- a/core/java/android/content/res/ConfigurationBoundResourceCache.java +++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java @@ -1,138 +1,58 @@ /* -* Copyright (C) 2014 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package android.content.res; + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -import android.util.ArrayMap; -import android.util.LongSparseArray; -import java.lang.ref.WeakReference; +package android.content.res; /** * A Cache class which can be used to cache resource objects that are easy to clone but more * expensive to inflate. - * @hide + * + * @hide For internal use only. */ -public class ConfigurationBoundResourceCache<T> { - - private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>> mCache = - new ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>>(); - - final Resources mResources; +public class ConfigurationBoundResourceCache<T> extends ThemedResourceCache<ConstantState<T>> { + private final Resources mResources; /** - * Creates a Resource cache for the given Resources instance. + * Creates a cache for the given Resources instance. * - * @param resources The Resource which can be used when creating new instances. + * @param resources the resources to use when creating new instances */ public ConfigurationBoundResourceCache(Resources resources) { mResources = resources; } /** - * Adds a new item to the cache. - * - * @param key A custom key that uniquely identifies the resource. - * @param theme The Theme instance where this resource was loaded. - * @param constantState The constant state that can create new instances of the resource. + * If the resource is cached, creates and returns a new instance of it. * + * @param key a key that uniquely identifies the drawable resource + * @param theme the theme where the resource will be used + * @return a new instance of the resource, or {@code null} if not in + * the cache */ - public void put(long key, Resources.Theme theme, ConstantState<T> constantState) { - if (constantState == null) { - return; - } - final String themeKey = theme == null ? "" : theme.getKey(); - LongSparseArray<WeakReference<ConstantState<T>>> themedCache; - synchronized (this) { - themedCache = mCache.get(themeKey); - if (themedCache == null) { - themedCache = new LongSparseArray<WeakReference<ConstantState<T>>>(1); - mCache.put(themeKey, themedCache); - } - themedCache.put(key, new WeakReference<ConstantState<T>>(constantState)); - } - } - - /** - * If the resource is cached, creates a new instance of it and returns. - * - * @param key The long key which can be used to uniquely identify the resource. - * @param theme The The Theme instance where we want to load this resource. - * - * @return If this resources was loaded before, returns a new instance of it. Otherwise, returns - * null. - */ - public T get(long key, Resources.Theme theme) { - final String themeKey = theme != null ? theme.getKey() : ""; - final LongSparseArray<WeakReference<ConstantState<T>>> themedCache; - final WeakReference<ConstantState<T>> wr; - synchronized (this) { - themedCache = mCache.get(themeKey); - if (themedCache == null) { - return null; - } - wr = themedCache.get(key); - } - if (wr == null) { - return null; - } - final ConstantState entry = wr.get(); + public T getInstance(long key, Resources.Theme theme) { + final ConstantState<T> entry = get(key, theme); if (entry != null) { - return (T) entry.newInstance(mResources, theme); - } else { // our entry has been purged - synchronized (this) { - // there is a potential race condition here where this entry may be put in - // another thread. But we prefer it to minimize lock duration - themedCache.delete(key); - } + return entry.newInstance(mResources, theme); } - return null; - } - /** - * Users of ConfigurationBoundResourceCache must call this method whenever a configuration - * change happens. On this callback, the cache invalidates all resources that are not valid - * anymore. - * - * @param configChanges The configuration changes - */ - public void onConfigurationChange(final int configChanges) { - synchronized (this) { - final int size = mCache.size(); - for (int i = size - 1; i >= 0; i--) { - final LongSparseArray<WeakReference<ConstantState<T>>> - themeCache = mCache.valueAt(i); - onConfigurationChangeInt(themeCache, configChanges); - if (themeCache.size() == 0) { - mCache.removeAt(i); - } - } - } + return null; } - private void onConfigurationChangeInt( - final LongSparseArray<WeakReference<ConstantState<T>>> themeCache, - final int configChanges) { - final int size = themeCache.size(); - for (int i = size - 1; i >= 0; i--) { - final WeakReference<ConstantState<T>> wr = themeCache.valueAt(i); - final ConstantState<T> constantState = wr.get(); - if (constantState == null || Configuration.needNewResources( - configChanges, constantState.getChangingConfigurations())) { - themeCache.removeAt(i); - } - } + @Override + public boolean shouldInvalidateEntry(ConstantState<T> entry, int configChanges) { + return Configuration.needNewResources(configChanges, entry.getChangingConfigurations()); } - } diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java new file mode 100644 index 0000000..fc70bc6 --- /dev/null +++ b/core/java/android/content/res/DrawableCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import android.graphics.drawable.Drawable; + +/** + * Class which can be used to cache Drawable resources against a theme. + */ +class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> { + private final Resources mResources; + + /** + * Creates a cache for the given Resources instance. + * + * @param resources the resources to use when creating new instances + */ + public DrawableCache(Resources resources) { + mResources = resources; + } + + /** + * If the resource is cached, creates and returns a new instance of it. + * + * @param key a key that uniquely identifies the drawable resource + * @param theme the theme where the resource will be used + * @return a new instance of the resource, or {@code null} if not in + * the cache + */ + public Drawable getInstance(long key, Resources.Theme theme) { + final Drawable.ConstantState entry = get(key, theme); + if (entry != null) { + return entry.newDrawable(mResources, theme); + } + + return null; + } + + @Override + public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) { + return false; + } +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ae41b69..a572590 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -20,6 +20,7 @@ import android.annotation.AttrRes; import android.annotation.ColorInt; import android.annotation.StyleRes; import android.annotation.StyleableRes; +import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -60,6 +61,7 @@ import android.util.Pools.SynchronizedPool; import android.util.Slog; import android.util.TypedValue; import android.view.ViewDebug; +import android.view.ViewHierarchyEncoder; import java.io.IOException; import java.io.InputStream; @@ -132,10 +134,8 @@ public class Resources { // These are protected by mAccessLock. private final Object mAccessLock = new Object(); private final Configuration mTmpConfig = new Configuration(); - private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache = - new ArrayMap<>(); - private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache = - new ArrayMap<>(); + private final DrawableCache mDrawableCache = new DrawableCache(this); + private final DrawableCache mColorDrawableCache = new DrawableCache(this); private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache = new ConfigurationBoundResourceCache<>(this); private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = @@ -1441,7 +1441,7 @@ public class Resources { AssetManager.applyThemeStyle(mTheme, resId, force); mThemeResId = resId; - mKey += Integer.toHexString(resId) + (force ? "! " : " "); + mKey.append(resId, force); } /** @@ -1457,7 +1457,7 @@ public class Resources { AssetManager.copyTheme(mTheme, other.mTheme); mThemeResId = other.mThemeResId; - mKey = other.mKey; + mKey.setTo(other.getKey()); } /** @@ -1765,6 +1765,9 @@ public class Resources { mTheme = mAssets.createTheme(); } + /** Unique key for the series of styles applied to this theme. */ + private final ThemeKey mKey = new ThemeKey(); + @SuppressWarnings("hiding") private final AssetManager mAssets; private final long mTheme; @@ -1772,9 +1775,6 @@ public class Resources { /** Resource identifier for the theme. */ private int mThemeResId = 0; - /** Unique key for the series of styles applied to this theme. */ - private String mKey = ""; - // Needed by layoutlib. /*package*/ long getNativeTheme() { return mTheme; @@ -1784,7 +1784,7 @@ public class Resources { return mThemeResId; } - /*package*/ String getKey() { + /*package*/ ThemeKey getKey() { return mKey; } @@ -1793,29 +1793,133 @@ public class Resources { } /** - * Parses {@link #mKey} and returns a String array that holds pairs of adjacent Theme data: - * resource name followed by whether or not it was forced, as specified by - * {@link #applyStyle(int, boolean)}. + * Parses {@link #mKey} and returns a String array that holds pairs of + * adjacent Theme data: resource name followed by whether or not it was + * forced, as specified by {@link #applyStyle(int, boolean)}. * * @hide */ @ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true) public String[] getTheme() { - String[] themeData = mKey.split(" "); - String[] themes = new String[themeData.length * 2]; - String theme; - boolean forced; - - for (int i = 0, j = themeData.length - 1; i < themes.length; i += 2, --j) { - theme = themeData[j]; - forced = theme.endsWith("!"); - themes[i] = forced ? - getResourceNameFromHexString(theme.substring(0, theme.length() - 1)) : - getResourceNameFromHexString(theme); + final int N = mKey.mCount; + final String[] themes = new String[N * 2]; + for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) { + final int resId = mKey.mResId[j]; + final boolean forced = mKey.mForce[j]; + try { + themes[i] = getResourceName(resId); + } catch (NotFoundException e) { + themes[i] = Integer.toHexString(i); + } themes[i + 1] = forced ? "forced" : "not forced"; } return themes; } + + /** @hide */ + public void encode(@NonNull ViewHierarchyEncoder encoder) { + encoder.beginObject(this); + final String[] properties = getTheme(); + for (int i = 0; i < properties.length; i += 2) { + encoder.addProperty(properties[i], properties[i+1]); + } + encoder.endObject(); + } + + /** + * Rebases the theme against the parent Resource object's current + * configuration by re-applying the styles passed to + * {@link #applyStyle(int, boolean)}. + * + * @hide + */ + public void rebase() { + AssetManager.clearTheme(mTheme); + + // Reapply the same styles in the same order. + for (int i = 0; i < mKey.mCount; i++) { + final int resId = mKey.mResId[i]; + final boolean force = mKey.mForce[i]; + AssetManager.applyThemeStyle(mTheme, resId, force); + } + } + } + + static class ThemeKey implements Cloneable { + int[] mResId; + boolean[] mForce; + int mCount; + + private int mHashCode = 0; + + public void append(int resId, boolean force) { + if (mResId == null) { + mResId = new int[4]; + } + + if (mForce == null) { + mForce = new boolean[4]; + } + + mResId = GrowingArrayUtils.append(mResId, mCount, resId); + mForce = GrowingArrayUtils.append(mForce, mCount, force); + mCount++; + + mHashCode = 31 * (31 * mHashCode + resId) + (force ? 1 : 0); + } + + /** + * Sets up this key as a deep copy of another key. + * + * @param other the key to deep copy into this key + */ + public void setTo(ThemeKey other) { + mResId = other.mResId == null ? null : other.mResId.clone(); + mForce = other.mForce == null ? null : other.mForce.clone(); + mCount = other.mCount; + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass() || hashCode() != o.hashCode()) { + return false; + } + + final ThemeKey t = (ThemeKey) o; + if (mCount != t.mCount) { + return false; + } + + final int N = mCount; + for (int i = 0; i < N; i++) { + if (mResId[i] != t.mResId[i] || mForce[i] != t.mForce[i]) { + return false; + } + } + + return true; + } + + /** + * @return a shallow copy of this key + */ + @Override + public ThemeKey clone() { + final ThemeKey other = new ThemeKey(); + other.mResId = mResId; + other.mForce = mForce; + other.mCount = mCount; + return other; + } } /** @@ -1944,8 +2048,8 @@ public class Resources { + " final compat is " + mCompatibilityInfo); } - clearDrawableCachesLocked(mDrawableCache, configChanges); - clearDrawableCachesLocked(mColorDrawableCache, configChanges); + mDrawableCache.onConfigurationChange(configChanges); + mColorDrawableCache.onConfigurationChange(configChanges); mColorStateListCache.onConfigurationChange(configChanges); mAnimatorCache.onConfigurationChange(configChanges); mStateListAnimatorCache.onConfigurationChange(configChanges); @@ -1983,48 +2087,6 @@ public class Resources { return configChanges; } - private void clearDrawableCachesLocked( - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - int configChanges) { - final int N = caches.size(); - for (int i = 0; i < N; i++) { - clearDrawableCacheLocked(caches.valueAt(i), configChanges); - } - } - - private void clearDrawableCacheLocked( - LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) { - if (DEBUG_CONFIG) { - Log.d(TAG, "Cleaning up drawables config changes: 0x" - + Integer.toHexString(configChanges)); - } - final int N = cache.size(); - for (int i = 0; i < N; i++) { - final WeakReference<ConstantState> ref = cache.valueAt(i); - if (ref != null) { - final ConstantState cs = ref.get(); - if (cs != null) { - if (Configuration.needNewResources( - configChanges, cs.getChangingConfigurations())) { - if (DEBUG_CONFIG) { - Log.d(TAG, "FLUSHING #0x" - + Long.toHexString(cache.keyAt(i)) - + " / " + cs + " with changes: 0x" - + Integer.toHexString(cs.getChangingConfigurations())); - } - cache.setValueAt(i, null); - } else if (DEBUG_CONFIG) { - Log.d(TAG, "(Keeping #0x" - + Long.toHexString(cache.keyAt(i)) - + " / " + cs + " with changes: 0x" - + Integer.toHexString(cs.getChangingConfigurations()) - + ")"); - } - } - } - } - } - /** * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated) * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively. @@ -2436,7 +2498,7 @@ public class Resources { } final boolean isColorDrawable; - final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches; + final DrawableCache caches; final long key; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { @@ -2452,7 +2514,7 @@ public class Resources { // First, check whether we have a cached version of this drawable // that was inflated against the specified theme. if (!mPreloading) { - final Drawable cachedDrawable = getCachedDrawable(caches, key, theme); + final Drawable cachedDrawable = caches.getInstance(key, theme); if (cachedDrawable != null) { return cachedDrawable; } @@ -2479,13 +2541,8 @@ public class Resources { // Determine if the drawable has unresolved theme attributes. If it // does, we'll need to apply a theme and store it in a theme-specific // cache. - final String cacheKey; - if (!dr.canApplyTheme()) { - cacheKey = CACHE_NOT_THEMED; - } else if (theme == null) { - cacheKey = CACHE_NULL_THEME; - } else { - cacheKey = theme.getKey(); + final boolean canApplyTheme = dr.canApplyTheme(); + if (canApplyTheme && theme != null) { dr = dr.mutate(); dr.applyTheme(theme); dr.clearMutated(); @@ -2495,15 +2552,14 @@ public class Resources { // cache: preload, not themed, null theme, or theme-specific. if (dr != null) { dr.setChangingConfigurations(value.changingConfigurations); - cacheDrawable(value, isColorDrawable, caches, cacheKey, key, dr); + cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); } return dr; } - private void cacheDrawable(TypedValue value, boolean isColorDrawable, - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - String cacheKey, long key, Drawable dr) { + private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches, + Theme theme, boolean usesTheme, long key, Drawable dr) { final ConstantState cs = dr.getConstantState(); if (cs == null) { return; @@ -2531,54 +2587,12 @@ public class Resources { } } else { synchronized (mAccessLock) { - LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(cacheKey); - if (themedCache == null) { - // Clean out the caches before we add more. This shouldn't - // happen very often. - pruneCaches(caches); - themedCache = new LongSparseArray<>(1); - caches.put(cacheKey, themedCache); - } - themedCache.put(key, new WeakReference<>(cs)); - } - } - } - - /** - * Prunes empty caches from the cache map. - * - * @param caches The map of caches to prune. - */ - private void pruneCaches(ArrayMap<String, - LongSparseArray<WeakReference<ConstantState>>> caches) { - final int N = caches.size(); - for (int i = N - 1; i >= 0; i--) { - final LongSparseArray<WeakReference<ConstantState>> cache = caches.valueAt(i); - if (pruneCache(cache)) { - caches.removeAt(i); + caches.put(key, theme, cs, usesTheme); } } } /** - * Prunes obsolete weak references from a cache, returning {@code true} if - * the cache is empty and should be removed. - * - * @param cache The cache of weak references to prune. - * @return {@code true} if the cache is empty and should be removed. - */ - private boolean pruneCache(LongSparseArray<WeakReference<ConstantState>> cache) { - final int N = cache.size(); - for (int i = N - 1; i >= 0; i--) { - final WeakReference entry = cache.valueAt(i); - if (entry == null || entry.get() == null) { - cache.removeAt(i); - } - } - return cache.size() == 0; - } - - /** * Loads a drawable from XML or resources stream. */ private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) { @@ -2631,51 +2645,6 @@ public class Resources { return dr; } - private Drawable getCachedDrawable( - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - long key, Theme theme) { - synchronized (mAccessLock) { - // First search theme-agnostic cache. - final Drawable unthemedDrawable = getCachedDrawableLocked( - caches, key, CACHE_NOT_THEMED); - if (unthemedDrawable != null) { - return unthemedDrawable; - } - - // Next search theme-specific cache. - final String themeKey = theme != null ? theme.getKey() : CACHE_NULL_THEME; - return getCachedDrawableLocked(caches, key, themeKey); - } - } - - private Drawable getCachedDrawableLocked( - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - long key, String themeKey) { - final LongSparseArray<WeakReference<ConstantState>> cache = caches.get(themeKey); - if (cache != null) { - final ConstantState entry = getConstantStateLocked(cache, key); - if (entry != null) { - return entry.newDrawable(this); - } - } - return null; - } - - private ConstantState getConstantStateLocked( - LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { - final WeakReference<ConstantState> wr = drawableCache.get(key); - if (wr != null) { - final ConstantState entry = wr.get(); - if (entry != null) { - return entry; - } else { - // Our entry has been purged. - drawableCache.delete(key); - } - } - return null; - } - @Nullable ColorStateList loadColorStateList(TypedValue value, int id, Theme theme) throws NotFoundException { @@ -2713,8 +2682,7 @@ public class Resources { } final ConfigurationBoundResourceCache<ColorStateList> cache = mColorStateListCache; - - csl = cache.get(key, theme); + csl = cache.getInstance(key, theme); if (csl != null) { return csl; } diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java new file mode 100644 index 0000000..9a2d06147 --- /dev/null +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources.Theme; +import android.content.res.Resources.ThemeKey; +import android.util.LongSparseArray; +import android.util.ArrayMap; + +import java.lang.ref.WeakReference; + +/** + * Data structure used for caching data against themes. + * + * @param <T> type of data to cache + */ +abstract class ThemedResourceCache<T> { + private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries; + private LongSparseArray<WeakReference<T>> mUnthemedEntries; + private LongSparseArray<WeakReference<T>> mNullThemedEntries; + + /** + * Adds a new theme-dependent entry to the cache. + * + * @param key a key that uniquely identifies the entry + * @param theme the theme against which this entry was inflated, or + * {@code null} if the entry has no theme applied + * @param entry the entry to cache + */ + public void put(long key, @Nullable Theme theme, @NonNull T entry) { + put(key, theme, entry, true); + } + + /** + * Adds a new entry to the cache. + * + * @param key a key that uniquely identifies the entry + * @param theme the theme against which this entry was inflated, or + * {@code null} if the entry has no theme applied + * @param entry the entry to cache + * @param usesTheme {@code true} if the entry is affected theme changes, + * {@code false} otherwise + */ + public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) { + if (entry == null) { + return; + } + + synchronized (this) { + final LongSparseArray<WeakReference<T>> entries; + if (!usesTheme) { + entries = getUnthemedLocked(true); + } else { + entries = getThemedLocked(theme, true); + } + if (entries != null) { + entries.put(key, new WeakReference<>(entry)); + } + } + } + + /** + * Returns an entry from the cache. + * + * @param key a key that uniquely identifies the entry + * @param theme the theme where the entry will be used + * @return a cached entry, or {@code null} if not in the cache + */ + @Nullable + public T get(long key, @Nullable Theme theme) { + // The themed (includes null-themed) and unthemed caches are mutually + // exclusive, so we'll give priority to whichever one we think we'll + // hit first. Since most of the framework drawables are themed, that's + // probably going to be the themed cache. + synchronized (this) { + final LongSparseArray<WeakReference<T>> themedEntries = getThemedLocked(theme, false); + if (themedEntries != null) { + final WeakReference<T> themedEntry = themedEntries.get(key); + if (themedEntry != null) { + return themedEntry.get(); + } + } + + final LongSparseArray<WeakReference<T>> unthemedEntries = getUnthemedLocked(false); + if (unthemedEntries != null) { + final WeakReference<T> unthemedEntry = unthemedEntries.get(key); + if (unthemedEntry != null) { + return unthemedEntry.get(); + } + } + } + + return null; + } + + /** + * Prunes cache entries that have been invalidated by a configuration + * change. + * + * @param configChanges a bitmask of configuration changes + */ + public void onConfigurationChange(int configChanges) { + prune(configChanges); + } + + /** + * Returns whether a cached entry has been invalidated by a configuration + * change. + * + * @param entry a cached entry + * @param configChanges a non-zero bitmask of configuration changes + * @return {@code true} if the entry is invalid, {@code false} otherwise + */ + protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges); + + /** + * Returns the cached data for the specified theme, optionally creating a + * new entry if one does not already exist. + * + * @param t the theme for which to return cached data + * @param create {@code true} to create an entry if one does not already + * exist, {@code false} otherwise + * @return the cached data for the theme, or {@code null} if the cache is + * empty and {@code create} was {@code false} + */ + @Nullable + private LongSparseArray<WeakReference<T>> getThemedLocked(@Nullable Theme t, boolean create) { + if (t == null) { + if (mNullThemedEntries == null && create) { + mNullThemedEntries = new LongSparseArray<>(1); + } + return mNullThemedEntries; + } + + if (mThemedEntries == null) { + if (create) { + mThemedEntries = new ArrayMap<>(1); + } else { + return null; + } + } + + final ThemeKey key = t.getKey(); + LongSparseArray<WeakReference<T>> cache = mThemedEntries.get(key); + if (cache == null && create) { + cache = new LongSparseArray<>(1); + + final ThemeKey keyClone = key.clone(); + mThemedEntries.put(keyClone, cache); + } + + return cache; + } + + /** + * Returns the theme-agnostic cached data. + * + * @param create {@code true} to create an entry if one does not already + * exist, {@code false} otherwise + * @return the theme-agnostic cached data, or {@code null} if the cache is + * empty and {@code create} was {@code false} + */ + @Nullable + private LongSparseArray<WeakReference<T>> getUnthemedLocked(boolean create) { + if (mUnthemedEntries == null && create) { + mUnthemedEntries = new LongSparseArray<>(1); + } + return mUnthemedEntries; + } + + /** + * Prunes cache entries affected by configuration changes or where weak + * references have expired. + * + * @param configChanges a bitmask of configuration changes, or {@code 0} to + * simply prune missing weak references + * @return {@code true} if the cache is completely empty after pruning + */ + private boolean prune(int configChanges) { + synchronized (this) { + if (mThemedEntries != null) { + for (int i = mThemedEntries.size() - 1; i >= 0; i--) { + if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { + mThemedEntries.removeAt(i); + } + } + } + + pruneEntriesLocked(mNullThemedEntries, configChanges); + pruneEntriesLocked(mUnthemedEntries, configChanges); + + return mThemedEntries == null && mNullThemedEntries == null + && mUnthemedEntries == null; + } + } + + private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries, + int configChanges) { + if (entries == null) { + return true; + } + + for (int i = entries.size() - 1; i >= 0; i--) { + final WeakReference<T> ref = entries.valueAt(i); + if (ref == null || pruneEntryLocked(ref.get(), configChanges)) { + entries.removeAt(i); + } + } + + return entries.size() == 0; + } + + private boolean pruneEntryLocked(@Nullable T entry, int configChanges) { + return entry == null || (configChanges != 0 + && shouldInvalidateEntry(entry, configChanges)); + } +} diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java index 3a36b0a..be48633 100644 --- a/core/java/android/ddm/DdmHandleViewDebug.java +++ b/core/java/android/ddm/DdmHandleViewDebug.java @@ -229,15 +229,25 @@ public class DdmHandleViewDebug extends ChunkHandler { private Chunk dumpHierarchy(View rootView, ByteBuffer in) { boolean skipChildren = in.getInt() > 0; boolean includeProperties = in.getInt() > 0; + boolean v2 = in.hasRemaining() && in.getInt() > 0; - ByteArrayOutputStream b = new ByteArrayOutputStream(1024); + long start = System.currentTimeMillis(); + + ByteArrayOutputStream b = new ByteArrayOutputStream(2*1024*1024); try { - ViewDebug.dump(rootView, skipChildren, includeProperties, b); - } catch (IOException e) { + if (v2) { + ViewDebug.dumpv2(rootView, b); + } else { + ViewDebug.dump(rootView, skipChildren, includeProperties, b); + } + } catch (IOException | InterruptedException e) { return createFailChunk(1, "Unexpected error while obtaining view hierarchy: " + e.getMessage()); } + long end = System.currentTimeMillis(); + Log.d(TAG, "Time to obtain view hierarchy (ms): " + (end - start)); + byte[] data = b.toByteArray(); return new Chunk(CHUNK_VURT, data, 0, data.length); } diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index aeddf03..ef71c42 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -212,8 +212,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * <p>All capture sessions can be used for capturing images from the camera but only capture * sessions created by * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} - * can submit reprocess capture requests. The list of requests must all be capturing images from - * the camera or all be reprocess capture requests. Submitting a reprocess request to a regular + * can submit reprocess capture requests. Submitting a reprocess request to a regular * capture session will result in an {@link IllegalArgumentException}.</p> * * @param requests the list of settings for this burst capture @@ -236,9 +235,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalArgumentException If the requests target no Surfaces, or the requests target * Surfaces not currently configured as outputs; or a reprocess * capture request is submitted in a non-reprocessible capture - * session; or the list of requests contains both requests to - * capture images from the camera and reprocess capture - * requests; or one of the reprocess capture requests was + * session; or one of the reprocess capture requests was * created with a {@link TotalCaptureResult} from a different * session; or one of the captures targets a Surface in the * middle of being {@link #prepare prepared}; or if the handler diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 3c19529..dff6227 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -177,26 +177,20 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { public synchronized int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, Handler handler) throws CameraAccessException { if (requests == null) { - throw new IllegalArgumentException("requests must not be null"); + throw new IllegalArgumentException("Requests must not be null"); } else if (requests.isEmpty()) { - throw new IllegalArgumentException("requests must have at least one element"); - } - - boolean reprocess = requests.get(0).isReprocess(); - if (reprocess && !isReprocessible()) { - throw new IllegalArgumentException("this capture session cannot handle reprocess " + - "requests"); - } else if (reprocess && requests.get(0).getReprocessibleSessionId() != mId) { - throw new IllegalArgumentException("capture request was created for another session"); + throw new IllegalArgumentException("Requests must have at least one element"); } - for (int i = 1; i < requests.size(); i++) { - if (requests.get(i).isReprocess() != reprocess) { - throw new IllegalArgumentException("cannot mix regular and reprocess capture " + - " requests"); - } else if (reprocess && requests.get(i).getReprocessibleSessionId() != mId) { - throw new IllegalArgumentException("capture request was created for another " + - "session"); + for (CaptureRequest request : requests) { + if (request.isReprocess()) { + if (!isReprocessible()) { + throw new IllegalArgumentException("This capture session cannot handle " + + "reprocess requests"); + } else if (request.getReprocessibleSessionId() != mId) { + throw new IllegalArgumentException("Capture request was created for another " + + "session"); + } } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index ff4ad79..e84b46a 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -94,11 +94,11 @@ public class CameraDeviceImpl extends CameraDevice { private final int mTotalPartialCount; /** - * A list tracking request and its expected last frame. - * Updated when calling ICameraDeviceUser methods. + * A list tracking request and its expected last regular frame number and last reprocess frame + * number. Updated when calling ICameraDeviceUser methods. */ - private final List<SimpleEntry</*frameNumber*/Long, /*requestId*/Integer>> - mFrameNumberRequestPairs = new ArrayList<SimpleEntry<Long, Integer>>(); + private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList = + new ArrayList<>(); /** * An object tracking received frame numbers. @@ -653,8 +653,8 @@ public class CameraDeviceImpl extends CameraDevice { * * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never * sent to HAL. Then onCaptureSequenceAborted is immediately triggered. - * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair - * is added to the list mFrameNumberRequestPairs.</p> + * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last + * regular frame number will be added to the list mRequestLastFrameNumbersList.</p> * * @param requestId the request ID of the current repeating request. * @@ -693,10 +693,6 @@ public class CameraDeviceImpl extends CameraDevice { "early trigger sequence complete for request %d", requestId)); } - if (lastFrameNumber < Integer.MIN_VALUE - || lastFrameNumber > Integer.MAX_VALUE) { - throw new AssertionError(lastFrameNumber + " cannot be cast to int"); - } holder.getCallback().onCaptureSequenceAborted( CameraDeviceImpl.this, requestId); @@ -710,9 +706,11 @@ public class CameraDeviceImpl extends CameraDevice { requestId)); } } else { - mFrameNumberRequestPairs.add( - new SimpleEntry<Long, Integer>(lastFrameNumber, - requestId)); + // This function is only called for regular request so lastFrameNumber is the last + // regular frame number. + mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId, + lastFrameNumber)); + // It is possible that the last frame has already arrived, so we need to check // for sequence completion right away checkAndFireSequenceComplete(); @@ -779,8 +777,8 @@ public class CameraDeviceImpl extends CameraDevice { } mRepeatingRequestId = requestId; } else { - mFrameNumberRequestPairs.add( - new SimpleEntry<Long, Integer>(lastFrameNumber, requestId)); + mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestList, + requestId, lastFrameNumber)); } if (mIdle) { @@ -1146,7 +1144,101 @@ public class CameraDeviceImpl extends CameraDevice { public int getSessionId() { return mSessionId; } + } + + /** + * This class holds a capture ID and its expected last regular frame number and last reprocess + * frame number. + */ + static class RequestLastFrameNumbersHolder { + // request ID + private final int mRequestId; + // The last regular frame number for this request ID. It's + // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request. + private final long mLastRegularFrameNumber; + // The last reprocess frame number for this request ID. It's + // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request. + private final long mLastReprocessFrameNumber; + + /** + * Create a request-last-frame-numbers holder with a list of requests, request ID, and + * the last frame number returned by camera service. + */ + public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, int requestId, + long lastFrameNumber) { + long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + long frameNumber = lastFrameNumber; + + if (lastFrameNumber < requestList.size() - 1) { + throw new IllegalArgumentException("lastFrameNumber: " + lastFrameNumber + + " should be at least " + (requestList.size() - 1) + " for the number of " + + " requests in the list: " + requestList.size()); + } + + // find the last regular frame number and the last reprocess frame number + for (int i = requestList.size() - 1; i >= 0; i--) { + CaptureRequest request = requestList.get(i); + if (request.isReprocess() && lastReprocessFrameNumber == + CaptureCallback.NO_FRAMES_CAPTURED) { + lastReprocessFrameNumber = frameNumber; + } else if (!request.isReprocess() && lastRegularFrameNumber == + CaptureCallback.NO_FRAMES_CAPTURED) { + lastRegularFrameNumber = frameNumber; + } + + if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED && + lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) { + break; + } + + frameNumber--; + } + + mLastRegularFrameNumber = lastRegularFrameNumber; + mLastReprocessFrameNumber = lastReprocessFrameNumber; + mRequestId = requestId; + } + + /** + * Create a request-last-frame-numbers holder with a request ID and last regular frame + * number. + */ + public RequestLastFrameNumbersHolder(int requestId, long lastRegularFrameNumber) { + mLastRegularFrameNumber = lastRegularFrameNumber; + mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + mRequestId = requestId; + } + + /** + * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if + * it contains no regular request. + */ + public long getLastRegularFrameNumber() { + return mLastRegularFrameNumber; + } + + /** + * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if + * it contains no reprocess request. + */ + public long getLastReprocessFrameNumber() { + return mLastReprocessFrameNumber; + } + /** + * Return the last frame number overall. + */ + public long getLastFrameNumber() { + return Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber); + } + + /** + * Return the request ID. + */ + public int getRequestId() { + return mRequestId; + } } /** @@ -1154,8 +1246,8 @@ public class CameraDeviceImpl extends CameraDevice { */ public class FrameNumberTracker { - private long mCompletedFrameNumber = -1; - private long mCompletedReprocessFrameNumber = -1; + private long mCompletedFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + private long mCompletedReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; /** the skipped frame numbers that belong to regular results */ private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>(); /** the skipped frame numbers that belong to reprocess results */ @@ -1360,11 +1452,11 @@ public class CameraDeviceImpl extends CameraDevice { long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); boolean isReprocess = false; - Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator(); + Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); while (iter.hasNext()) { - final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next(); + final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); boolean sequenceCompleted = false; - final int requestId = frameNumberRequestPair.getValue(); + final int requestId = requestLastFrameNumbers.getRequestId(); final CaptureCallbackHolder holder; synchronized(mInterfaceLock) { if (mRemoteDevice == null) { @@ -1376,19 +1468,22 @@ public class CameraDeviceImpl extends CameraDevice { holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; if (holder != null) { - isReprocess = holder.getRequest().isReprocess(); + long lastRegularFrameNumber = + requestLastFrameNumbers.getLastRegularFrameNumber(); + long lastReprocessFrameNumber = + requestLastFrameNumbers.getLastReprocessFrameNumber(); + // check if it's okay to remove request from mCaptureCallbackMap - if ((isReprocess && frameNumberRequestPair.getKey() <= - completedReprocessFrameNumber) || (!isReprocess && - frameNumberRequestPair.getKey() <= completedFrameNumber)) { + if (lastRegularFrameNumber <= completedFrameNumber && + lastReprocessFrameNumber <= completedReprocessFrameNumber) { sequenceCompleted = true; mCaptureCallbackMap.removeAt(index); if (DEBUG) { Log.v(TAG, String.format( - "remove holder for requestId %d, " - + "because lastFrame %d is <= %d", - requestId, frameNumberRequestPair.getKey(), - completedFrameNumber)); + "Remove holder for requestId %d, because lastRegularFrame %d " + + "is <= %d and lastReprocessFrame %d is <= %d", requestId, + lastRegularFrameNumber, completedFrameNumber, + lastReprocessFrameNumber, completedReprocessFrameNumber)); } } } @@ -1412,16 +1507,10 @@ public class CameraDeviceImpl extends CameraDevice { requestId)); } - long lastFrameNumber = frameNumberRequestPair.getKey(); - if (lastFrameNumber < Integer.MIN_VALUE - || lastFrameNumber > Integer.MAX_VALUE) { - throw new AssertionError(lastFrameNumber - + " cannot be cast to int"); - } holder.getCallback().onCaptureSequenceCompleted( CameraDeviceImpl.this, requestId, - lastFrameNumber); + requestLastFrameNumbers.getLastFrameNumber()); } } }; diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 36fc4f9..50eed3e 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -173,6 +173,27 @@ public class Build { "ro.build.version.sdk", 0); /** + * The developer preview revision of a prerelease SDK. This value will always + * be <code>0</code> on production platform builds/devices. + * + * <p>When this value is nonzero, any new API added since the last + * officially published {@link #SDK_INT API level} is only guaranteed to be present + * on that specific preview revision. For example, an API <code>Activity.fooBar()</code> + * might be present in preview revision 1 but renamed or removed entirely in + * preview revision 2, which may cause an app attempting to call it to crash + * at runtime.</p> + * + * <p>Experimental apps targeting preview APIs should check this value for + * equality (<code>==</code>) with the preview SDK revision they were built for + * before using any prerelease platform APIs. Apps that detect a preview SDK revision + * other than the specific one they expect should fall back to using APIs from + * the previously published API level only to avoid unwanted runtime exceptions. + * </p> + */ + public static final int PREVIEW_SDK_INT = SystemProperties.getInt( + "ro.build.version.preview_sdk", 0); + + /** * The current development codename, or the string "REL" if this is * a release build. */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 293cf6f..7c3c11b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -324,23 +324,7 @@ public final class Settings { "android.settings.BLUETOOTH_SETTINGS"; /** - * Activity Action: Show settings to allow configuration of Wifi Displays. - * <p> - * In some cases, a matching Activity may not exist, so ensure you - * safeguard against this. - * <p> - * Input: Nothing. - * <p> - * Output: Nothing. - * @hide - */ - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_WIFI_DISPLAY_SETTINGS = - "android.settings.WIFI_DISPLAY_SETTINGS"; - - /** - * Activity Action: Show settings to allow configuration of - * {@link android.media.routing.MediaRouteService media route providers}. + * Activity Action: Show settings to allow configuration of cast endpoints. * <p> * In some cases, a matching Activity may not exist, so ensure you * safeguard against this. @@ -5951,6 +5935,12 @@ public final class Settings { "wireless_charging_started_sound"; /** + * Whether to play a sound for charging events. + * @hide + */ + public static final String CHARGING_SOUNDS_ENABLED = "charging_sounds_enabled"; + + /** * Whether we keep the device on while the device is plugged in. * Supported values are: * <ul> @@ -6166,6 +6156,17 @@ public final class Settings { */ public static final String MOBILE_DATA = "mobile_data"; + /** + * Whether the mobile data connection should remain active even when higher + * priority networks like WiFi are active, to help make network switching faster. + * + * See ConnectivityService for more info. + * + * (0 = disabled, 1 = enabled) + * @hide + */ + public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; + /** {@hide} */ public static final String NETSTATS_ENABLED = "netstats_enabled"; /** {@hide} */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 29aaf30..0171869 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -40,7 +40,7 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.ViewGroup; diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index cf29310..3eeb04a 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1481,14 +1481,8 @@ public class TextToSpeech { // interface). // Sanitize locale using isLanguageAvailable. - int result = service.isLanguageAvailable( language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } + int result = service.isLanguageAvailable(language, country, variant); + if (result >= LANG_AVAILABLE) { // Get the default voice for the locale. String voiceName = service.getDefaultVoiceNameFor(language, country, variant); if (TextUtils.isEmpty(voiceName)) { @@ -1502,10 +1496,28 @@ public class TextToSpeech { return LANG_NOT_SUPPORTED; } + // Set the language/country/variant of the voice, so #getLanguage will return + // the currently set voice locale when called. + Voice voice = getVoice(service, voiceName); + String voiceLanguage = ""; + try { + voiceLanguage = voice.getLocale().getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + + voice.getLocale(), e); + } + + String voiceCountry = ""; + try { + voiceCountry = voice.getLocale().getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + + voice.getLocale(), e); + } mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName); - mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); - mParams.putString(Engine.KEY_PARAM_COUNTRY, country); - mParams.putString(Engine.KEY_PARAM_VARIANT, variant); + mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage); + mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry); + mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); } return result; } @@ -1654,20 +1666,32 @@ public class TextToSpeech { if (TextUtils.isEmpty(voiceName)) { return null; } - List<Voice> voices = service.getVoices(); - if (voices == null) { - return null; - } - for (Voice voice : voices) { - if (voice.getName().equals(voiceName)) { - return voice; - } - } - return null; + return getVoice(service, voiceName); } }, null, "getVoice"); } + + /** + * Returns a Voice instance of the voice with the given voice name. + * + * @return Voice instance with the given voice name, or {@code null} if not set or on error. + * + * @see Voice + */ + private Voice getVoice(ITextToSpeechService service, String voiceName) throws RemoteException { + List<Voice> voices = service.getVoices(); + if (voices == null) { + return null; + } + for (Voice voice : voices) { + if (voice.getName().equals(voiceName)) { + return voice; + } + } + return null; + } + /** * Returns a Voice instance that's the default voice for the default Text-to-speech language. * @return The default voice instance for the default language, or {@code null} if not set or @@ -1690,14 +1714,7 @@ public class TextToSpeech { // Sanitize the locale using isLanguageAvailable. int result = service.isLanguageAvailable(language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } - } else { + if (result < LANG_AVAILABLE) { // The default language is not supported. return null; } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 451abea..59c7c6d 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -91,6 +91,7 @@ public class StaticLayout extends Layout { b.mEllipsizedWidth = width; b.mEllipsize = null; b.mMaxLines = Integer.MAX_VALUE; + b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; b.mMeasuredText = MeasuredText.obtain(); return b; @@ -100,6 +101,8 @@ public class StaticLayout extends Layout { b.mPaint = null; b.mText = null; MeasuredText.recycle(b.mMeasuredText); + b.mMeasuredText = null; + nFinishBuilder(b.mNativePtr); sPool.release(b); } diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java index 11226a9..c4dc5ed 100644 --- a/core/java/android/text/method/WordIterator.java +++ b/core/java/android/text/method/WordIterator.java @@ -95,6 +95,45 @@ public class WordIterator implements Selection.PositionIterator { } while (true); } + /** {@inheritDoc} */ + public boolean isBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + checkOffsetIsValid(shiftedOffset); + return mIterator.isBoundary(shiftedOffset); + } + + /** + * Returns the position of next boundary after the given offset. Returns + * {@code DONE} if there is no boundary after the given offset. + * + * @param offset the given start position to search from. + * @return the position of the last boundary preceding the given offset. + */ + public int nextBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + shiftedOffset = mIterator.following(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + return shiftedOffset + mOffsetShift; + } + + /** + * Returns the position of boundary preceding the given offset or + * {@code DONE} if the given offset specifies the starting position. + * + * @param offset the given start position to search from. + * @return the position of the last boundary preceding the given offset. + */ + public int prevBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + shiftedOffset = mIterator.preceding(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + return shiftedOffset + mOffsetShift; + } + /** If <code>offset</code> is within a word, returns the index of the first character of that * word, otherwise returns BreakIterator.DONE. * diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index ebc2aac..1b25505 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1639,6 +1639,7 @@ public abstract class Transition implements Cloneable { for (int i = 0; i < count; i++) { TransitionValues values = lookIn.get(i); if (values == null) { + // Null values are always added to the end of the list, so we know to stop now. return null; } if (values.view == view) { @@ -1742,6 +1743,9 @@ public abstract class Transition implements Cloneable { View oldView = oldInfo.view; TransitionValues startValues = getTransitionValues(oldView, true); TransitionValues endValues = getMatchedTransitionValues(oldView, true); + if (startValues == null && endValues == null) { + endValues = mEndValues.viewValues.get(oldView); + } boolean cancel = (startValues != null || endValues != null) && oldInfo.transition.areValuesChanged(oldValues, endValues); if (cancel) { diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java index 8f488af..bb7d15f 100644 --- a/core/java/android/util/FloatMath.java +++ b/core/java/android/util/FloatMath.java @@ -25,6 +25,8 @@ package android.util; * {@link java.lang.Math}. {@link java.lang.Math} should be used in * preference. * + * <p>All methods were removed from the public API in version 23. + * * @deprecated Use {@link java.lang.Math} instead. */ @Deprecated @@ -39,6 +41,7 @@ public class FloatMath { * * @param value to be converted * @return the floor of value + * @removed */ public static float floor(float value) { return (float) Math.floor(value); @@ -50,6 +53,7 @@ public class FloatMath { * * @param value to be converted * @return the ceiling of value + * @removed */ public static float ceil(float value) { return (float) Math.ceil(value); @@ -60,6 +64,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the sine of angle + * @removed */ public static float sin(float angle) { return (float) Math.sin(angle); @@ -70,6 +75,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the cosine of angle + * @removed */ public static float cos(float angle) { return (float) Math.cos(angle); @@ -81,6 +87,7 @@ public class FloatMath { * * @param value to compute sqrt of * @return the square root of value + * @removed */ public static float sqrt(float value) { return (float) Math.sqrt(value); @@ -92,6 +99,7 @@ public class FloatMath { * * @param value to compute the exponential of * @return the exponential of value + * @removed */ public static float exp(float value) { return (float) Math.exp(value); @@ -104,6 +112,7 @@ public class FloatMath { * @param x the base of the operation. * @param y the exponent of the operation. * @return {@code x} to the power of {@code y}. + * @removed */ public static float pow(float x, float y) { return (float) Math.pow(x, y); @@ -116,6 +125,7 @@ public class FloatMath { * @param x a float number * @param y a float number * @return the hypotenuse + * @removed */ public static float hypot(float x, float y) { return (float) Math.hypot(x, y); diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java index bc38e1a..41502b6 100644 --- a/core/java/android/view/GhostView.java +++ b/core/java/android/view/GhostView.java @@ -40,8 +40,7 @@ public class GhostView extends View { mView.mGhostView = this; final ViewGroup parent = (ViewGroup) mView.getParent(); setGhostedVisibility(View.INVISIBLE); - parent.mRecreateDisplayList = true; - parent.updateDisplayListIfDirty(); + parent.invalidate(); } @Override @@ -83,8 +82,7 @@ public class GhostView extends View { mView.mGhostView = null; final ViewGroup parent = (ViewGroup) mView.getParent(); if (parent != null) { - parent.mRecreateDisplayList = true; - parent.updateDisplayListIfDirty(); + parent.invalidate(); } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ee6bbe1..e1f1816 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22359,4 +22359,138 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final String output = bits + " " + name; found.put(key, output); } + + /** {@hide} */ + public void encode(@NonNull ViewHierarchyEncoder stream) { + stream.beginObject(this); + encodeProperties(stream); + stream.endObject(); + } + + /** {@hide} */ + @CallSuper + protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { + Object resolveId = ViewDebug.resolveId(getContext(), mID); + if (resolveId instanceof String) { + stream.addProperty("id", (String) resolveId); + } else { + stream.addProperty("id", mID); + } + + stream.addProperty("misc:transformation.alpha", + mTransformationInfo != null ? mTransformationInfo.mAlpha : 0); + stream.addProperty("misc:transitionName", getTransitionName()); + + // layout + stream.addProperty("layout:left", mLeft); + stream.addProperty("layout:right", mRight); + stream.addProperty("layout:top", mTop); + stream.addProperty("layout:bottom", mBottom); + stream.addProperty("layout:width", getWidth()); + stream.addProperty("layout:height", getHeight()); + stream.addProperty("layout:layoutDirection", getLayoutDirection()); + stream.addProperty("layout:layoutRtl", isLayoutRtl()); + stream.addProperty("layout:hasTransientState", hasTransientState()); + stream.addProperty("layout:baseline", getBaseline()); + + // layout params + ViewGroup.LayoutParams layoutParams = getLayoutParams(); + if (layoutParams != null) { + stream.addPropertyKey("layoutParams"); + layoutParams.encode(stream); + } + + // scrolling + stream.addProperty("scrolling:scrollX", mScrollX); + stream.addProperty("scrolling:scrollY", mScrollY); + + // padding + stream.addProperty("padding:paddingLeft", mPaddingLeft); + stream.addProperty("padding:paddingRight", mPaddingRight); + stream.addProperty("padding:paddingTop", mPaddingTop); + stream.addProperty("padding:paddingBottom", mPaddingBottom); + stream.addProperty("padding:userPaddingRight", mUserPaddingRight); + stream.addProperty("padding:userPaddingLeft", mUserPaddingLeft); + stream.addProperty("padding:userPaddingBottom", mUserPaddingBottom); + stream.addProperty("padding:userPaddingStart", mUserPaddingStart); + stream.addProperty("padding:userPaddingEnd", mUserPaddingEnd); + + // measurement + stream.addProperty("measurement:minHeight", mMinHeight); + stream.addProperty("measurement:minWidth", mMinWidth); + stream.addProperty("measurement:measuredWidth", mMeasuredWidth); + stream.addProperty("measurement:measuredHeight", mMeasuredHeight); + + // drawing + stream.addProperty("drawing:elevation", getElevation()); + stream.addProperty("drawing:translationX", getTranslationX()); + stream.addProperty("drawing:translationY", getTranslationY()); + stream.addProperty("drawing:translationZ", getTranslationZ()); + stream.addProperty("drawing:rotation", getRotation()); + stream.addProperty("drawing:rotationX", getRotationX()); + stream.addProperty("drawing:rotationY", getRotationY()); + stream.addProperty("drawing:scaleX", getScaleX()); + stream.addProperty("drawing:scaleY", getScaleY()); + stream.addProperty("drawing:pivotX", getPivotX()); + stream.addProperty("drawing:pivotY", getPivotY()); + stream.addProperty("drawing:opaque", isOpaque()); + stream.addProperty("drawing:alpha", getAlpha()); + stream.addProperty("drawing:transitionAlpha", getTransitionAlpha()); + stream.addProperty("drawing:shadow", hasShadow()); + stream.addProperty("drawing:solidColor", getSolidColor()); + stream.addProperty("drawing:layerType", mLayerType); + stream.addProperty("drawing:willNotDraw", willNotDraw()); + stream.addProperty("drawing:hardwareAccelerated", isHardwareAccelerated()); + stream.addProperty("drawing:willNotCacheDrawing", willNotCacheDrawing()); + stream.addProperty("drawing:drawingCacheEnabled", isDrawingCacheEnabled()); + stream.addProperty("drawing:overlappingRendering", hasOverlappingRendering()); + + // focus + stream.addProperty("focus:hasFocus", hasFocus()); + stream.addProperty("focus:isFocused", isFocused()); + stream.addProperty("focus:isFocusable", isFocusable()); + stream.addProperty("focus:isFocusableInTouchMode", isFocusableInTouchMode()); + + stream.addProperty("misc:clickable", isClickable()); + stream.addProperty("misc:pressed", isPressed()); + stream.addProperty("misc:selected", isSelected()); + stream.addProperty("misc:touchMode", isInTouchMode()); + stream.addProperty("misc:hovered", isHovered()); + stream.addProperty("misc:activated", isActivated()); + + stream.addProperty("misc:visibility", getVisibility()); + stream.addProperty("misc:fitsSystemWindows", getFitsSystemWindows()); + stream.addProperty("misc:filterTouchesWhenObscured", getFilterTouchesWhenObscured()); + + stream.addProperty("misc:enabled", isEnabled()); + stream.addProperty("misc:soundEffectsEnabled", isSoundEffectsEnabled()); + stream.addProperty("misc:hapticFeedbackEnabled", isHapticFeedbackEnabled()); + + // theme attributes + Resources.Theme theme = getContext().getTheme(); + if (theme != null) { + stream.addPropertyKey("theme"); + theme.encode(stream); + } + + // view attribute information + int n = mAttributes != null ? mAttributes.length : 0; + stream.addProperty("meta:__attrCount__", n/2); + for (int i = 0; i < n; i += 2) { + stream.addProperty("meta:__attr__" + mAttributes[i], mAttributes[i+1]); + } + + stream.addProperty("misc:scrollBarStyle", getScrollBarStyle()); + + // text + stream.addProperty("text:textDirection", getTextDirection()); + stream.addProperty("text:textAlignment", getTextAlignment()); + + // accessibility + CharSequence contentDescription = getContentDescription(); + stream.addProperty("accessibility:contentDescription", + contentDescription == null ? "" : contentDescription.toString()); + stream.addProperty("accessibility:labelFor", getLabelFor()); + stream.addProperty("accessibility:importantForAccessibility", getImportantForAccessibility()); + } } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 27304f5..8bf53a8 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -800,6 +801,7 @@ public class ViewDebug { /** * Dumps the view hierarchy starting from the given view. + * @deprecated See {@link #dumpv2(View, ByteArrayOutputStream)} below. * @hide */ public static void dump(View root, boolean skipChildren, boolean includeProperties, @@ -825,6 +827,28 @@ public class ViewDebug { } /** + * Dumps the view hierarchy starting from the given view. + * Rather than using reflection, it uses View's encode method to obtain all the properties. + * @hide + */ + public static void dumpv2(@NonNull final View view, @NonNull ByteArrayOutputStream out) + throws InterruptedException { + final ViewHierarchyEncoder encoder = new ViewHierarchyEncoder(out); + final CountDownLatch latch = new CountDownLatch(1); + + view.post(new Runnable() { + @Override + public void run() { + view.encode(encoder); + latch.countDown(); + } + }); + + latch.await(2, TimeUnit.SECONDS); + encoder.endStream(); + } + + /** * Dumps the theme attributes from the given View. * @hide */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index c25042a..51c4760 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -6861,6 +6861,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } return String.valueOf(size); } + + /** @hide */ + void encode(@NonNull ViewHierarchyEncoder encoder) { + encoder.beginObject(this); + encodeProperties(encoder); + encoder.endObject(); + } + + /** @hide */ + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + encoder.addProperty("width", width); + encoder.addProperty("height", height); + } } /** @@ -7329,6 +7342,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager bottomMargin, paint); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + encoder.addProperty("leftMargin", leftMargin); + encoder.addProperty("topMargin", topMargin); + encoder.addProperty("rightMargin", rightMargin); + encoder.addProperty("bottomMargin", bottomMargin); + encoder.addProperty("startMargin", startMargin); + encoder.addProperty("endMargin", endMargin); + } } /* Describes a touched view and the ids of the pointers that it has captured. @@ -7665,4 +7690,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager canvas.drawLines(sDebugLines, paint); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("focus:descendantFocusability", getDescendantFocusability()); + encoder.addProperty("drawing:clipChildren", getClipChildren()); + encoder.addProperty("drawing:clipToPadding", getClipToPadding()); + encoder.addProperty("drawing:childrenDrawingOrderEnabled", isChildrenDrawingOrderEnabled()); + encoder.addProperty("drawing:persistentDrawingCache", getPersistentDrawingCache()); + + int n = getChildCount(); + encoder.addProperty("meta:__childCount__", (short)n); + for (int i = 0; i < n; i++) { + encoder.addPropertyKey("meta:__child__" + i); + getChildAt(i).encode(encoder); + } + } } diff --git a/core/java/android/view/ViewHierarchyEncoder.java b/core/java/android/view/ViewHierarchyEncoder.java new file mode 100644 index 0000000..8770216 --- /dev/null +++ b/core/java/android/view/ViewHierarchyEncoder.java @@ -0,0 +1,201 @@ +package android.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +/** + * {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out + * view hierarchies (the view tree, along with the properties for each view) to a stream. + * + * It is typically used as follows: + * <pre> + * ViewHierarchyEncoder e = new ViewHierarchyEncoder(); + * + * for (View view : views) { + * e.beginObject(view); + * e.addProperty("prop1", value); + * ... + * e.endObject(); + * } + * + * // repeat above snippet for each view, finally end with: + * e.endStream(); + * </pre> + * + * <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one + * corresponding to each view) with the property name as the key and the property value + * as the value. + * + * <p>Since the property names are practically the same across all views, rather than using + * the property name directly as the key, we use a short integer id corresponding to each + * property name as the key. A final map is added at the end which contains the mapping + * from the integer to its property name. + * + * <p>A value is encoded as a single byte type identifier followed by the encoding of the + * value. Only primitive types are supported as values, in addition to the Map type. + * + * @hide + */ +public class ViewHierarchyEncoder { + // Prefixes for simple primitives. These match the JNI definitions. + private static final byte SIG_BOOLEAN = 'Z'; + private static final byte SIG_BYTE = 'B'; + private static final byte SIG_SHORT = 'S'; + private static final byte SIG_INT = 'I'; + private static final byte SIG_LONG = 'J'; + private static final byte SIG_FLOAT = 'F'; + private static final byte SIG_DOUBLE = 'D'; + + // Prefixes for some commonly used objects + private static final byte SIG_STRING = 'R'; + + private static final byte SIG_MAP = 'M'; // a map with an short key + private static final short SIG_END_MAP = 0; + + private final DataOutputStream mStream; + + private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200); + private short mPropertyId = 1; + private Charset mCharset = Charset.forName("utf-8"); + + public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) { + mStream = new DataOutputStream(stream); + } + + public void beginObject(@NonNull Object o) { + startPropertyMap(); + addProperty("meta:__name__", o.getClass().getName()); + addProperty("meta:__hash__", o.hashCode()); + } + + public void endObject() { + endPropertyMap(); + } + + public void endStream() { + // write out the string table + startPropertyMap(); + addProperty("__name__", "propertyIndex"); + for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) { + writeShort(entry.getValue()); + writeString(entry.getKey()); + } + endPropertyMap(); + } + + public void addProperty(@NonNull String name, boolean v) { + writeShort(createPropertyIndex(name)); + writeBoolean(v); + } + + public void addProperty(@NonNull String name, short s) { + writeShort(createPropertyIndex(name)); + writeShort(s); + } + + public void addProperty(@NonNull String name, int v) { + writeShort(createPropertyIndex(name)); + writeInt(v); + } + + public void addProperty(@NonNull String name, float v) { + writeShort(createPropertyIndex(name)); + writeFloat(v); + } + + public void addProperty(@NonNull String name, @Nullable String s) { + writeShort(createPropertyIndex(name)); + writeString(s); + } + + /** + * Writes the given name as the property name, and leaves it to the callee + * to fill in value for this property. + */ + public void addPropertyKey(@NonNull String name) { + writeShort(createPropertyIndex(name)); + } + + private short createPropertyIndex(@NonNull String name) { + Short index = mPropertyNames.get(name); + if (index == null) { + index = mPropertyId++; + mPropertyNames.put(name, index); + } + + return index; + } + + private void startPropertyMap() { + try { + mStream.write(SIG_MAP); + } catch (IOException e) { + // does not happen since the stream simply wraps a ByteArrayOutputStream + } + } + + private void endPropertyMap() { + writeShort(SIG_END_MAP); + } + + private void writeBoolean(boolean v) { + try { + mStream.write(SIG_BOOLEAN); + mStream.write(v ? 1 : 0); + } catch (IOException e) { + // does not happen since the stream simply wraps a ByteArrayOutputStream + } + } + + private void writeShort(short s) { + try { + mStream.write(SIG_SHORT); + mStream.writeShort(s); + } catch (IOException e) { + // does not happen since the stream simply wraps a ByteArrayOutputStream + } + } + + private void writeInt(int i) { + try { + mStream.write(SIG_INT); + mStream.writeInt(i); + } catch (IOException e) { + // does not happen since the stream simply wraps a ByteArrayOutputStream + } + } + + private void writeFloat(float v) { + try { + mStream.write(SIG_FLOAT); + mStream.writeFloat(v); + } catch (IOException e) { + // does not happen since the stream simply wraps a ByteArrayOutputStream + } + } + + private void writeString(@Nullable String s) { + if (s == null) { + s = ""; + } + + try { + mStream.write(SIG_STRING); + byte[] bytes = s.getBytes(mCharset); + + short len = (short)Math.min(bytes.length, Short.MAX_VALUE); + mStream.writeShort(len); + + mStream.write(bytes, 0, len); + } catch (IOException e) { + // does not happen since the stream simply wraps a ByteArrayOutputStream + } + } +} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 080498c..ea1dadb 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -76,6 +76,7 @@ import android.widget.Scroller; import com.android.internal.R; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.PhoneFallbackEventHandler; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e983910..2797b6e 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -16,11 +16,11 @@ package android.view; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Presentation; import android.content.Context; import android.content.pm.ActivityInfo; -import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.IBinder; @@ -1119,6 +1119,15 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800; /** + * Flag to force the status bar window to be visible all the time. If the bar is hidden when + * this flag is set it will be shown again and the bar will have a transparent background. + * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT = 0x00001000; + + /** * Control flags that are private to the platform. * @hide */ @@ -2066,5 +2075,18 @@ public interface WindowManager extends ViewManager { } private CharSequence mTitle = ""; + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("x", x); + encoder.addProperty("y", y); + encoder.addProperty("horizontalWeight", horizontalWeight); + encoder.addProperty("verticalWeight", verticalWeight); + encoder.addProperty("type", type); + encoder.addProperty("flags", flags); + } } } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index b8b3719..e27e253 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -16,6 +16,7 @@ package android.webkit; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.annotation.Widget; import android.content.Context; @@ -43,6 +44,7 @@ import android.view.View; import android.view.ViewStructure; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -2576,4 +2578,18 @@ public class WebView extends AbsoluteLayout super.onFinishTemporaryDetach(); mProvider.getViewDelegate().onFinishTemporaryDetach(); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + checkThread(); + encoder.addProperty("webview:contentHeight", mProvider.getContentHeight()); + encoder.addProperty("webview:contentWidth", mProvider.getContentWidth()); + encoder.addProperty("webview:scale", mProvider.getScale()); + encoder.addProperty("webview:title", mProvider.getTitle()); + encoder.addProperty("webview:url", mProvider.getUrl()); + encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl()); + } } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index c57a53a..9903b7e 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -18,6 +18,7 @@ package android.widget; import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; @@ -56,6 +57,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; @@ -6330,6 +6332,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public LayoutParams(ViewGroup.LayoutParams source) { super(source); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("list:viewType", viewType); + encoder.addProperty("list:recycledHeaderFooter", recycledHeaderFooter); + encoder.addProperty("list:forceAdd", forceAdd); + } } /** @@ -6912,6 +6924,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("drawing:cacheColorHint", getCacheColorHint()); + encoder.addProperty("list:fastScrollEnabled", isFastScrollEnabled()); + encoder.addProperty("list:scrollingCacheEnabled", isScrollingCacheEnabled()); + encoder.addProperty("list:smoothScrollbarEnabled", isSmoothScrollbarEnabled()); + encoder.addProperty("list:stackFromBottom", isStackFromBottom()); + encoder.addProperty("list:textFilterEnabled", isTextFilterEnabled()); + + View selectedView = getSelectedView(); + if (selectedView != null) { + encoder.addPropertyKey("selectedView"); + selectedView.encode(encoder); + } + } + /** * Abstract positon scroller used to handle smooth scrolling. */ diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index e0b0e1f..f08141c 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -61,6 +61,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter implements ActionProvider.SubUiVisibilityListener { private static final String TAG = "ActionMenuPresenter"; private static final int ITEM_ANIMATION_DURATION = 150; + private static final boolean ACTIONBAR_ANIMATIONS_ENABLED = false; private OverflowMenuButton mOverflowButton; private boolean mReserveOverflow; @@ -414,7 +415,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter @Override public void updateMenuView(boolean cleared) { final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent(); - if (menuViewParent != null) { + if (menuViewParent != null && ACTIONBAR_ANIMATIONS_ENABLED) { setupItemAnimations(); } super.updateMenuView(cleared); diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java index d6f2276..278a8fb 100644 --- a/core/java/android/widget/ActionMenuView.java +++ b/core/java/android/widget/ActionMenuView.java @@ -15,6 +15,7 @@ */ package android.widget; +import android.annotation.NonNull; import android.annotation.StyleRes; import android.content.Context; import android.content.res.ColorStateList; @@ -28,6 +29,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import com.android.internal.view.menu.ActionMenuItemView; import com.android.internal.view.menu.MenuBuilder; @@ -835,5 +837,17 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo super(width, height); this.isOverflowButton = isOverflowButton; } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("layout:overFlowButton", isOverflowButton); + encoder.addProperty("layout:cellsUsed", cellsUsed); + encoder.addProperty("layout:extraPixels", extraPixels); + encoder.addProperty("layout:expandable", expandable); + encoder.addProperty("layout:preventEdgeOffset", preventEdgeOffset); + } } } diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 72cb0b5..54e3996 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.database.DataSetObserver; @@ -29,6 +30,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -1245,4 +1247,16 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { } } } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("scrolling:firstPosition", mFirstPosition); + encoder.addProperty("list:nextSelectedPosition", mNextSelectedPosition); + encoder.addProperty("list:nextSelectedRowId", mNextSelectedRowId); + encoder.addProperty("list:selectedPosition", mSelectedPosition); + encoder.addProperty("list:itemCount", mItemCount); + } } diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index 22e079c..6b4b2c7 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -16,6 +16,8 @@ package android.widget; +import android.annotation.NonNull; +import android.view.ViewHierarchyEncoder; import com.android.internal.R; import android.annotation.DrawableRes; @@ -459,4 +461,11 @@ public class CheckedTextView extends TextView implements Checkable { info.setCheckable(true); info.setChecked(mChecked); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { + super.encodeProperties(stream); + stream.addProperty("text:checked", isChecked()); + } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index f2afeeb..770077d 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -17,8 +17,10 @@ package android.widget; import android.annotation.DrawableRes; +import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.PorterDuff; +import android.view.ViewHierarchyEncoder; import com.android.internal.R; import android.content.Context; @@ -530,9 +532,16 @@ public abstract class CompoundButton extends Button implements Checkable { @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; - + super.onRestoreInstanceState(ss.getSuperState()); setChecked(ss.checked); requestLayout(); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { + super.encodeProperties(stream); + stream.addProperty("checked", isChecked()); + } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 712fdba..86a100f 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -688,44 +688,101 @@ public class Editor { private int getWordStart(int offset) { // FIXME - For this and similar methods we're not doing anything to check if there's // a LocaleSpan in the text, this may be something we should try handling or checking for. - int retOffset = getWordIteratorWithText().getBeginning(offset); - if (retOffset == BreakIterator.DONE) retOffset = offset; + int retOffset = getWordIteratorWithText().prevBoundary(offset); + if (isPunctBoundaryBehind(retOffset, true /* isStart */)) { + // If we're on a punctuation boundary we should continue to get the + // previous offset until we're not longer on a punctuation boundary. + retOffset = getWordIteratorWithText().prevBoundary(retOffset); + while (!isPunctBoundaryBehind(retOffset, false /* isStart */) + && retOffset != BreakIterator.DONE) { + retOffset = getWordIteratorWithText().prevBoundary(retOffset); + } + } + if (retOffset == BreakIterator.DONE) { + return offset; + } return retOffset; } - private int getWordEnd(int offset, boolean includePunctuation) { - int retOffset = getWordIteratorWithText().getEnd(offset); + private int getWordEnd(int offset) { + int retOffset = getWordIteratorWithText().nextBoundary(offset); + if (isPunctBoundaryForward(retOffset, true /* isStart */)) { + // If we're on a punctuation boundary we should continue to get the + // next offset until we're no longer on a punctuation boundary. + retOffset = getWordIteratorWithText().nextBoundary(retOffset); + while (!isPunctBoundaryForward(retOffset, false /* isStart */) + && retOffset != BreakIterator.DONE) { + retOffset = getWordIteratorWithText().nextBoundary(retOffset); + } + } if (retOffset == BreakIterator.DONE) { - retOffset = offset; - } else if (includePunctuation) { - retOffset = handlePunctuation(retOffset); + return offset; } return retOffset; } - private boolean isEndBoundary(int offset) { - int thisEnd = getWordEnd(offset, false); - return offset == thisEnd; - } + /** + * Checks for punctuation boundaries for the provided offset and the + * previous character. + * + * @param offset The offset to check from. + * @param isStart Whether the boundary being checked for is at the start or + * end of a punctuation sequence. + * @return Whether this is a punctuation boundary. + */ + private boolean isPunctBoundaryBehind(int offset, boolean isStart) { + CharSequence text = mTextView.getText(); + if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) { + return false; + } + int cp = Character.codePointAt(text, offset); + int prevCp = Character.codePointBefore(text, offset); - private boolean isStartBoundary(int offset) { - int thisStart = getWordStart(offset); - return thisStart == offset; + if (isPunctuation(cp)) { + // If it's the start, the current cp and the prev cp are + // punctuation. If it's at the end of a punctuation sequence the + // current is punctuation and the prev is not. + return isStart ? isPunctuation(prevCp) : !isPunctuation(prevCp); + } + return false; } - private int handlePunctuation(int offset) { - // FIXME - Check with UX how repeated ending punctuation should be handled. - // FIXME - Check with UX if / how we would handle non sentence ending characters. - // FIXME - Consider punctuation in different languages. + /** + * Checks for punctuation boundaries for the provided offset and the next + * character. + * + * @param offset The offset to check from. + * @param isStart Whether the boundary being checked for is at the start or + * end of a punctuation sequence. + * @return Whether this is a punctuation boundary. + */ + private boolean isPunctBoundaryForward(int offset, boolean isStart) { CharSequence text = mTextView.getText(); - if (offset < text.length()) { - int c = Character.codePointAt(text, offset); - if (c == 0x002e /* period */|| c == 0x003f /* question mark */ - || c == 0x0021 /* exclamation mark */) { - offset = Character.offsetByCodePoints(text, offset, 1); - } + if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) { + return false; + } + int cp = Character.codePointBefore(text, offset); + int nextCpOffset = Math.min(offset + Character.charCount(cp), text.length() - 1); + int nextCp = Character.codePointBefore(text, nextCpOffset); + + if (isPunctuation(cp)) { + // If it's the start, the current cp and the next cp are + // punctuation. If it's at the end of a punctuation sequence the + // current is punctuation and the next is not. + return isStart ? isPunctuation(nextCp) : !isPunctuation(nextCp); } - return offset; + return false; + } + + private boolean isPunctuation(int cp) { + int type = Character.getType(cp); + return (type == Character.CONNECTOR_PUNCTUATION || + type == Character.DASH_PUNCTUATION || + type == Character.END_PUNCTUATION || + type == Character.FINAL_QUOTE_PUNCTUATION || + type == Character.INITIAL_QUOTE_PUNCTUATION || + type == Character.OTHER_PUNCTUATION || + type == Character.START_PUNCTUATION); } /** @@ -788,7 +845,7 @@ public class Editor { if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE || selectionStart == selectionEnd) { // Possible when the word iterator does not properly handle the text's language - long range = getCharRange(minOffset); + long range = getCharClusterRange(minOffset); selectionStart = TextUtils.unpackRangeStartFromLong(range); selectionEnd = TextUtils.unpackRangeEndFromLong(range); } @@ -831,29 +888,25 @@ public class Editor { return mWordIteratorWithText; } - private long getCharRange(int offset) { + private int getNextCursorOffset(int offset, boolean findAfterGivenOffset) { + final Layout layout = mTextView.getLayout(); + if (layout == null) return offset; + final CharSequence text = mTextView.getText(); + final int nextOffset = layout.getPaint().getTextRunCursor(text, 0, text.length(), + layout.isRtlCharAt(offset) ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR, + offset, findAfterGivenOffset ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE); + return nextOffset == -1 ? offset : nextOffset; + } + + private long getCharClusterRange(int offset) { final int textLength = mTextView.getText().length(); - if (offset + 1 < textLength) { - final char currentChar = mTextView.getText().charAt(offset); - final char nextChar = mTextView.getText().charAt(offset + 1); - if (Character.isSurrogatePair(currentChar, nextChar)) { - return TextUtils.packRangeInLong(offset, offset + 2); - } - } if (offset < textLength) { - return TextUtils.packRangeInLong(offset, offset + 1); - } - if (offset - 2 >= 0) { - final char previousChar = mTextView.getText().charAt(offset - 1); - final char previousPreviousChar = mTextView.getText().charAt(offset - 2); - if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { - return TextUtils.packRangeInLong(offset - 2, offset); - } + return TextUtils.packRangeInLong(offset, getNextCursorOffset(offset, true)); } if (offset - 1 >= 0) { - return TextUtils.packRangeInLong(offset - 1, offset); + return TextUtils.packRangeInLong(getNextCursorOffset(offset, false), offset); } - return TextUtils.packRangeInLong(offset, offset); + return TextUtils.packRangeInLong(offset, offset); } private boolean touchPositionIsInSelection() { @@ -3903,13 +3956,9 @@ public class Editor { public void updatePosition(float x, float y) { final int trueOffset = mTextView.getOffsetForPosition(x, y); final int currLine = mTextView.getLineAtCoordinate(y); - - // Don't select white space on different lines. - if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return; - boolean positionCursor = false; int offset = trueOffset; - int end = getWordEnd(offset, true); + int end = getWordEnd(offset); int start = getWordStart(offset); if (offset < mPreviousOffset) { @@ -3925,7 +3974,7 @@ public class Editor { } } mTouchWordOffset = Math.max(trueOffset - offset, 0); - mInWord = !isStartBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } else if (offset - mTouchWordOffset > mPreviousOffset) { // User is shrinking the selection. @@ -3934,7 +3983,7 @@ public class Editor { offset = end; } offset -= mTouchWordOffset; - mInWord = !isEndBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } @@ -3946,7 +3995,7 @@ public class Editor { int alteredOffset = mTextView.getOffsetAtCoordinate(mPrevLine, x); if (alteredOffset >= selectionEnd) { // Can't pass the other drag handle. - offset = Math.max(0, selectionEnd - 1); + offset = getNextCursorOffset(selectionEnd, false); } else { offset = alteredOffset; } @@ -4005,14 +4054,9 @@ public class Editor { public void updatePosition(float x, float y) { final int trueOffset = mTextView.getOffsetForPosition(x, y); final int currLine = mTextView.getLineAtCoordinate(y); - - // Don't select white space on different lines. - if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return; - int offset = trueOffset; boolean positionCursor = false; - - int end = getWordEnd(offset, true); + int end = getWordEnd(offset); int start = getWordStart(offset); if (offset > mPreviousOffset) { @@ -4028,7 +4072,7 @@ public class Editor { } } mTouchWordOffset = Math.max(offset - trueOffset, 0); - mInWord = !isEndBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } else if (offset + mTouchWordOffset < mPreviousOffset) { // User is shrinking the selection. @@ -4038,7 +4082,7 @@ public class Editor { } offset += mTouchWordOffset; positionCursor = true; - mInWord = !isStartBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); } if (positionCursor) { @@ -4049,7 +4093,7 @@ public class Editor { int length = mTextView.getText().length(); if (alteredOffset <= selectionStart) { // Can't pass the other drag handle. - offset = Math.min(selectionStart + 1, length); + offset = getNextCursorOffset(selectionStart, true); } else { offset = Math.min(alteredOffset, length); } @@ -4070,36 +4114,6 @@ public class Editor { } /** - * Checks whether selection is happening on a different line than previous and - * if that line only contains whitespace up to the touch location. - * - * @param prevLine The previous line the selection was on. - * @param currLine The current line being selected. - * @param offset The offset in the text where the touch occurred. - * @return Whether or not it was just a white space line being selected. - */ - private boolean isWhitespaceLine(int prevLine, int currLine, int offset) { - if (prevLine == currLine) { - // Same line; don't care. - return false; - } - CharSequence text = mTextView.getText(); - if (offset == text.length()) { - // No character at the last position. - return false; - } - int lineEndOffset = mTextView.getLayout().getLineEnd(currLine); - for (int cp, i = offset; i < lineEndOffset; i += Character.charCount(cp)) { - cp = Character.codePointAt(text, i); - if (!Character.isSpaceChar(cp) && !Character.isWhitespace(cp)) { - // There are non white space chars on the line. - return false; - } - } - return true; - } - - /** * A CursorController instance can be used to control a cursor in the text. */ private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener { @@ -4178,8 +4192,6 @@ public class Editor { private int mStartOffset = -1; // Indicates whether the user is selecting text and using the drag accelerator. private boolean mDragAcceleratorActive; - // Indicates the line of text the drag accelerator is on. - private int mPrevLine = -1; SelectionModifierCursorController() { resetTouchOffsets(); @@ -4272,8 +4284,6 @@ public class Editor { } } - // New selection, reset line. - mPrevLine = mTextView.getLineAtCoordinate(y); mDownPositionX = x; mDownPositionY = y; mGestureStayedInTapRegion = true; @@ -4318,7 +4328,7 @@ public class Editor { // We don't start "dragging" until the user is past the initial word that // gets selected on long press. int firstWordStart = getWordStart(mStartOffset); - int firstWordEnd = getWordEnd(mStartOffset, false); + int firstWordEnd = getWordEnd(mStartOffset); if (offset > firstWordEnd || offset < firstWordStart) { // Basically the goal in the below code is to have the highlight be @@ -4330,13 +4340,6 @@ public class Editor { if (my > fingerOffset) my -= fingerOffset; offset = mTextView.getOffsetForPosition(mx, my); - int currLine = mTextView.getLineAtCoordinate(my); - - // Don't select white space on different lines. - if (isWhitespaceLine(mPrevLine, currLine, offset)) return; - - mPrevLine = currLine; - // Perform the check for closeness at edge of view, if we're very close // don't adjust the offset to be in front of the finger - otherwise the // user can't select words at the edge. @@ -4365,7 +4368,7 @@ public class Editor { // Need to adjust start offset based on direction of movement. int newStart = mStartOffset < offset ? getWordStart(mStartOffset) - : getWordEnd(mStartOffset, true); + : getWordEnd(mStartOffset); Selection.setSelection((Spannable) mTextView.getText(), newStart, offset); } diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index 0602944..7ca450a 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -33,6 +33,7 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.widget.RemoteViews.RemoteView; import com.android.internal.R; @@ -407,6 +408,18 @@ public class FrameLayout extends ViewGroup { return FrameLayout.class.getName(); } + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("measurement:measureAllChildren", mMeasureAllChildren); + encoder.addProperty("padding:foregroundPaddingLeft", mForegroundPaddingLeft); + encoder.addProperty("padding:foregroundPaddingTop", mForegroundPaddingTop); + encoder.addProperty("padding:foregroundPaddingRight", mForegroundPaddingRight); + encoder.addProperty("padding:foregroundPaddingBottom", mForegroundPaddingBottom); + } + /** * Per-child layout information for layouts that support margins. * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes} diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index c959774..dcaafa5 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -17,6 +17,7 @@ package android.widget; import android.annotation.IntDef; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; @@ -31,6 +32,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -2420,4 +2422,11 @@ public class GridView extends AbsListView { row, 1, column, 1, isHeading, isSelected); info.setCollectionItemInfo(itemInfo); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + encoder.addProperty("numColumns", getNumColumns()); + } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 0879c5d..cf67905 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -35,6 +36,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -1695,6 +1697,13 @@ public class HorizontalScrollView extends FrameLayout { return ss; } + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + encoder.addProperty("layout:fillViewPort", mFillViewport); + } + static class SavedState extends BaseSavedState { public int scrollPosition; public boolean isLayoutRtl; diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 6d2f368..05059bc 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -17,6 +17,7 @@ package android.widget; import android.annotation.DrawableRes; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; @@ -43,6 +44,7 @@ import android.util.Log; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewDebug; +import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import android.widget.RemoteViews.RemoteView; @@ -1431,4 +1433,11 @@ public class ImageView extends View { public CharSequence getAccessibilityClassName() { return ImageView.class.getName(); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { + super.encodeProperties(stream); + stream.addProperty("layout:baseline", getBaseline()); + } } diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 72f51c9..f153ce5 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -19,6 +19,7 @@ package android.widget; import com.android.internal.R; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; @@ -29,6 +30,7 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.widget.RemoteViews.RemoteView; import java.lang.annotation.Retention; @@ -1813,6 +1815,20 @@ public class LinearLayout extends ViewGroup { return LinearLayout.class.getName(); } + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + encoder.addProperty("layout:baselineAligned", mBaselineAligned); + encoder.addProperty("layout:baselineAlignedChildIndex", mBaselineAlignedChildIndex); + encoder.addProperty("measurement:baselineChildTop", mBaselineChildTop); + encoder.addProperty("measurement:orientation", mOrientation); + encoder.addProperty("measurement:gravity", mGravity); + encoder.addProperty("measurement:totalLength", mTotalLength); + encoder.addProperty("layout:totalLength", mTotalLength); + encoder.addProperty("layout:useLargestChild", mUseLargestChild); + } + /** * Per-child layout information associated with ViewLinearLayout. * @@ -1921,5 +1937,14 @@ public class LinearLayout extends ViewGroup { return output + "LinearLayout.LayoutParams={width=" + sizeToString(width) + ", height=" + sizeToString(height) + " weight=" + weight + "}"; } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("layout:weight", weight); + encoder.addProperty("layout:gravity", gravity); + } } } diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 05866f0..94b9416 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -1791,8 +1791,9 @@ public class ListPopupWindow { private class ResizePopupRunnable implements Runnable { public void run() { - if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() && - mDropDownList.getChildCount() <= mListItemExpandMaximum) { + if (mDropDownList != null && mDropDownList.isAttachedToWindow() + && mDropDownList.getCount() > mDropDownList.getChildCount() + && mDropDownList.getChildCount() <= mListItemExpandMaximum) { mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); show(); } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index a79c8e8..7dcaa1f 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -23,6 +23,7 @@ import com.android.internal.util.Predicate; import com.google.android.collect.Lists; import android.annotation.IdRes; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; @@ -40,6 +41,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.ViewParent; import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityNodeInfo; @@ -3938,4 +3940,12 @@ public class ListView extends AbsListView { position, 1, 0, 1, isHeading, isSelected); info.setCollectionItemInfo(itemInfo); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("recycleOnMeasure", recycleOnMeasure()); + } } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index 8d8b3a3..97348e30 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -28,7 +28,7 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.View; import android.view.ViewGroup; import android.view.Window; diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index b59ae17..639a09c 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.PorterDuff; @@ -49,6 +50,7 @@ import android.view.Gravity; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewDebug; +import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.AlphaAnimation; @@ -1893,6 +1895,17 @@ public class ProgressBar extends View { postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); } + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { + super.encodeProperties(stream); + + stream.addProperty("progress:max", getMax()); + stream.addProperty("progress:progress", getProgress()); + stream.addProperty("progress:secondaryProgress", getSecondaryProgress()); + stream.addProperty("progress:indeterminate", isIndeterminate()); + } + /** * Command for sending an accessibility event. */ diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index d12739f..affc5da 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.util.ArrayMap; import com.android.internal.R; @@ -36,6 +37,7 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import android.widget.RemoteViews.RemoteView; @@ -1616,6 +1618,13 @@ public class RelativeLayout extends ViewGroup { // This will set the layout direction super.resolveLayoutDirection(layoutDirection); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + encoder.addProperty("layout:alignWithParent", alignWithParent); + } } private static class DependencyGraph { diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 98d61d3..2709f25 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -38,6 +39,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -1787,6 +1789,13 @@ public class ScrollView extends FrameLayout { return ss; } + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + encoder.addProperty("fillViewport", mFillViewport); + } + static class SavedState extends BaseSavedState { public int scrollPosition; diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index f73ee49..d4288d6 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; @@ -24,7 +25,7 @@ import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; - +import android.view.ViewHierarchyEncoder; /** * <p>A layout that arranges its children horizontally. A TableRow should @@ -509,6 +510,14 @@ public class TableRow extends LinearLayout { height = WRAP_CONTENT; } } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + encoder.addProperty("layout:column", column); + encoder.addProperty("layout:span", span); + } } // special transparent hierarchy change listener diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index e2acaac..5d7b569 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -16,6 +16,7 @@ package android.widget; +import android.annotation.NonNull; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -32,6 +33,7 @@ import android.provider.Settings; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.RemotableViewMethod; +import android.view.ViewHierarchyEncoder; import com.android.internal.R; @@ -546,4 +548,18 @@ public class TextClock extends TextView { mTime.setTimeInMillis(System.currentTimeMillis()); setText(DateFormat.format(mFormat, mTime)); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { + super.encodeProperties(stream); + + CharSequence s = getFormat12Hour(); + stream.addProperty("format12Hour", s == null ? null : s.toString()); + + s = getFormat24Hour(); + stream.addProperty("format24Hour", s == null ? null : s.toString()); + stream.addProperty("format", mFormat == null ? null : mFormat.toString()); + stream.addProperty("hasSeconds", mHasSeconds); + } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d6fdee0..68c49cd 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -120,6 +120,7 @@ import android.view.ViewDebug; import android.view.ViewGroup.LayoutParams; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; +import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -2844,7 +2845,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") }) public int getTypefaceStyle() { - return mTextPaint.getTypeface().getStyle(); + Typeface typeface = mTextPaint.getTypeface(); + return typeface != null ? typeface.getStyle() : Typeface.NORMAL; } /** @@ -9556,6 +9558,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { + super.encodeProperties(stream); + + TruncateAt ellipsize = getEllipsize(); + stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); + stream.addProperty("text:textSize", getTextSize()); + stream.addProperty("text:scaledTextSize", getScaledTextSize()); + stream.addProperty("text:typefaceStyle", getTypefaceStyle()); + stream.addProperty("text:selectionStart", getSelectionStart()); + stream.addProperty("text:selectionEnd", getSelectionEnd()); + stream.addProperty("text:curTextColor", mCurTextColor); + stream.addProperty("text:text", mText == null ? null : mText.toString()); + stream.addProperty("text:gravity", mGravity); + } + /** * User interface state that is stored by TextView for implementing * {@link View#onSaveInstanceState}. diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index 6173832..9277f9b 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -18,6 +18,7 @@ package com.android.internal.logging; import android.content.Context; import android.os.Build; +import android.view.View; /** * Log all the things. @@ -33,6 +34,10 @@ public class MetricsLogger implements MetricsConstants { public static final int ACTION_BAN_APP_NOTES = 146; public static final int NOTIFICATION_ZEN_MODE_EVENT_RULE = 147; public static final int ACTION_DISMISS_ALL_NOTES = 148; + public static final int QS_DND_DETAILS = 149; + public static final int QS_BLUETOOTH_DETAILS = 150; + public static final int QS_CAST_DETAILS = 151; + public static final int QS_WIFI_DETAILS = 152; public static void visible(Context context, int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { @@ -41,13 +46,27 @@ public class MetricsLogger implements MetricsConstants { EventLogTags.writeSysuiViewVisibility(category, 100); } - public static void hidden(Context context, int category) { + public static void hidden(Context context, int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { throw new IllegalArgumentException("Must define metric category"); } EventLogTags.writeSysuiViewVisibility(category, 0); } + public static void visibility(Context context, int category, boolean visibile) + throws IllegalArgumentException { + if (visibile) { + visible(context, category); + } else { + hidden(context, category); + } + } + + public static void visibility(Context context, int category, int vis) + throws IllegalArgumentException { + visibility(context, category, vis == View.VISIBLE); + } + public static void action(Context context, int category) { action(context, category, ""); } diff --git a/core/java/android/view/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java index 350650d..2cb9c25 100644 --- a/core/java/android/view/PhoneFallbackEventHandler.java +++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import android.app.KeyguardManager; import android.app.SearchManager; @@ -28,10 +28,11 @@ import android.os.UserHandle; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.Log; -import android.view.View; -import android.view.HapticFeedbackConstants; import android.view.FallbackEventHandler; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; +import android.view.View; +import com.android.internal.policy.PhoneWindow; /** * @hide diff --git a/core/java/android/view/PhoneLayoutInflater.java b/core/java/com/android/internal/policy/PhoneLayoutInflater.java index 7d89a0b..991b6bb 100644 --- a/core/java/android/view/PhoneLayoutInflater.java +++ b/core/java/com/android/internal/policy/PhoneLayoutInflater.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import android.content.Context; import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; /** * @hide diff --git a/core/java/android/view/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index a3e7a10..a578a6e 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; @@ -27,6 +27,34 @@ import android.app.ActivityManagerNative; import android.app.SearchManager; import android.os.UserHandle; +import android.view.ActionMode; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.Gravity; +import android.view.IRotationWatcher.Stub; +import android.view.IWindowManager; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputQueue; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.SurfaceHolder.Callback2; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.ViewParent; +import android.view.ViewRootImpl; +import android.view.ViewStub; +import android.view.ViewTreeObserver.OnPreDrawListener; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; import com.android.internal.R; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.FloatingActionMode; @@ -67,7 +95,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionInflater; @@ -140,7 +167,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ViewGroup mContentRoot; - SurfaceHolder.Callback2 mTakeSurfaceCallback; + Callback2 mTakeSurfaceCallback; InputQueue.Callback mTakeInputQueueCallback; @@ -427,7 +454,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void takeSurface(SurfaceHolder.Callback2 callback) { + public void takeSurface(Callback2 callback) { mTakeSurfaceCallback = callback; } @@ -2181,7 +2208,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ActionBarContextView mPrimaryActionModeView; private PopupWindow mPrimaryActionModePopup; private Runnable mShowPrimaryActionModePopup; - private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; + private OnPreDrawListener mFloatingToolbarPreDrawListener; private View mFloatingActionModeOriginatingView; private FloatingToolbar mFloatingToolbar; @@ -3354,7 +3381,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mContext, callback, originatingView, mFloatingToolbar); mFloatingActionModeOriginatingView = originatingView; mFloatingToolbarPreDrawListener = - new ViewTreeObserver.OnPreDrawListener() { + new OnPreDrawListener() { @Override public boolean onPreDraw() { mode.updateViewLocationInWindow(); @@ -4718,7 +4745,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } - static class RotationWatcher extends IRotationWatcher.Stub { + static class RotationWatcher extends Stub { private Handler mHandler; private final Runnable mRotationChanged = new Runnable() { public void run() { diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java deleted file mode 100644 index 1a6736a..0000000 --- a/core/java/com/android/internal/transition/EpicenterClipReveal.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.transition; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.RectEvaluator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.util.Property; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.view.animation.PathInterpolator; - -import com.android.internal.R; - -/** - * EpicenterClipReveal captures the {@link View#getClipBounds()} before and - * after the scene change and animates between those and the epicenter bounds - * during a visibility transition. - */ -public class EpicenterClipReveal extends Visibility { - private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final boolean mCenterClipBounds; - - public EpicenterClipReveal() { - mInterpolatorX = null; - mInterpolatorY = null; - mCenterClipBounds = false; - } - - public EpicenterClipReveal(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterClipReveal, 0, 0); - - mCenterClipBounds = a.getBoolean(R.styleable.EpicenterClipReveal_centerClipBounds, false); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect clip = view.getClipBounds(); - values.values.put(PROPNAME_CLIP, clip); - - if (clip == null) { - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - } - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = getBestRect(endValues); - final Rect start = getEpicenterOrCenter(end); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = getBestRect(startValues); - final Rect end = getEpicenterOrCenter(start); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - // Translate the clip bounds to be centered within the target bounds. - if (mCenterClipBounds) { - final int offsetX = bestRect.centerX() - epicenter.centerX(); - final int offsetY = bestRect.centerY() - epicenter.centerY(); - epicenter.offset(offsetX, offsetY); - } - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private Rect getBestRect(TransitionValues values) { - final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); - if (clipRect == null) { - return (Rect) values.values.get(PROPNAME_BOUNDS); - } - return clipRect; - } - - private static Animator createRectAnimator(final View view, Rect start, Rect end, - TransitionValues endValues, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY) { - final RectEvaluator evaluator = new RectEvaluator(new Rect()); - final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); - - final ClipDimenProperty propX = new ClipDimenProperty(ClipDimenProperty.TARGET_X); - final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, start, end); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ClipDimenProperty propY = new ClipDimenProperty(ClipDimenProperty.TARGET_Y); - final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, start, end); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY); - animSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setClipBounds(terminalClip); - } - }); - return animSet; - } - - private static class ClipDimenProperty extends Property<View, Rect> { - public static final char TARGET_X = 'x'; - public static final char TARGET_Y = 'y'; - - private final Rect mTempRect = new Rect(); - - private final int mTargetDimension; - - public ClipDimenProperty(char targetDimension) { - super(Rect.class, "clip_bounds_" + targetDimension); - - mTargetDimension = targetDimension; - } - - @Override - public Rect get(View object) { - final Rect tempRect = mTempRect; - if (!object.getClipBounds(tempRect)) { - tempRect.setEmpty(); - } - return tempRect; - } - - @Override - public void set(View object, Rect value) { - final Rect tempRect = mTempRect; - if (object.getClipBounds(tempRect)) { - if (mTargetDimension == TARGET_X) { - tempRect.left = value.left; - tempRect.right = value.right; - } else { - tempRect.top = value.top; - tempRect.bottom = value.bottom; - } - object.setClipBounds(tempRect); - } - } - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslate.java b/core/java/com/android/internal/transition/EpicenterTranslate.java deleted file mode 100644 index eac3ff8..0000000 --- a/core/java/com/android/internal/transition/EpicenterTranslate.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.transition; - -import com.android.internal.R; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; - -/** - * EpicenterTranslate captures the {@link View#getTranslationX()} and - * {@link View#getTranslationY()} before and after the scene change and - * animates between those and the epicenter's center during a visibility - * transition. - */ -public class EpicenterTranslate extends Visibility { - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; - private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; - private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; - private static final String PROPNAME_Z = "android:epicenterReveal:z"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final TimeInterpolator mInterpolatorZ; - - public EpicenterTranslate() { - mInterpolatorX = null; - mInterpolatorY = null; - mInterpolatorZ = null; - } - - public EpicenterTranslate(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterTranslate, 0, 0); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorZ = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorZ, 0); - if (interpolatorZ != 0) { - mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); - } else { - mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); - values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); - values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); - values.values.put(PROPNAME_Z, view.getZ()); - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect start = getEpicenterOrCenter(end); - final float startX = start.centerX() - end.centerX(); - final float startY = start.centerY() - end.centerY(); - final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); - - // Translate the view to be centered on the epicenter. - view.setTranslationX(startX); - view.setTranslationY(startY); - view.setTranslationZ(startZ); - - final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect end = getEpicenterOrCenter(start); - final float endX = end.centerX() - start.centerX(); - final float endY = end.centerY() - start.centerY(); - final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); - - final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private static Animator createAnimator(final View view, float startX, float startY, - float startZ, float endX, float endY, float endZ, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ) { - final ObjectAnimator animX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ObjectAnimator animY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); - if (interpolatorZ != null) { - animZ.setInterpolator(interpolatorZ); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY, animZ); - return animSet; - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java new file mode 100644 index 0000000..2c10297 --- /dev/null +++ b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.TypeEvaluator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.transition.TransitionValues; +import android.transition.Visibility; +import android.util.AttributeSet; +import android.util.Property; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; + +import com.android.internal.R; + +/** + * EpicenterTranslateClipReveal captures the clip bounds and translation values + * before and after the scene change and animates between those and the + * epicenter bounds during a visibility transition. + */ +public class EpicenterTranslateClipReveal extends Visibility { + private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; + private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; + private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; + private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; + private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; + private static final String PROPNAME_Z = "android:epicenterReveal:z"; + + private final TimeInterpolator mInterpolatorX; + private final TimeInterpolator mInterpolatorY; + private final TimeInterpolator mInterpolatorZ; + + public EpicenterTranslateClipReveal() { + mInterpolatorX = null; + mInterpolatorY = null; + mInterpolatorZ = null; + } + + public EpicenterTranslateClipReveal(Context context, AttributeSet attrs) { + super(context, attrs); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.EpicenterTranslateClipReveal, 0, 0); + + final int interpolatorX = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorX, 0); + if (interpolatorX != 0) { + mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); + } else { + mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; + } + + final int interpolatorY = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorY, 0); + if (interpolatorY != 0) { + mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); + } else { + mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; + } + + final int interpolatorZ = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorZ, 0); + if (interpolatorZ != 0) { + mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); + } else { + mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; + } + + a.recycle(); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + super.captureEndValues(transitionValues); + captureValues(transitionValues); + } + + private void captureValues(TransitionValues values) { + final View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + values.values.put(PROPNAME_BOUNDS, bounds); + values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); + values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); + values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); + values.values.put(PROPNAME_Z, view.getZ()); + + final Rect clip = view.getClipBounds(); + values.values.put(PROPNAME_CLIP, clip); + } + + @Override + public Animator onAppear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (endValues == null) { + return null; + } + + final Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect startBounds = getEpicenterOrCenter(endBounds); + final float startX = startBounds.centerX() - endBounds.centerX(); + final float startY = startBounds.centerY() - endBounds.centerY(); + final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); + + // Translate the view to be centered on the epicenter. + view.setTranslationX(startX); + view.setTranslationY(startY); + view.setTranslationZ(startZ); + + final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect endClip = getBestRect(endValues); + final Rect startClip = getEpicenterOrCenter(endClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (startValues == null) { + return null; + } + + final Rect startBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect endBounds = getEpicenterOrCenter(startBounds); + final float endX = endBounds.centerX() - startBounds.centerX(); + final float endY = endBounds.centerY() - startBounds.centerY(); + final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); + + final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect startClip = getBestRect(startValues); + final Rect endClip = getEpicenterOrCenter(startClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + private Rect getEpicenterOrCenter(Rect bestRect) { + final Rect epicenter = getEpicenter(); + if (epicenter != null) { + return epicenter; + } + + final int centerX = bestRect.centerX(); + final int centerY = bestRect.centerY(); + return new Rect(centerX, centerY, centerX, centerY); + } + + private Rect getBestRect(TransitionValues values) { + final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); + if (clipRect == null) { + return (Rect) values.values.get(PROPNAME_BOUNDS); + } + return clipRect; + } + + private static Animator createRectAnimator(final View view, State startX, State startY, + float startZ, State endX, State endY, float endZ, TransitionValues endValues, + TimeInterpolator interpolatorX, TimeInterpolator interpolatorY, + TimeInterpolator interpolatorZ) { + final StateEvaluator evaluator = new StateEvaluator(); + + final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); + if (interpolatorZ != null) { + animZ.setInterpolator(interpolatorZ); + } + + final StateProperty propX = new StateProperty(StateProperty.TARGET_X); + final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, startX, endX); + if (interpolatorX != null) { + animX.setInterpolator(interpolatorX); + } + + final StateProperty propY = new StateProperty(StateProperty.TARGET_Y); + final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, startY, endY); + if (interpolatorY != null) { + animY.setInterpolator(interpolatorY); + } + + final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); + final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setClipBounds(terminalClip); + } + }; + + final AnimatorSet animSet = new AnimatorSet(); + animSet.playTogether(animX, animY, animZ); + animSet.addListener(animatorListener); + return animSet; + } + + private static class State { + int lower; + int upper; + float trans; + + public State() {} + + public State(int lower, int upper, float trans) { + this.lower = lower; + this.upper = upper; + this.trans = trans; + } + } + + private static class StateEvaluator implements TypeEvaluator<State> { + private final State mTemp = new State(); + + @Override + public State evaluate(float fraction, State startValue, State endValue) { + mTemp.upper = startValue.upper + (int) ((endValue.upper - startValue.upper) * fraction); + mTemp.lower = startValue.lower + (int) ((endValue.lower - startValue.lower) * fraction); + mTemp.trans = startValue.trans + (int) ((endValue.trans - startValue.trans) * fraction); + return mTemp; + } + } + + private static class StateProperty extends Property<View, State> { + public static final char TARGET_X = 'x'; + public static final char TARGET_Y = 'y'; + + private final Rect mTempRect = new Rect(); + private final State mTempState = new State(); + + private final int mTargetDimension; + + public StateProperty(char targetDimension) { + super(State.class, "state_" + targetDimension); + + mTargetDimension = targetDimension; + } + + @Override + public State get(View object) { + final Rect tempRect = mTempRect; + if (!object.getClipBounds(tempRect)) { + tempRect.setEmpty(); + } + final State tempState = mTempState; + if (mTargetDimension == TARGET_X) { + tempState.trans = object.getTranslationX(); + tempState.lower = tempRect.left + (int) tempState.trans; + tempState.upper = tempRect.right + (int) tempState.trans; + } else { + tempState.trans = object.getTranslationY(); + tempState.lower = tempRect.top + (int) tempState.trans; + tempState.upper = tempRect.bottom + (int) tempState.trans; + } + return tempState; + } + + @Override + public void set(View object, State value) { + final Rect tempRect = mTempRect; + if (object.getClipBounds(tempRect)) { + if (mTargetDimension == TARGET_X) { + tempRect.left = value.lower - (int) value.trans; + tempRect.right = value.upper - (int) value.trans; + } else { + tempRect.top = value.lower - (int) value.trans; + tempRect.bottom = value.upper - (int) value.trans; + } + object.setClipBounds(tempRect); + } + + if (mTargetDimension == TARGET_X) { + object.setTranslationX(value.trans); + } else { + object.setTranslationY(value.trans); + } + } + } +} diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java index 34f62ba..bd0e6ce 100644 --- a/core/java/com/android/internal/util/AsyncChannel.java +++ b/core/java/com/android/internal/util/AsyncChannel.java @@ -462,10 +462,8 @@ public class AsyncChannel { } catch(Exception e) { } // Tell source we're disconnected. - if (mSrcHandler != null) { - replyDisconnected(STATUS_SUCCESSFUL); - mSrcHandler = null; - } + replyDisconnected(STATUS_SUCCESSFUL); + mSrcHandler = null; // Unlink only when bindService isn't used if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) { mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0); @@ -871,6 +869,8 @@ public class AsyncChannel { * @param status to be stored in msg.arg1 */ private void replyDisconnected(int status) { + // Can't reply if already disconnected. Avoid NullPointerException. + if (mSrcHandler == null) return; Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED); msg.arg1 = status; msg.obj = this; diff --git a/core/java/com/android/internal/util/CallbackRegistry.java b/core/java/com/android/internal/util/CallbackRegistry.java new file mode 100644 index 0000000..0f228d4 --- /dev/null +++ b/core/java/com/android/internal/util/CallbackRegistry.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks callbacks for the event. This class supports reentrant modification + * of the callbacks during notification without adversely disrupting notifications. + * A common pattern for callbacks is to receive a notification and then remove + * themselves. This class handles this behavior with constant memory under + * most circumstances. + * + * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to + * the constructor to define how notifications should be called. That implementation + * does the actual notification on the listener.</p> + * + * <p>This class supports only callbacks with at most two parameters. + * Typically, these are the notification originator and a parameter, but these may + * be used as required. If more than two parameters are required or primitive types + * must be used, <code>A</code> should be some kind of containing structure that + * the subclass may reuse between notifications.</p> + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> Opaque argument used to pass additional data beyond an int. + */ +public class CallbackRegistry<C, T, A> implements Cloneable { + private static final String TAG = "CallbackRegistry"; + + /** An ordered collection of listeners waiting to be notified. */ + private List<C> mCallbacks = new ArrayList<C>(); + + /** + * A bit flag for the first 64 listeners that are removed during notification. + * The lowest significant bit corresponds to the 0th index into mCallbacks. + * For a small number of callbacks, no additional array of objects needs to + * be allocated. + */ + private long mFirst64Removed = 0x0; + + /** + * Bit flags for the remaining callbacks that are removed during notification. + * When there are more than 64 callbacks and one is marked for removal, a dynamic + * array of bits are allocated for the callbacks. + */ + private long[] mRemainderRemoved; + + /** + * The reentrancy level of the notification. When we notify a callback, it may cause + * further notifications. The reentrancy level must be tracked to let us clean up + * the callback state when all notifications have been processed. + */ + private int mNotificationLevel; + + /** The notification mechanism for notifying an event. */ + private final NotifierCallback<C, T, A> mNotifier; + + /** + * Creates an EventRegistry that notifies the event with notifier. + * @param notifier The class to use to notify events. + */ + public CallbackRegistry(NotifierCallback<C, T, A> notifier) { + mNotifier = notifier; + } + + /** + * Notify all callbacks. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + public synchronized void notifyCallbacks(T sender, int arg, A arg2) { + mNotificationLevel++; + notifyRecurseLocked(sender, arg, arg2); + mNotificationLevel--; + if (mNotificationLevel == 0) { + if (mRemainderRemoved != null) { + for (int i = mRemainderRemoved.length - 1; i >= 0; i--) { + final long removedBits = mRemainderRemoved[i]; + if (removedBits != 0) { + removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits); + mRemainderRemoved[i] = 0; + } + } + } + if (mFirst64Removed != 0) { + removeRemovedCallbacks(0, mFirst64Removed); + mFirst64Removed = 0; + } + } + } + + /** + * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyFirst64Locked(T sender, int arg, A arg2) { + final int maxNotified = Math.min(Long.SIZE, mCallbacks.size()); + notifyCallbacksLocked(sender, arg, arg2, 0, maxNotified, mFirst64Removed); + } + + /** + * Notify all callbacks using a recursive algorithm to avoid allocating on the heap. + * This part captures the callbacks beyond Long.SIZE that have no bits allocated for + * removal before it recurses into {@link #notifyRemainderLocked(Object, int, A, int)}. + * <p> + * Recursion is used to avoid allocating temporary state on the heap. Each stack has one + * long (64 callbacks) worth of information of which has been removed. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyRecurseLocked(T sender, int arg, A arg2) { + final int callbackCount = mCallbacks.size(); + final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1; + + // Now we've got all callbacks that have no mRemainderRemoved value, so notify the + // others. + notifyRemainderLocked(sender, arg, arg2, remainderIndex); + + // notifyRemainderLocked notifies all at maxIndex, so we'd normally start at maxIndex + 1 + // However, we must also keep track of those in mFirst64Removed, so we add 2 instead: + final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE; + + // The remaining have no bit set + notifyCallbacksLocked(sender, arg, arg2, startCallbackIndex, callbackCount, 0); + } + + /** + * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If + * remainderIndex is -1, the first 64 will be notified instead. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param remainderIndex The index into mRemainderRemoved that should be notified. + */ + private void notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex) { + if (remainderIndex < 0) { + notifyFirst64Locked(sender, arg, arg2); + } else { + final long bits = mRemainderRemoved[remainderIndex]; + final int startIndex = (remainderIndex + 1) * Long.SIZE; + final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE); + notifyRemainderLocked(sender, arg, arg2, remainderIndex - 1); + notifyCallbacksLocked(sender, arg, arg2, startIndex, endIndex, bits); + } + } + + /** + * Notify callbacks from startIndex to endIndex, using bits as the bit status + * for whether they have been removed or not. bits should be from mRemainderRemoved or + * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to + * endIndex should be notified. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param startIndex The index into the mCallbacks to start notifying. + * @param endIndex One past the last index into mCallbacks to notify. + * @param bits A bit field indicating which callbacks have been removed and shouldn't + * be notified. + */ + private void notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex, + final int endIndex, final long bits) { + long bitMask = 1; + for (int i = startIndex; i < endIndex; i++) { + if ((bits & bitMask) == 0) { + mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); + } + bitMask <<= 1; + } + } + + /** + * Add a callback to be notified. If the callback is already in the list, another won't + * be added. This does not affect current notifications. + * @param callback The callback to add. + */ + public synchronized void add(C callback) { + int index = mCallbacks.lastIndexOf(callback); + if (index < 0 || isRemovedLocked(index)) { + mCallbacks.add(callback); + } + } + + /** + * Returns true if the callback at index has been marked for removal. + * + * @param index The index into mCallbacks to check. + * @return true if the callback at index has been marked for removal. + */ + private boolean isRemovedLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + return (mFirst64Removed & bitMask) != 0; + } else if (mRemainderRemoved == null) { + // It is after the first 64 callbacks, but nothing else was marked for removal. + return false; + } else { + final int maskIndex = (index / Long.SIZE) - 1; + if (maskIndex >= mRemainderRemoved.length) { + // There are some items in mRemainderRemoved, but nothing at the given index. + return false; + } else { + // There is something marked for removal, so we have to check the bit. + final long bits = mRemainderRemoved[maskIndex]; + final long bitMask = 1L << (index % Long.SIZE); + return (bits & bitMask) != 0; + } + } + } + + /** + * Removes callbacks from startIndex to startIndex + Long.SIZE, based + * on the bits set in removed. + * @param startIndex The index into the mCallbacks to start removing callbacks. + * @param removed The bits indicating removal, where each bit is set for one callback + * to be removed. + */ + private void removeRemovedCallbacks(int startIndex, long removed) { + // The naive approach should be fine. There may be a better bit-twiddling approach. + final int endIndex = startIndex + Long.SIZE; + + long bitMask = 1L << (Long.SIZE - 1); + for (int i = endIndex - 1; i >= startIndex; i--) { + if ((removed & bitMask) != 0) { + mCallbacks.remove(i); + } + bitMask >>>= 1; + } + } + + /** + * Remove a callback. This callback won't be notified after this call completes. + * @param callback The callback to remove. + */ + public synchronized void remove(C callback) { + if (mNotificationLevel == 0) { + mCallbacks.remove(callback); + } else { + int index = mCallbacks.lastIndexOf(callback); + if (index >= 0) { + setRemovalBitLocked(index); + } + } + } + + private void setRemovalBitLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + mFirst64Removed |= bitMask; + } else { + final int remainderIndex = (index / Long.SIZE) - 1; + if (mRemainderRemoved == null) { + mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE]; + } else if (mRemainderRemoved.length < remainderIndex) { + // need to make it bigger + long[] newRemainders = new long[mCallbacks.size() / Long.SIZE]; + System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length); + mRemainderRemoved = newRemainders; + } + final long bitMask = 1L << (index % Long.SIZE); + mRemainderRemoved[remainderIndex] |= bitMask; + } + } + + /** + * Makes a copy of the registered callbacks and returns it. + * + * @return a copy of the registered callbacks. + */ + public synchronized ArrayList<C> copyListeners() { + ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size()); + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + callbacks.add(mCallbacks.get(i)); + } + } + return callbacks; + } + + /** + * Returns true if there are no registered callbacks or false otherwise. + * + * @return true if there are no registered callbacks or false otherwise. + */ + public synchronized boolean isEmpty() { + if (mCallbacks.isEmpty()) { + return true; + } else if (mNotificationLevel == 0) { + return false; + } else { + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + return false; + } + } + return true; + } + } + + /** + * Removes all callbacks from the list. + */ + public synchronized void clear() { + if (mNotificationLevel == 0) { + mCallbacks.clear(); + } else if (!mCallbacks.isEmpty()) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + setRemovalBitLocked(i); + } + } + } + + public synchronized CallbackRegistry<C, T, A> clone() { + CallbackRegistry<C, T, A> clone = null; + try { + clone = (CallbackRegistry<C, T, A>) super.clone(); + clone.mFirst64Removed = 0; + clone.mRemainderRemoved = null; + clone.mNotificationLevel = 0; + clone.mCallbacks = new ArrayList<C>(); + final int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + clone.mCallbacks.add(mCallbacks.get(i)); + } + } + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return clone; + } + + /** + * Class used to notify events from CallbackRegistry. + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> An opaque argument to pass to the notifier + */ + public abstract static class NotifierCallback<C, T, A> { + /** + * Used to notify the callback. + * + * @param callback The callback to notify. + * @param sender The opaque sender object. + * @param arg The opaque notification parameter. + * @param arg2 An opaque argument passed in + * {@link CallbackRegistry#notifyCallbacks} + * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback) + */ + public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2); + } +} diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 8ae2e3b..2785c48 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -188,18 +188,43 @@ size_t Bitmap::rowBytes() const { return mPixelRef->rowBytes(); } -SkPixelRef* Bitmap::pixelRef() const { +SkPixelRef* Bitmap::peekAtPixelRef() const { assertValid(); return mPixelRef.get(); } +SkPixelRef* Bitmap::refPixelRef() { + assertValid(); + android::AutoMutex _lock(mLock); + return refPixelRefLocked(); +} + +SkPixelRef* Bitmap::refPixelRefLocked() { + mPixelRef->ref(); + if (mPixelRef->unique()) { + // We just restored this from 0, pin the pixels and inc the strong count + // Note that there *might be* an incoming onStrongRefDestroyed from whatever + // last unref'd + pinPixelsLocked(); + mPinnedRefCount++; + } + return mPixelRef.get(); +} + void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) { + { + android::AutoMutex _lock(mLock); + if (mPinnedRefCount) { + ALOGW("Called reconfigure on a bitmap that is in use! This may" + " cause graphical corruption!"); + } + } mPixelRef->reconfigure(info, rowBytes, ctable); } void Bitmap::reconfigure(const SkImageInfo& info) { - mPixelRef->reconfigure(info, mPixelRef->rowBytes(), mPixelRef->colorTable()); + reconfigure(info, info.minRowBytes(), nullptr); } void Bitmap::detachFromJava() { @@ -287,18 +312,10 @@ void Bitmap::unpinPixelsLocked() { void Bitmap::getSkBitmap(SkBitmap* outBitmap) { assertValid(); android::AutoMutex _lock(mLock); - mPixelRef->ref(); - if (mPixelRef->unique()) { - // We just restored this from 0, pin the pixels and inc the strong count - // Note that there *might be* an incoming onStrongRefDestroyed from whatever - // last unref'd - pinPixelsLocked(); - mPinnedRefCount++; - } // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes() // would require locking the pixels first. outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes()); - outBitmap->setPixelRef(mPixelRef.get())->unref(); + outBitmap->setPixelRef(refPixelRefLocked())->unref(); outBitmap->setHasHardwareMipMap(hasHardwareMipMap()); } @@ -323,7 +340,7 @@ public: } void* pixels() { - return mBitmap->pixelRef()->pixels(); + return mBitmap->peekAtPixelRef()->pixels(); } bool valid() { @@ -780,7 +797,7 @@ static jint Bitmap_config(JNIEnv* env, jobject, jlong bitmapHandle) { static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); - return static_cast<jint>(bitmap->pixelRef()->getGenerationID()); + return static_cast<jint>(bitmap->peekAtPixelRef()->getGenerationID()); } static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) { @@ -800,10 +817,10 @@ static void Bitmap_setHasAlpha(JNIEnv* env, jobject, jlong bitmapHandle, jboolean hasAlpha, jboolean requestPremul) { LocalScopedBitmap bitmap(bitmapHandle); if (hasAlpha) { - bitmap->pixelRef()->changeAlphaType( + bitmap->peekAtPixelRef()->changeAlphaType( requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); } else { - bitmap->pixelRef()->changeAlphaType(kOpaque_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kOpaque_SkAlphaType); } } @@ -812,9 +829,9 @@ static void Bitmap_setPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle, LocalScopedBitmap bitmap(bitmapHandle); if (!bitmap->info().isOpaque()) { if (isPremul) { - bitmap->pixelRef()->changeAlphaType(kPremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kPremul_SkAlphaType); } else { - bitmap->pixelRef()->changeAlphaType(kUnpremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kUnpremul_SkAlphaType); } } } @@ -1164,7 +1181,7 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) { LocalScopedBitmap bitmap(bitmapHandle); - SkPixelRef* pixelRef = bitmap.valid() ? bitmap->pixelRef() : nullptr; + SkPixelRef* pixelRef = bitmap.valid() ? bitmap->peekAtPixelRef() : nullptr; SkSafeRef(pixelRef); return reinterpret_cast<jlong>(pixelRef); } diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h index d6e5c61..efeb898 100644 --- a/core/jni/android/graphics/Bitmap.h +++ b/core/jni/android/graphics/Bitmap.h @@ -62,7 +62,8 @@ public: int width() const { return info().width(); } int height() const { return info().height(); } size_t rowBytes() const; - SkPixelRef* pixelRef() const; + SkPixelRef* peekAtPixelRef() const; + SkPixelRef* refPixelRef(); bool valid() const { return mPixelStorageType != PixelStorageType::Invalid; } void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable); @@ -88,6 +89,7 @@ private: JNIEnv* jniEnv(); bool shouldDisposeSelfLocked(); void assertValid() const; + SkPixelRef* refPixelRefLocked(); android::Mutex mLock; int mPinnedRefCount = 0; diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index cdd397d..3ca4e72 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -184,7 +184,7 @@ public: } mBitmap->reconfigure(info, bitmap->rowBytes(), ctable); - bitmap->setPixelRef(mBitmap->pixelRef()); + bitmap->setPixelRef(mBitmap->refPixelRef())->unref(); // since we're already allocated, we lockPixels right away // HeapAllocator/JavaPixelAllocator behaves this way too @@ -258,7 +258,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding unsigned int existingBufferSize = 0; if (javaBitmap != NULL) { reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap); - if (reuseBitmap->pixelRef()->isImmutable()) { + if (reuseBitmap->peekAtPixelRef()->isImmutable()) { ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = NULL; reuseBitmap = nullptr; diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 0deb8cc..1c6f7de 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -352,8 +352,8 @@ void GraphicsJNI::getSkBitmap(JNIEnv* env, jobject bitmap, SkBitmap* outBitmap) getBitmap(env, bitmap)->getSkBitmap(outBitmap); } -SkPixelRef* GraphicsJNI::getSkPixelRef(JNIEnv* env, jobject bitmap) { - return getBitmap(env, bitmap)->pixelRef(); +SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject bitmap) { + return getBitmap(env, bitmap)->refPixelRef(); } SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) { diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index e748bac..ef9c2a9 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -52,7 +52,7 @@ public: static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas); static android::Bitmap* getBitmap(JNIEnv*, jobject bitmap); static void getSkBitmap(JNIEnv*, jobject bitmap, SkBitmap* outBitmap); - static SkPixelRef* getSkPixelRef(JNIEnv*, jobject bitmap); + static SkPixelRef* refSkPixelRef(JNIEnv*, jobject bitmap); static SkRegion* getNativeRegion(JNIEnv*, jobject region); // Given the 'native' long held by the Rasterizer.java object, return a diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index db495dd..74a9e4e 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -976,6 +976,12 @@ static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz, dest->setTo(*src); } +static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle) +{ + ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); + theme->clear(); +} + static jint android_content_AssetManager_loadThemeAttributeValue( JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve) { @@ -2108,6 +2114,8 @@ static JNINativeMethod gAssetManagerMethods[] = { (void*) android_content_AssetManager_applyThemeStyle }, { "copyTheme", "(JJ)V", (void*) android_content_AssetManager_copyTheme }, + { "clearTheme", "(J)V", + (void*) android_content_AssetManager_clearTheme }, { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I", (void*) android_content_AssetManager_loadThemeAttributeValue }, { "getThemeChangingConfigurations", "(J)I", diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 1965cd3..77af341 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -180,7 +180,7 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, (void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot, screenshotInfo, rowBytes, nullptr); screenshot.detach(); - bitmap->pixelRef()->setImmutable(); + bitmap->peekAtPixelRef()->setImmutable(); return GraphicsJNI::createBitmap(env, bitmap, GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e3930cd..5669b91 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2352,11 +2352,6 @@ <permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE" android:protectionLevel="signature" /> - <!-- Must be required by a {@link android.media.routing.MediaRouteService}, - to ensure that only the system can bind to it. --> - <permission android:name="android.permission.BIND_MEDIA_ROUTE_SERVICE" - android:protectionLevel="signature" /> - <!-- Must be required by an {@link android.service.dreams.DreamService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_DREAM_SERVICE" diff --git a/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 0179433..0000000 --- a/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index e5760be..0000000 --- a/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 3939214..0000000 --- a/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 432c385..0000000 --- a/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable/text_cursor_material.xml b/core/res/res/drawable/text_cursor_material.xml index a350c47..0bedaa9 100644 --- a/core/res/res/drawable/text_cursor_material.xml +++ b/core/res/res/drawable/text_cursor_material.xml @@ -14,6 +14,15 @@ limitations under the License. --> -<nine-patch xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/text_cursor_mtrl_alpha" - android:tint="?attr/colorControlActivated" /> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:inset="2dp"> + <shape + android:tint="?attr/colorControlActivated" + android:shape="rectangle"> + <size + android:height="2dp" + android:width="2dp" /> + <solid + android:color="@color/white" /> + </shape> +</inset> diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml index 89c3749..4b544d2 100644 --- a/core/res/res/layout-land/time_picker_material.xml +++ b/core/res/res/layout-land/time_picker_material.xml @@ -16,7 +16,6 @@ --> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -27,8 +26,7 @@ android:layout_column="0" android:layout_row="0" android:layout_rowSpan="3" - android:layout_gravity="center|fill" - tools:background="@color/accent_material_light" /> + android:layout_gravity="center|fill" /> <RelativeLayout android:layout_width="wrap_content" @@ -56,20 +54,14 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the right of the separator, regardless of the current locale's layout direction. --> @@ -80,10 +72,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> </LinearLayout> <!-- The layout alignment of this view will switch between toRightOf @@ -106,10 +95,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> <CheckedTextView android:id="@+id/pm_label" @@ -121,10 +107,7 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml index 2150341..a4388f6 100644 --- a/core/res/res/layout/date_picker_header_material.xml +++ b/core/res/res/layout/date_picker_header_material.xml @@ -16,17 +16,13 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/date_picker_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="18dp" android:paddingStart="?attr/dialogPreferredPadding" android:paddingEnd="?attr/dialogPreferredPadding" - android:orientation="vertical" - tools:background="@color/accent_material_light" - tools:paddingStart="24dp" - tools:paddingEnd="24dp"> + android:orientation="vertical"> <!-- Top padding should stay on this view so that the touch target is a bit larger. --> @@ -35,10 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="16dp" - android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" - tools:text="2015" - tools:textSize="@dimen/date_picker_year_label_size" - tools:textColor="@color/white" /> + android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" /> <TextView android:id="@+id/date_picker_header_date" @@ -47,9 +40,6 @@ android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel" android:gravity="start" android:maxLines="2" - android:ellipsize="none" - tools:text="Thu, Sep 30" - tools:textSize="@dimen/date_picker_date_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml index be9e443..3f5e300 100644 --- a/core/res/res/layout/time_picker_header_material.xml +++ b/core/res/res/layout/time_picker_header_material.xml @@ -18,13 +18,11 @@ <!-- This layout is duplicated in land/time_picker_material.xml, so any changes made here need to be manually copied over. --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/time_header" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" - android:padding="@dimen/timepicker_separator_padding" - tools:background="@color/accent_material_light"> + android:padding="@dimen/timepicker_separator_padding"> <!-- The hour should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -37,10 +35,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" @@ -50,10 +45,7 @@ android:layout_marginRight="@dimen/timepicker_separator_padding" android:layout_centerInParent="true" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -66,10 +58,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> <!-- The layout alignment of this view will switch between toRightOf @id/minutes and toLeftOf @id/hours depending on the locale. --> @@ -90,10 +79,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> <CheckedTextView android:id="@+id/pm_label" android:layout_width="wrap_content" @@ -103,9 +89,6 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/transition/popup_window_enter.xml b/core/res/res/transition/popup_window_enter.xml index 38c41f0..c4c8dac 100644 --- a/core/res/res/transition/popup_window_enter.xml +++ b/core/res/res/transition/popup_window_enter.xml @@ -16,17 +16,14 @@ <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together"> - <!-- Start from location of epicenter, move to popup location. --> - <transition - class="com.android.internal.transition.EpicenterTranslate" - android:duration="300" /> - <!-- Start from size of epicenter, expand to full width/height. --> <transition - class="com.android.internal.transition.EpicenterClipReveal" - android:centerClipBounds="true" - android:duration="300" /> + class="com.android.internal.transition.EpicenterTranslateClipReveal" + android:duration="250" /> <!-- Quickly fade in. --> - <fade android:duration="100" /> + <fade + android:duration="100" + android:fromAlpha="0.1" + android:toAlpha="1.0" /> </transitionSet> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index bb3ec65..0b96d22 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5872,16 +5872,9 @@ </declare-styleable> <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterClipReveal"> - <attr name="centerClipBounds" format="boolean" /> + <declare-styleable name="EpicenterTranslateClipReveal"> <attr name="interpolatorX" format="reference" /> <attr name="interpolatorY" format="reference" /> - </declare-styleable> - - <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterTranslate"> - <attr name="interpolatorX" /> - <attr name="interpolatorY" /> <attr name="interpolatorZ" format="reference" /> </declare-styleable> diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java deleted file mode 100644 index f479e2b..0000000 --- a/core/tests/coretests/src/android/util/FloatMathTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.util; - -import junit.framework.TestCase; -import android.test.suitebuilder.annotation.SmallTest; - -public class FloatMathTest extends TestCase { - - @SmallTest - public void testSqrt() { - assertEquals(7, FloatMath.sqrt(49), 0); - assertEquals(10, FloatMath.sqrt(100), 0); - assertEquals(0, FloatMath.sqrt(0), 0); - assertEquals(1, FloatMath.sqrt(1), 0); - } - - @SmallTest - public void testFloor() { - assertEquals(78, FloatMath.floor(78.89f), 0); - assertEquals(-79, FloatMath.floor(-78.89f), 0); - } - - @SmallTest - public void testCeil() { - assertEquals(79, FloatMath.ceil(78.89f), 0); - assertEquals(-78, FloatMath.ceil(-78.89f), 0); - } - - @SmallTest - public void testSin() { - assertEquals(0.0, FloatMath.sin(0), 0); - assertEquals(0.8414709848078965f, FloatMath.sin(1), 0); - } - - @SmallTest - public void testCos() { - assertEquals(1.0f, FloatMath.cos(0), 0); - assertEquals(0.5403023058681398f, FloatMath.cos(1), 0); - } -} diff --git a/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java new file mode 100644 index 0000000..c53f4cc --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import junit.framework.TestCase; + +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class CallbackRegistryTest extends TestCase { + + final Integer callback1 = 1; + final Integer callback2 = 2; + final Integer callback3 = 3; + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry; + int notify1; + int notify2; + int notify3; + int[] deepNotifyCount = new int[300]; + Integer argValue; + + private void addNotifyCount(Integer callback) { + if (callback == callback1) { + notify1++; + } else if (callback == callback2) { + notify2++; + } else if (callback == callback3) { + notify3++; + } + deepNotifyCount[callback]++; + } + + public void testAddListener() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertNotNull(registry.copyListeners()); + assertEquals(0, registry.copyListeners().size()); + + registry.add(callback); + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + Integer otherListener = 1; + registry.add(otherListener); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + assertEquals(otherListener, callbacks.get(1)); + + registry.remove(callback); + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(1)); + assertEquals(otherListener, callbacks.get(0)); + } + + public void testSimpleNotify() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + assertEquals(arg1, (int) arg); + addNotifyCount(callback); + argValue = arg; + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback2); + Integer arg = 1; + registry.notifyCallbacks(this, arg, arg); + assertEquals(arg, argValue); + assertEquals(1, notify2); + } + + public void testRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback1); + registry.remove(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback3, callbacks.get(0)); + } + + public void testDeepRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(2, notify2); + assertEquals(3, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(0, callbacks.size()); + } + + public void testAddRemovedListener() { + + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback2); + } else if (callback == callback3) { + registry.add(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(3, callbacks.size()); + assertEquals(callback1, callbacks.get(0)); + assertEquals(callback3, callbacks.get(1)); + assertEquals(callback2, callbacks.get(2)); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + } + + public void testVeryDeepRemoveWhileNotifying() { + final Integer[] callbacks = new Integer[deepNotifyCount.length]; + for (int i = 0; i < callbacks.length; i++) { + callbacks[i] = i; + } + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.remove(callbacks[callbacks.length - callback - 1]); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < callbacks.length; i++) { + registry.add(callbacks[i]); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + int expectedCount = Math.min(i + 1, deepNotifyCount.length - i); + assertEquals(expectedCount, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.clear(); + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(0, deepNotifyCount[i]); + } + } + + public void testNestedClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.clear(); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(1, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testIsEmpty() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertTrue(registry.isEmpty()); + registry.add(callback); + assertFalse(registry.isEmpty()); + } + + public void testClone() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + assertTrue(registry.isEmpty()); + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry2 = registry.clone(); + Integer callback = 0; + registry.add(callback); + assertFalse(registry.isEmpty()); + assertTrue(registry2.isEmpty()); + registry2 = registry.clone(); + assertFalse(registry2.isEmpty()); + } +} |