diff options
168 files changed, 4721 insertions, 2302 deletions
diff --git a/api/current.txt b/api/current.txt index 392ab4d..cdc2404 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28506,6 +28506,53 @@ package android.security { public abstract class KeyStoreKeyProperties { } + public static abstract class KeyStoreKeyProperties.Algorithm { + field public static final java.lang.String AES = "AES"; + field public static final java.lang.String EC = "EC"; + field public static final java.lang.String HMAC_SHA1 = "HmacSHA1"; + field public static final java.lang.String HMAC_SHA224 = "HmacSHA224"; + field public static final java.lang.String HMAC_SHA256 = "HmacSHA256"; + field public static final java.lang.String HMAC_SHA384 = "HmacSHA384"; + field public static final java.lang.String HMAC_SHA512 = "HmacSHA512"; + field public static final java.lang.String RSA = "RSA"; + } + + public static abstract class KeyStoreKeyProperties.AlgorithmEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.BlockMode { + field public static final java.lang.String CBC = "CBC"; + field public static final java.lang.String CTR = "CTR"; + field public static final java.lang.String ECB = "ECB"; + field public static final java.lang.String GCM = "GCM"; + } + + public static abstract class KeyStoreKeyProperties.BlockModeEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.Digest { + field public static final java.lang.String MD5 = "MD5"; + field public static final java.lang.String NONE = "NONE"; + field public static final java.lang.String SHA1 = "SHA-1"; + field public static final java.lang.String SHA224 = "SHA-224"; + field public static final java.lang.String SHA256 = "SHA-256"; + field public static final java.lang.String SHA384 = "SHA-384"; + field public static final java.lang.String SHA512 = "SHA-512"; + } + + public static abstract class KeyStoreKeyProperties.DigestEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.EncryptionPadding { + field public static final java.lang.String NONE = "NoPadding"; + field public static final java.lang.String PKCS7 = "PKCS7Padding"; + field public static final java.lang.String RSA_OAEP = "OAEPPadding"; + field public static final java.lang.String RSA_PKCS1 = "PKCS1Padding"; + } + + public static abstract class KeyStoreKeyProperties.EncryptionPaddingEnum implements java.lang.annotation.Annotation { + } + public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 @@ -28525,6 +28572,14 @@ package android.security { public static abstract class KeyStoreKeyProperties.PurposeEnum implements java.lang.annotation.Annotation { } + public static abstract class KeyStoreKeyProperties.SignaturePadding { + field public static final java.lang.String RSA_PKCS1 = "PKCS1"; + field public static final java.lang.String RSA_PSS = "PSS"; + } + + public static abstract class KeyStoreKeyProperties.SignaturePaddingEnum implements java.lang.annotation.Annotation { + } + public class KeyStoreKeySpec implements java.security.spec.KeySpec { method public java.lang.String[] getBlockModes(); method public java.lang.String[] getDigests(); @@ -33951,14 +34006,6 @@ package android.util { } public deprecated class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); } public final class JsonReader implements java.io.Closeable { diff --git a/api/removed.txt b/api/removed.txt index 5e77e15..0046a70 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -108,6 +108,21 @@ package android.text.format { } +package android.util { + + public deprecated class FloatMath { + method public static float ceil(float); + method public static float cos(float); + method public static float exp(float); + method public static float floor(float); + method public static float hypot(float, float); + method public static float pow(float, float); + method public static float sin(float); + method public static float sqrt(float); + } + +} + package android.view { public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { diff --git a/api/system-current.txt b/api/system-current.txt index 029d611..7012773 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -30520,6 +30520,53 @@ package android.security { public abstract class KeyStoreKeyProperties { } + public static abstract class KeyStoreKeyProperties.Algorithm { + field public static final java.lang.String AES = "AES"; + field public static final java.lang.String EC = "EC"; + field public static final java.lang.String HMAC_SHA1 = "HmacSHA1"; + field public static final java.lang.String HMAC_SHA224 = "HmacSHA224"; + field public static final java.lang.String HMAC_SHA256 = "HmacSHA256"; + field public static final java.lang.String HMAC_SHA384 = "HmacSHA384"; + field public static final java.lang.String HMAC_SHA512 = "HmacSHA512"; + field public static final java.lang.String RSA = "RSA"; + } + + public static abstract class KeyStoreKeyProperties.AlgorithmEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.BlockMode { + field public static final java.lang.String CBC = "CBC"; + field public static final java.lang.String CTR = "CTR"; + field public static final java.lang.String ECB = "ECB"; + field public static final java.lang.String GCM = "GCM"; + } + + public static abstract class KeyStoreKeyProperties.BlockModeEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.Digest { + field public static final java.lang.String MD5 = "MD5"; + field public static final java.lang.String NONE = "NONE"; + field public static final java.lang.String SHA1 = "SHA-1"; + field public static final java.lang.String SHA224 = "SHA-224"; + field public static final java.lang.String SHA256 = "SHA-256"; + field public static final java.lang.String SHA384 = "SHA-384"; + field public static final java.lang.String SHA512 = "SHA-512"; + } + + public static abstract class KeyStoreKeyProperties.DigestEnum implements java.lang.annotation.Annotation { + } + + public static abstract class KeyStoreKeyProperties.EncryptionPadding { + field public static final java.lang.String NONE = "NoPadding"; + field public static final java.lang.String PKCS7 = "PKCS7Padding"; + field public static final java.lang.String RSA_OAEP = "OAEPPadding"; + field public static final java.lang.String RSA_PKCS1 = "PKCS1Padding"; + } + + public static abstract class KeyStoreKeyProperties.EncryptionPaddingEnum implements java.lang.annotation.Annotation { + } + public static abstract class KeyStoreKeyProperties.Origin { field public static final int GENERATED = 1; // 0x1 field public static final int IMPORTED = 2; // 0x2 @@ -30539,6 +30586,14 @@ package android.security { public static abstract class KeyStoreKeyProperties.PurposeEnum implements java.lang.annotation.Annotation { } + public static abstract class KeyStoreKeyProperties.SignaturePadding { + field public static final java.lang.String RSA_PKCS1 = "PKCS1"; + field public static final java.lang.String RSA_PSS = "PSS"; + } + + public static abstract class KeyStoreKeyProperties.SignaturePaddingEnum implements java.lang.annotation.Annotation { + } + public class KeyStoreKeySpec implements java.security.spec.KeySpec { method public java.lang.String[] getBlockModes(); method public java.lang.String[] getDigests(); @@ -36162,14 +36217,6 @@ package android.util { } public deprecated class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); } public final class JsonReader implements java.io.Closeable { diff --git a/api/system-removed.txt b/api/system-removed.txt index 5e77e15..0046a70 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -108,6 +108,21 @@ package android.text.format { } +package android.util { + + public deprecated class FloatMath { + method public static float ceil(float); + method public static float cos(float); + method public static float exp(float); + method public static float floor(float); + method public static float hypot(float, float); + method public static float pow(float, float); + method public static float sin(float); + method public static float sqrt(float); + } + +} + package android.view { public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index bb25ec6..21dc1e2 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -630,7 +630,10 @@ bool BootAnimation::movie() } glDisable(GL_SCISSOR_TEST); } - glDrawTexiOES(xc, yc, 0, animation.width, animation.height); + // specify the y center as ceiling((mHeight - animation.height) / 2) + // which is equivalent to mHeight - (yc + animation.height) + glDrawTexiOES(xc, mHeight - (yc + animation.height), + 0, animation.width, animation.height); eglSwapBuffers(mDisplay, mSurface); nsecs_t now = systemTime(); 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/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/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..e65b4ca 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,134 @@ 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[i]; + final boolean forced = mKey.mForce[i]; + 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); + // TODO: revert after getTheme() is fixed + String[] properties = new String[0]; // 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 +2049,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 +2088,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 +2499,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 +2515,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 +2542,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 +2553,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 +2588,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 +2646,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 +2683,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/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/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..9f46f45 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 diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f62e6a2..a3d0b2a 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22353,4 +22353,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 babb4e9..d0738b0 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/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 7ab5aaa..a261aaf 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.ViewAssistStructure; 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/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 449173f..b9a08f5 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/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/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 1c4b5f7..eaa6278 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5877,16 +5877,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()); + } +} diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js index b28f978..8538671 100644 --- a/docs/html/jd_collections.js +++ b/docs/html/jd_collections.js @@ -1221,6 +1221,14 @@ var RESOURCE_COLLECTIONS = { "https://support.google.com/googleplay/answer/2651410" ] }, + "preview/landing/resources": { + "title": "", + "resources": [ + "preview/api-overview.html", + "preview/setup-sdk.html", + "preview/samples.html" + ] + }, "autolanding": { "title": "", "resources": [ diff --git a/docs/html/jd_tag_helpers.js b/docs/html/jd_tag_helpers.js index 7538e4d..f03b1d7 100644 --- a/docs/html/jd_tag_helpers.js +++ b/docs/html/jd_tag_helpers.js @@ -13,6 +13,7 @@ var ALL_RESOURCES = mergeArrays( GOOGLE_RESOURCES, GUIDE_RESOURCES, SAMPLES_RESOURCES, + PREVIEW_RESOURCES, TOOLS_RESOURCES, TRAINING_RESOURCES, YOUTUBE_RESOURCES, @@ -70,6 +71,7 @@ var ALL_RESOURCES_BY_TYPE = { 'google': GOOGLE_RESOURCES, 'guide': GUIDE_RESOURCES, 'samples': SAMPLES_RESOURCES, + 'preview': PREVIEW_RESOURCES, 'tools': TOOLS_RESOURCES, 'training': TRAINING_RESOURCES, 'youtube': YOUTUBE_RESOURCES, @@ -86,6 +88,7 @@ var ALL_RESOURCES_BY_TAG = mergeMaps( {map:GOOGLE_BY_TAG,arr:GOOGLE_RESOURCES}, {map:GUIDE_BY_TAG,arr:GUIDE_RESOURCES}, {map:SAMPLES_BY_TAG,arr:SAMPLES_RESOURCES}, + {map:PREVIEW_BY_TAG,arr:PREVIEW_RESOURCES}, {map:TOOLS_BY_TAG,arr:TOOLS_RESOURCES}, {map:TRAINING_BY_TAG,arr:TRAINING_RESOURCES}, {map:YOUTUBE_BY_TAG,arr:YOUTUBE_RESOURCES}, diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd index f72ffbb..dde3c7b 100644 --- a/docs/html/preview/api-overview.jd +++ b/docs/html/preview/api-overview.jd @@ -1,5 +1,5 @@ page.title=API Overview -excludeFromSuggestions=true +page.keywords=preview,sdk,compatibility sdk.platform.apiLevel=22 @jd:body diff --git a/docs/html/preview/index.jd b/docs/html/preview/index.jd new file mode 100644 index 0000000..a2d0b18 --- /dev/null +++ b/docs/html/preview/index.jd @@ -0,0 +1,60 @@ +page.title=M Developer Preview +page.tags=preview +meta.tags="preview" +fullpage=true +page.viewport_width=970 +section.landing=true +header.hide=1 +footer.hide=1 +@jd:body + +<section class="dac-expand dac-hero dac-light"> + <div class="wrap"> + <div class="cols dac-hero-content"> + <div class="col-1of2 col-push-1of2 dac-hero-figure"> + <img class="dac-hero-image" src="/design/media/hero-material-design.png"> + </div> + <div class="col-1of2 col-pull-1of2"> + <h1 class="dac-hero-title">M Developer Preview</h1> + <p class="dac-hero-description"> + Get ready for the next official release of the platform. Test your apps + and give us feedback! + </p> + <a class="dac-hero-cta" href="{@docRoot}preview/setup-sdk.html"> + <span class="dac-sprite dac-auto-chevron"></span> + Set up the Preview SDK + </a><br> + <a class="dac-hero-cta" href="{@docRoot}preview/api-overview.html"> + <span class="dac-sprite dac-auto-chevron"></span> + Review the API changes + </a><br> + <a class="dac-hero-cta" href="https://code.google.com/p/android-developer-preview/"> + <span class="dac-sprite dac-auto-chevron"></span> + Report issues + </a><br> + </div> + </div> + </div> +</section> + +<section class="dac-section dac-gray dac-small dac-invert"><div class="wrap"> + <h2 class="norule">Latest</h2> + <div class="resource-widget resource-flow-layout col-16" + data-query="collection:develop/landing/latest" + data-cardSizes="6x6" + data-maxResults="3"></div> +</div></section> + + +<section class="dac-section"><div class="wrap"> + <h1 class="dac-section-title">Resources</h1> + <div class="dac-section-subtitle"> + Check out these resources to help you get started with the M Developer Preview. + </div> + <div class="resource-widget resource-flow-layout col-16" + data-query="collection:preview/landing/resources" + data-cardSizes="6x6" + data-maxResults="6"></div> +</div></section> + + diff --git a/docs/html/preview/overview.jd b/docs/html/preview/overview.jd new file mode 100644 index 0000000..00f1cfe --- /dev/null +++ b/docs/html/preview/overview.jd @@ -0,0 +1,7 @@ +page.title=Preview Program Overview + +@jd:body + +<p> + This is an overview of the program. Bacon. +</p>
\ No newline at end of file diff --git a/docs/html/preview/preview_toc.cs b/docs/html/preview/preview_toc.cs index bea4914..fbf73f6 100644 --- a/docs/html/preview/preview_toc.cs +++ b/docs/html/preview/preview_toc.cs @@ -1,6 +1,11 @@ <ul id="nav"> <li class="nav-section"> + <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/overview.html"> + Program Overview</a></div> + </li> + + <li class="nav-section"> <div class="nav-section-header empty"><a href="<?cs var:toroot ?>preview/setup-sdk.html"> Set up the SDK</a></div> </li> @@ -29,9 +34,4 @@ License Agreement</a></div> </li> - <li class="nav-section" style="margin: 20px 0 0 -10px;"> - <div class="nav-section-header empty"><a href="<?cs var:toroot ?>index.html" class="back-link"> - Developer Home</a></div> - </li> - </ul> diff --git a/docs/html/preview/reference.jd b/docs/html/preview/reference.jd index ee1f24d..2d30c62 100644 --- a/docs/html/preview/reference.jd +++ b/docs/html/preview/reference.jd @@ -9,7 +9,7 @@ page.title=Reference <ul> <li> - <a href="http://storage.googleapis.com/androiddevelopers/preview/l-developer-preview-reference.zip"> + <a href="http://storage.googleapis.com/androiddevelopers/preview/m-developer-preview-reference.zip"> M Developer Preview reference</a> </li> </ul>
\ No newline at end of file diff --git a/graphics/java/android/graphics/Atlas.java b/graphics/java/android/graphics/Atlas.java index 39a5a53..e0a5345 100644 --- a/graphics/java/android/graphics/Atlas.java +++ b/graphics/java/android/graphics/Atlas.java @@ -21,10 +21,12 @@ package android.graphics; */ public class Atlas { /** - * This flag indicates whether the packing algorithm will attempt - * to rotate entries to make them fit better in the atlas. + * WARNING: These flag values are part of the on-disk configuration information, + * do not change their values. */ - public static final int FLAG_ALLOW_ROTATIONS = 0x1; + + /** DELETED: FLAG_ROTATION = 0x01 */ + /** * This flag indicates whether the packing algorithm should leave * an empty 1 pixel wide border around each bitmap. This border can @@ -52,9 +54,7 @@ public class Atlas { /** * Represents a bitmap packed in the atlas. Each entry has a location in - * pixels in the atlas and a rotation flag. If the entry was rotated, the - * bitmap must be rotated by 90 degrees (in either direction as long as - * the origin remains the same) before being rendered into the atlas. + * pixels in the atlas and a rotation flag. */ public static class Entry { /** @@ -65,11 +65,6 @@ public class Atlas { * Location, in pixels, of the bitmap on the Y axis in the atlas. */ public int y; - - /** - * If true, the bitmap must be rotated 90 degrees in the atlas. - */ - public boolean rotated; } private final Policy mPolicy; @@ -239,7 +234,6 @@ public class Atlas { private final SplitDecision mSplitDecision; - private final boolean mAllowRotation; private final int mPadding; /** @@ -263,7 +257,6 @@ public class Atlas { } SlicePolicy(int width, int height, int flags, SplitDecision splitDecision) { - mAllowRotation = (flags & FLAG_ALLOW_ROTATIONS) != 0; mPadding = (flags & FLAG_ADD_PADDING) != 0 ? 1 : 0; // The entire atlas is empty at first, minus padding @@ -360,26 +353,9 @@ public class Atlas { * * @return True if the rectangle was packed in the atlas, false otherwise */ - @SuppressWarnings("SuspiciousNameCombination") private boolean insert(Cell cell, Cell prev, int width, int height, Entry entry) { - boolean rotated = false; - - // If the rectangle doesn't fit we'll try to rotate it - // if possible before giving up if (cell.width < width || cell.height < height) { - if (mAllowRotation) { - if (cell.width < height || cell.height < width) { - return false; - } - - // Rotate the rectangle - int temp = width; - width = height; - height = temp; - rotated = true; - } else { - return false; - } + return false; } // Remaining free space after packing the rectangle @@ -433,7 +409,6 @@ public class Atlas { // Return the location and rotation of the packed rectangle entry.x = cell.x; entry.y = cell.y; - entry.rotated = rotated; return true; } diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index 587e7fa..b15caeb 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -1631,6 +1631,7 @@ public: status_t applyStyle(uint32_t resID, bool force=false); status_t setTo(const Theme& other); + status_t clear(); /** * Retrieve a value in the theme. If the theme defines this diff --git a/keystore/java/android/security/AndroidKeyPairGenerator.java b/keystore/java/android/security/AndroidKeyPairGenerator.java index 3b25ba6..3f29c6a 100644 --- a/keystore/java/android/security/AndroidKeyPairGenerator.java +++ b/keystore/java/android/security/AndroidKeyPairGenerator.java @@ -54,13 +54,13 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { public static class RSA extends AndroidKeyPairGenerator { public RSA() { - super("RSA"); + super(KeyStoreKeyProperties.Algorithm.RSA); } } public static class EC extends AndroidKeyPairGenerator { public EC() { - super("EC"); + super(KeyStoreKeyProperties.Algorithm.EC); } } @@ -83,15 +83,15 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { private android.security.KeyStore mKeyStore; private KeyPairGeneratorSpec mSpec; - private String mKeyAlgorithm; + private @KeyStoreKeyProperties.AlgorithmEnum String mKeyAlgorithm; private int mKeyType; private int mKeySize; - protected AndroidKeyPairGenerator(String algorithm) { + protected AndroidKeyPairGenerator(@KeyStoreKeyProperties.AlgorithmEnum String algorithm) { mAlgorithm = algorithm; } - public String getAlgorithm() { + public @KeyStoreKeyProperties.AlgorithmEnum String getAlgorithm() { return mAlgorithm; } @@ -197,7 +197,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { return certGen.generate(privateKey); } - private String getKeyAlgorithm(KeyPairGeneratorSpec spec) { + private @KeyStoreKeyProperties.AlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) { String result = spec.getKeyType(); if (result != null) { return result; @@ -248,10 +248,11 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } } - private static String getDefaultSignatureAlgorithmForKeyAlgorithm(String algorithm) { - if ("RSA".equalsIgnoreCase(algorithm)) { + private static String getDefaultSignatureAlgorithmForKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(algorithm)) { return "sha256WithRSA"; - } else if ("EC".equalsIgnoreCase(algorithm)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(algorithm)) { return "sha256WithECDSA"; } else { throw new IllegalArgumentException("Unsupported key type " + algorithm); @@ -287,7 +288,7 @@ public abstract class AndroidKeyPairGenerator extends KeyPairGeneratorSpi { } KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) params; - String keyAlgorithm = getKeyAlgorithm(spec); + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithm = getKeyAlgorithm(spec); int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm); if (keyType == -1) { throw new InvalidAlgorithmParameterException( diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java index 72cb062..e82ff6a 100644 --- a/keystore/java/android/security/AndroidKeyStore.java +++ b/keystore/java/android/security/AndroidKeyStore.java @@ -128,10 +128,11 @@ public class AndroidKeyStore extends KeyStoreSpi { keymasterDigest = keymasterDigests.get(0); } - String keyAlgorithmString; + @KeyStoreKeyProperties.AlgorithmEnum String keyAlgorithmString; try { - keyAlgorithmString = KeymasterUtils.getJcaSecretKeyAlgorithm( - keymasterAlgorithm, keymasterDigest); + keyAlgorithmString = + KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + keymasterAlgorithm, keymasterDigest); } catch (IllegalArgumentException e) { throw (UnrecoverableKeyException) new UnrecoverableKeyException("Unsupported secret key type").initCause(e); @@ -451,10 +452,10 @@ public class AndroidKeyStore extends KeyStoreSpi { int keymasterAlgorithm; int keymasterDigest; try { - keymasterAlgorithm = KeymasterUtils.getKeymasterAlgorithmFromJcaSecretKeyAlgorithm( + keymasterAlgorithm = KeyStoreKeyProperties.Algorithm.toKeymasterSecretKeyAlgorithm( keyAlgorithmString); keymasterDigest = - KeymasterUtils.getKeymasterDigestfromJcaSecretKeyAlgorithm(keyAlgorithmString); + KeyStoreKeyProperties.Algorithm.toKeymasterDigest(keyAlgorithmString); } catch (IllegalArgumentException e) { throw new KeyStoreException("Unsupported secret key algorithm: " + keyAlgorithmString); } @@ -465,8 +466,7 @@ public class AndroidKeyStore extends KeyStoreSpi { int[] keymasterDigests; if (params.isDigestsSpecified()) { // Digest(s) specified in parameters - keymasterDigests = - KeymasterUtils.getKeymasterDigestsFromJcaDigestAlgorithms(params.getDigests()); + keymasterDigests = KeyStoreKeyProperties.Digest.allToKeymaster(params.getDigests()); if (keymasterDigest != -1) { // Digest also specified in the JCA key algorithm name. if (!com.android.internal.util.ArrayUtils.contains( @@ -494,8 +494,8 @@ public class AndroidKeyStore extends KeyStoreSpi { } @KeyStoreKeyProperties.PurposeEnum int purposes = params.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - params.getBlockModes()); + int[] keymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(params.getBlockModes()); if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (params.isRandomizedEncryptionRequired())) { for (int keymasterBlockMode : keymasterBlockModes) { @@ -503,8 +503,7 @@ public class AndroidKeyStore extends KeyStoreSpi { throw new KeyStoreException( "Randomized encryption (IND-CPA) required but may be violated by block" + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) + + KeyStoreKeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + ". See KeyStoreParameter documentation."); } } @@ -513,11 +512,11 @@ public class AndroidKeyStore extends KeyStoreSpi { args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); } args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - int[] keymasterPaddings = ArrayUtils.concat( - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( - params.getEncryptionPaddings()), - KeymasterUtils.getKeymasterPaddingsFromJcaSignaturePaddings( - params.getSignaturePaddings())); + if (params.getSignaturePaddings().length > 0) { + throw new KeyStoreException("Signature paddings not supported for symmetric keys"); + } + int[] keymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + params.getEncryptionPaddings()); args.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); KeymasterUtils.addUserAuthArgs(args, params.getContext(), diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index e9c24dd..8e27dc3 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -262,7 +262,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, String host, int port, String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String alias) { choosePrivateKeyAlias(activity, response, keyTypes, issuers, host, port, null, alias); } @@ -306,9 +307,8 @@ public final class KeyChain { * unavailable. */ public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response, - String[] keyTypes, Principal[] issuers, - String host, int port, String url, - String alias) { + @KeyStoreKeyProperties.AlgorithmEnum String[] keyTypes, Principal[] issuers, + String host, int port, String url, String alias) { /* * TODO currently keyTypes, issuers are unused. They are meant * to follow the semantics and purpose of X509KeyManager @@ -431,9 +431,11 @@ public final class KeyChain { * specific {@code PrivateKey} type indicated by {@code algorithm} (e.g., * "RSA"). */ - public static boolean isKeyAlgorithmSupported(String algorithm) { + public static boolean isKeyAlgorithmSupported( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { final String algUpper = algorithm.toUpperCase(Locale.US); - return "EC".equals(algUpper) || "RSA".equals(algUpper); + return KeyStoreKeyProperties.Algorithm.EC.equals(algUpper) + || KeyStoreKeyProperties.Algorithm.RSA.equals(algUpper); } /** @@ -443,7 +445,8 @@ public final class KeyChain { * hardware support that can be used to bind keys to the device in a way * that makes it non-exportable. */ - public static boolean isBoundKeyAlgorithm(String algorithm) { + public static boolean isBoundKeyAlgorithm( + @KeyStoreKeyProperties.AlgorithmEnum String algorithm) { if (!isKeyAlgorithmSupported(algorithm)) { return false; } diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java index 8f135a6..729646d 100644 --- a/keystore/java/android/security/KeyGeneratorSpec.java +++ b/keystore/java/android/security/KeyGeneratorSpec.java @@ -48,8 +48,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -63,8 +63,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -160,14 +160,14 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -195,10 +195,12 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; @@ -220,8 +222,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -346,7 +348,8 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for keys which are used for encryption/decryption. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -357,7 +360,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>This must be specified for encryption/decryption keys. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -425,7 +428,7 @@ public class KeyGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the user needs to authenticate for every use of the key. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java index d6d3789..25c61fd 100644 --- a/keystore/java/android/security/KeyPairGeneratorSpec.java +++ b/keystore/java/android/security/KeyPairGeneratorSpec.java @@ -85,13 +85,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mDigests; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private final String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; @@ -138,10 +138,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] digests, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] blockModes, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -246,7 +246,7 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Returns the key type (e.g., "EC", "RSA") specified by this parameter. */ - public String getKeyType() { + public @KeyStoreKeyProperties.AlgorithmEnum String getKeyType() { return mKeyType; } @@ -352,28 +352,28 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding schemes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -403,14 +403,14 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { } /** - * Gets the duration of time (seconds) for which the private key can be used after the user - * is successfully authenticated. + * Gets the duration of time (seconds) for which this key can be used after the user is + * successfully authenticated. This has effect only if user authentication is required. * * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ @@ -468,13 +468,13 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mDigests; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; - private String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; - private String[] mSignaturePaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; - private String[] mBlockModes; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; @@ -511,7 +511,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { /** * Sets the key type (e.g., EC, RSA) of the keypair to be created. */ - public Builder setKeyType(String keyType) throws NoSuchAlgorithmException { + public Builder setKeyType(@KeyStoreKeyProperties.AlgorithmEnum String keyType) + throws NoSuchAlgorithmException { if (keyType == null) { throw new NullPointerException("keyType == null"); } else { @@ -628,6 +629,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityEnd(Date) */ public Builder setKeyValidityStart(Date startDate) { @@ -640,6 +643,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityStart(Date) * @see #setKeyValidityForConsumptionEnd(Date) * @see #setKeyValidityForOriginationEnd(Date) @@ -655,6 +660,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityForConsumptionEnd(Date) */ public Builder setKeyValidityForOriginationEnd(Date endDate) { @@ -668,6 +675,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect. + * * @see #setKeyValidityForOriginationEnd(Date) */ public Builder setKeyValidityForConsumptionEnd(Date endDate) { @@ -679,6 +688,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Sets the set of purposes for which the key can be used. * * <p>This must be specified for all keys. There is no default. + * + * <p><b>NOTE: This has currently no effect. */ public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { mPurposes = purposes; @@ -690,8 +701,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * to use the key with any other digest will be rejected. * * <p>This must be specified for keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -702,8 +715,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * rejected. * * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -714,8 +730,11 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * rejected. * * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -725,8 +744,10 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * Attempts to use the key with any other block modes will be rejected. * * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -750,6 +771,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <li>If you are using RSA encryption without padding, consider switching to padding * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> * </ul> + * + * <p><b>NOTE: This has currently no effect. */ public Builder setRandomizedEncryptionRequired(boolean required) { mRandomizedEncryptionRequired = required; @@ -772,6 +795,8 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * + * <p><b>NOTE: This has currently no effect. + * * @see #setUserAuthenticationValidityDurationSeconds(int) */ public Builder setUserAuthenticationRequired(boolean required) { @@ -788,7 +813,9 @@ public final class KeyPairGeneratorSpec implements AlgorithmParameterSpec { * <p>This restriction applies only to private key operations. Public key operations are not * restricted. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * <p><b>NOTE: This has currently no effect. + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 82d328b..304d277 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -115,10 +115,10 @@ public class KeyStore { return mToken; } - static int getKeyTypeForAlgorithm(String keyType) { - if ("RSA".equalsIgnoreCase(keyType)) { + static int getKeyTypeForAlgorithm(@KeyStoreKeyProperties.AlgorithmEnum String keyType) { + if (KeyStoreKeyProperties.Algorithm.RSA.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_RSA; - } else if ("EC".equalsIgnoreCase(keyType)) { + } else if (KeyStoreKeyProperties.Algorithm.EC.equalsIgnoreCase(keyType)) { return NativeConstants.EVP_PKEY_EC; } else { return -1; diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java index 094aa75..bd601bc 100644 --- a/keystore/java/android/security/KeyStoreCipherSpi.java +++ b/keystore/java/android/security/KeyStoreCipherSpi.java @@ -27,6 +27,7 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; @@ -315,15 +316,15 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } else if (e instanceof InvalidAlgorithmParameterException) { throw (InvalidAlgorithmParameterException) e; } else { - throw new RuntimeException("Unexpected exception type", e); + throw new ProviderException("Unexpected exception type", e); } } if (mOperationToken == null) { - throw new IllegalStateException("Keystore returned null operation token"); + throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { - throw new IllegalStateException("Keystore returned invalid operation handle"); + throw new ProviderException("Keystore returned invalid operation handle"); } loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs); @@ -494,13 +495,14 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry } if ((mIv != null) && (mIv.length > 0)) { try { - AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + AlgorithmParameters params = + AlgorithmParameters.getInstance(KeyStoreKeyProperties.Algorithm.AES); params.init(new IvParameterSpec(mIv)); return params; } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Failed to obtain AES AlgorithmParameters", e); + throw new ProviderException("Failed to obtain AES AlgorithmParameters", e); } catch (InvalidParameterSpecException e) { - throw new RuntimeException( + throw new ProviderException( "Failed to initialize AES AlgorithmParameters with an IV", e); } } @@ -633,10 +635,9 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if ((mIv == null) && (mEncrypting)) { // IV was not provided by the caller and thus will be generated by keymaster. // Mix in some additional entropy from the provided SecureRandom. - if (mRng != null) { - mAdditionalEntropyForBegin = new byte[mBlockSizeBytes]; - mRng.nextBytes(mAdditionalEntropyForBegin); - } + mAdditionalEntropyForBegin = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, mBlockSizeBytes); } } } @@ -668,11 +669,11 @@ public abstract class KeyStoreCipherSpi extends CipherSpi implements KeyStoreCry if (mIv == null) { mIv = returnedIv; } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { - throw new IllegalStateException("IV in use differs from provided IV"); + throw new ProviderException("IV in use differs from provided IV"); } } else { if (returnedIv != null) { - throw new IllegalStateException( + throw new ProviderException( "IV in use despite IV not being used by this transformation"); } } diff --git a/keystore/java/android/security/KeyStoreConnectException.java b/keystore/java/android/security/KeyStoreConnectException.java index 1aa3aec..885f1f7 100644 --- a/keystore/java/android/security/KeyStoreConnectException.java +++ b/keystore/java/android/security/KeyStoreConnectException.java @@ -16,12 +16,14 @@ package android.security; +import java.security.ProviderException; + /** * Indicates a communications error with keystore service. * * @hide */ -public class KeyStoreConnectException extends IllegalStateException { +public class KeyStoreConnectException extends ProviderException { public KeyStoreConnectException() { super("Failed to communicate with keystore service"); } diff --git a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java index e5933ad..311278b 100644 --- a/keystore/java/android/security/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/KeyStoreCryptoOperationUtils.java @@ -21,6 +21,7 @@ import android.security.keymaster.KeymasterDefs; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.SecureRandom; /** * Assorted utility methods for implementing crypto operations on top of KeyStore. @@ -28,6 +29,9 @@ import java.security.InvalidKeyException; * @hide */ abstract class KeyStoreCryptoOperationUtils { + + private static volatile SecureRandom sRng; + private KeyStoreCryptoOperationUtils() {} /** @@ -81,4 +85,28 @@ abstract class KeyStoreCryptoOperationUtils { // General cases return getInvalidKeyExceptionForInit(keyStore, key, beginOpResultCode); } + + /** + * Returns the requested number of random bytes to mix into keystore/keymaster RNG. + * + * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default + * RNG. + */ + static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (rng == null) { + rng = getRng(); + } + byte[] result = new byte[sizeBytes]; + rng.nextBytes(result); + return result; + } + + private static SecureRandom getRng() { + // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is + // required to be thread-safe. + if (sRng == null) { + sRng = new SecureRandom(); + } + return sRng; + } } diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java index 0dbe788..5089a25 100644 --- a/keystore/java/android/security/KeyStoreHmacSpi.java +++ b/keystore/java/android/security/KeyStoreHmacSpi.java @@ -24,6 +24,7 @@ import android.security.keymaster.OperationResult; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; +import java.security.ProviderException; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.MacSpi; @@ -185,10 +186,10 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp } if (mOperationToken == null) { - throw new IllegalStateException("Keystore returned null operation token"); + throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { - throw new IllegalStateException("Keystore returned invalid operation handle"); + throw new ProviderException("Keystore returned invalid operation handle"); } mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( @@ -206,17 +207,17 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { - throw new IllegalStateException("Failed to reinitialize MAC", e); + throw new ProviderException("Failed to reinitialize MAC", e); } byte[] output; try { output = mChunkedStreamer.update(input, offset, len); } catch (KeyStoreException e) { - throw new IllegalStateException("Keystore operation failed", e); + throw new ProviderException("Keystore operation failed", e); } if ((output != null) && (output.length != 0)) { - throw new IllegalStateException("Update operation unexpectedly produced output"); + throw new ProviderException("Update operation unexpectedly produced output"); } } @@ -225,14 +226,14 @@ public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOp try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { - throw new IllegalStateException("Failed to reinitialize MAC", e); + throw new ProviderException("Failed to reinitialize MAC", e); } byte[] result; try { result = mChunkedStreamer.doFinal(null, 0, 0); } catch (KeyStoreException e) { - throw new IllegalStateException("Keystore operation failed", e); + throw new ProviderException("Keystore operation failed", e); } resetWhilePreservingInitState(); diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java index 68b5751..4b914c2 100644 --- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java @@ -21,6 +21,7 @@ import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import java.security.InvalidAlgorithmParameterException; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.util.Date; @@ -39,6 +40,17 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { public AES() { super(KeymasterDefs.KM_ALGORITHM_AES, 128); } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + super.engineInit(params, random); + if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) { + throw new InvalidAlgorithmParameterException( + "Unsupported key size: " + mKeySizeBits + + ". Supported: 128, 192, 256."); + } + } } protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi { @@ -87,6 +99,11 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { private KeyGeneratorSpec mSpec; private SecureRandom mRng; + protected int mKeySizeBits; + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterPaddings; + protected KeyStoreKeyGeneratorSpi( int keymasterAlgorithm, int defaultKeySizeBits) { @@ -100,6 +117,97 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { mKeymasterAlgorithm = keymasterAlgorithm; mKeymasterDigest = keymasterDigest; mDefaultKeySizeBits = defaultKeySizeBits; + if (mDefaultKeySizeBits <= 0) { + throw new IllegalArgumentException("Default key size must be positive"); + } + + if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) { + throw new IllegalArgumentException( + "Digest algorithm must be specified for HMAC key"); + } + } + + @Override + protected void engineInit(SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without an " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if ((params == null) || (!(params instanceof KeyGeneratorSpec))) { + throw new InvalidAlgorithmParameterException("Cannot initialize without an " + + KeyGeneratorSpec.class.getName() + " parameter"); + } + KeyGeneratorSpec spec = (KeyGeneratorSpec) params; + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + mRng = random; + mSpec = spec; + + mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; + if (mKeySizeBits <= 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be positive: " + mKeySizeBits); + } else if ((mKeySizeBits % 8) != 0) { + throw new InvalidAlgorithmParameterException( + "Key size in must be a multiple of 8: " + mKeySizeBits); + } + + try { + mKeymasterPurposes = + KeyStoreKeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterPaddings = KeyStoreKeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + mKeymasterBlockModes = + KeyStoreKeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : mKeymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible( + keymasterBlockMode)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by block mode: " + + KeyStoreKeyProperties.BlockMode.fromKeymaster( + keymasterBlockMode) + + ". See " + KeyGeneratorSpec.class.getName() + + " documentation."); + } + } + } + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mSpec = null; + mRng = null; + mKeySizeBits = -1; + mKeymasterPurposes = null; + mKeymasterPaddings = null; + mKeymasterBlockModes = null; } @Override @@ -117,43 +225,14 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { } KeymasterArguments args = new KeymasterArguments(); + args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); if (mKeymasterDigest != -1) { args.addInt(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); } - if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { - if (mKeymasterDigest == -1) { - throw new IllegalStateException("Digest algorithm must be specified for HMAC key"); - } - } - int keySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; - args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits); - @KeyStoreKeyProperties.PurposeEnum int purposes = spec.getPurposes(); - int[] keymasterBlockModes = KeymasterUtils.getKeymasterBlockModesFromJcaBlockModes( - spec.getBlockModes()); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) - && (spec.isRandomizedEncryptionRequired())) { - for (int keymasterBlockMode : keymasterBlockModes) { - if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) { - throw new IllegalStateException( - "Randomized encryption (IND-CPA) required but may be violated by block" - + " mode: " - + KeymasterUtils.getJcaBlockModeFromKeymasterBlockMode( - keymasterBlockMode) - + ". See KeyGeneratorSpec documentation."); - } - } - } - - for (int keymasterPurpose : - KeyStoreKeyProperties.Purpose.allToKeymaster(purposes)) { - args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose); - } - args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes); - args.addInts( - KeymasterDefs.KM_TAG_PADDING, - KeymasterUtils.getKeymasterPaddingsFromJcaEncryptionPaddings( - spec.getEncryptionPaddings())); + args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes); + args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); + args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); KeymasterUtils.addUserAuthArgs(args, spec.getContext(), spec.isUserAuthenticationRequired(), @@ -168,57 +247,31 @@ public abstract class KeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { (spec.getKeyValidityForConsumptionEnd() != null) ? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE)); - if (((purposes & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) + if (((spec.getPurposes() & KeyStoreKeyProperties.Purpose.ENCRYPT) != 0) && (!spec.isRandomizedEncryptionRequired())) { // Permit caller-provided IV when encrypting with this key args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE); } - byte[] additionalEntropy = null; - SecureRandom rng = mRng; - if (rng != null) { - additionalEntropy = new byte[(keySizeBits + 7) / 8]; - rng.nextBytes(additionalEntropy); - } - + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); int flags = spec.getFlags(); String keyAliasInKeystore = Credentials.USER_SECRET_KEY + spec.getKeystoreAlias(); + KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics(); int errorCode = mKeyStore.generateKey( - keyAliasInKeystore, args, additionalEntropy, flags, new KeyCharacteristics()); + keyAliasInKeystore, args, additionalEntropy, flags, resultingKeyCharacteristics); if (errorCode != KeyStore.NO_ERROR) { - throw new IllegalStateException( + throw new ProviderException( "Keystore operation failed", KeyStore.getKeyStoreException(errorCode)); } - String keyAlgorithmJCA = - KeymasterUtils.getJcaSecretKeyAlgorithm(mKeymasterAlgorithm, mKeymasterDigest); - return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); - } - - @Override - protected void engineInit(SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without an " - + KeyGeneratorSpec.class.getName() + " parameter"); - } - - @Override - protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) - throws InvalidAlgorithmParameterException { - if ((params == null) || (!(params instanceof KeyGeneratorSpec))) { - throw new InvalidAlgorithmParameterException("Cannot initialize without an " - + KeyGeneratorSpec.class.getName() + " parameter"); - } - KeyGeneratorSpec spec = (KeyGeneratorSpec) params; - if (spec.getKeystoreAlias() == null) { - throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); } - - mSpec = spec; - mRng = random; - } - - @Override - protected void engineInit(int keySize, SecureRandom random) { - throw new UnsupportedOperationException("Cannot initialize without a " - + KeyGeneratorSpec.class.getName() + " parameter"); + return new KeyStoreSecretKey(keyAliasInKeystore, keyAlgorithmJCA); } } diff --git a/keystore/java/android/security/KeyStoreKeyProperties.java b/keystore/java/android/security/KeyStoreKeyProperties.java index b85ec53..1c3e300 100644 --- a/keystore/java/android/security/KeyStoreKeyProperties.java +++ b/keystore/java/android/security/KeyStoreKeyProperties.java @@ -17,13 +17,23 @@ package android.security; import android.annotation.IntDef; +import android.annotation.StringDef; import android.security.keymaster.KeymasterDefs; import libcore.util.EmptyArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; import java.util.Collection; +import java.util.Locale; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; /** * Properties of {@code AndroidKeyStore} keys. @@ -37,7 +47,7 @@ public abstract class KeyStoreKeyProperties { public @interface PurposeEnum {} /** - * Purpose of key. + * Purposes of key. */ public static abstract class Purpose { private Purpose() {} @@ -122,6 +132,514 @@ public abstract class KeyStoreKeyProperties { } @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Algorithm.RSA, + Algorithm.EC, + Algorithm.AES, + Algorithm.HMAC_SHA1, + Algorithm.HMAC_SHA224, + Algorithm.HMAC_SHA256, + Algorithm.HMAC_SHA384, + Algorithm.HMAC_SHA512, + }) + public @interface AlgorithmEnum {} + + /** + * Key algorithms. + * + * <p>These are standard names which can be used to obtain instances of {@link KeyGenerator}, + * {@link KeyPairGenerator}, {@link Cipher} (as part of the transformation string), {@link Mac}, + * {@link KeyFactory}, {@link SecretKeyFactory}. These are also the names used by + * {@link Key#getAlgorithm()}. + */ + public static abstract class Algorithm { + private Algorithm() {} + + /** Rivest Shamir Adleman (RSA) key. */ + public static final String RSA = "RSA"; + + /** Elliptic Curve (EC) key. */ + public static final String EC = "EC"; + + /** Advanced Encryption Standard (AES) key. */ + public static final String AES = "AES"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-1 as the hash. */ + public static final String HMAC_SHA1 = "HmacSHA1"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-224 as the hash. */ + public static final String HMAC_SHA224 = "HmacSHA224"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-256 as the hash. */ + public static final String HMAC_SHA256 = "HmacSHA256"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-384 as the hash. */ + public static final String HMAC_SHA384 = "HmacSHA384"; + + /** Keyed-Hash Message Authentication Code (HMAC) key using SHA-512 as the hash. */ + public static final String HMAC_SHA512 = "HmacSHA512"; + + /** + * @hide + */ + static int toKeymasterSecretKeyAlgorithm(@AlgorithmEnum String algorithm) { + if (AES.equalsIgnoreCase(algorithm)) { + return KeymasterDefs.KM_ALGORITHM_AES; + } else if (algorithm.toUpperCase(Locale.US).startsWith("HMAC")) { + return KeymasterDefs.KM_ALGORITHM_HMAC; + } else { + throw new IllegalArgumentException( + "Unsupported secret key algorithm: " + algorithm); + } + } + + /** + * @hide + */ + static @AlgorithmEnum String fromKeymasterSecretKeyAlgorithm( + int keymasterAlgorithm, int keymasterDigest) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_AES: + if (keymasterDigest != -1) { + throw new IllegalArgumentException("Digest not supported for AES key: " + + Digest.fromKeymaster(keymasterDigest)); + } + return AES; + case KeymasterDefs.KM_ALGORITHM_HMAC: + switch (keymasterDigest) { + case KeymasterDefs.KM_DIGEST_SHA1: + return HMAC_SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return HMAC_SHA224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return HMAC_SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return HMAC_SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return HMAC_SHA512; + default: + throw new IllegalArgumentException("Unsupported HMAC digest: " + + Digest.fromKeymaster(keymasterDigest)); + } + default: + throw new IllegalArgumentException( + "Unsupported algorithm: " + keymasterAlgorithm); + } + } + + /** + * @hide + * + * @return keymaster digest or {@code -1} if the algorithm does not involve a digest. + */ + static int toKeymasterDigest(@AlgorithmEnum String algorithm) { + String algorithmUpper = algorithm.toUpperCase(Locale.US); + if (algorithmUpper.startsWith("HMAC")) { + String digestUpper = algorithmUpper.substring("HMAC".length()); + switch (digestUpper) { + case "SHA1": + return KeymasterDefs.KM_DIGEST_SHA1; + case "SHA224": + return KeymasterDefs.KM_DIGEST_SHA_2_224; + case "SHA256": + return KeymasterDefs.KM_DIGEST_SHA_2_256; + case "SHA384": + return KeymasterDefs.KM_DIGEST_SHA_2_384; + case "SHA512": + return KeymasterDefs.KM_DIGEST_SHA_2_512; + default: + throw new IllegalArgumentException( + "Unsupported HMAC digest: " + digestUpper); + } + } else { + return -1; + } + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + BlockMode.ECB, + BlockMode.CBC, + BlockMode.CTR, + BlockMode.GCM, + }) + public @interface BlockModeEnum {} + + /** + * Block modes that can be used when encrypting/decrypting using a key. + */ + public static abstract class BlockMode { + private BlockMode() {} + + /** Electronic Codebook (ECB) block mode. */ + public static final String ECB = "ECB"; + + /** Cipher Block Chaining (CBC) block mode. */ + public static final String CBC = "CBC"; + + /** Counter (CTR) block mode. */ + public static final String CTR = "CTR"; + + /** Galois/Counter Mode (GCM) block mode. */ + public static final String GCM = "GCM"; + + /** + * @hide + */ + static int toKeymaster(@BlockModeEnum String blockMode) { + if (ECB.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_ECB; + } else if (CBC.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CBC; + } else if (CTR.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_CTR; + } else if (GCM.equalsIgnoreCase(blockMode)) { + return KeymasterDefs.KM_MODE_GCM; + } else { + throw new IllegalArgumentException("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String fromKeymaster(int blockMode) { + switch (blockMode) { + case KeymasterDefs.KM_MODE_ECB: + return ECB; + case KeymasterDefs.KM_MODE_CBC: + return CBC; + case KeymasterDefs.KM_MODE_CTR: + return CTR; + case KeymasterDefs.KM_MODE_GCM: + return GCM; + default: + throw new IllegalArgumentException("Unsupported block mode: " + blockMode); + } + } + + /** + * @hide + */ + static @BlockModeEnum String[] allFromKeymaster(Collection<Integer> blockModes) { + if ((blockModes == null) || (blockModes.isEmpty())) { + return EmptyArray.STRING; + } + @BlockModeEnum String[] result = new String[blockModes.size()]; + int offset = 0; + for (int blockMode : blockModes) { + result[offset] = fromKeymaster(blockMode); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@BlockModeEnum String[] blockModes) { + if ((blockModes == null) || (blockModes.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[blockModes.length]; + for (int i = 0; i < blockModes.length; i++) { + result[i] = toKeymaster(blockModes[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + EncryptionPadding.NONE, + EncryptionPadding.PKCS7, + EncryptionPadding.RSA_PKCS1, + EncryptionPadding.RSA_OAEP, + }) + public @interface EncryptionPaddingEnum {} + + /** + * Padding schemes for encryption/decryption. + */ + public static abstract class EncryptionPadding { + private EncryptionPadding() {} + + /** + * No padding. + */ + public static final String NONE = "NoPadding"; + + /** + * PKCS#7 padding. + */ + public static final String PKCS7 = "PKCS7Padding"; + + /** + * RSA PKCS#1 v1.5 padding for encryption/decryption. + */ + public static final String RSA_PKCS1 = "PKCS1Padding"; + + /** + * RSA Optimal Asymmetric Encryption Padding (OAEP). + */ + public static final String RSA_OAEP = "OAEPPadding"; + + /** + * @hide + */ + static int toKeymaster(@EncryptionPaddingEnum String padding) { + if (NONE.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_NONE; + } else if (PKCS7.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_PKCS7; + } else if (RSA_PKCS1.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; + } else if (RSA_OAEP.equalsIgnoreCase(padding)) { + return KeymasterDefs.KM_PAD_RSA_OAEP; + } else { + throw new IllegalArgumentException( + "Unsupported encryption padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @EncryptionPaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_NONE: + return NONE; + case KeymasterDefs.KM_PAD_PKCS7: + return PKCS7; + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_OAEP: + return RSA_OAEP; + default: + throw new IllegalArgumentException( + "Unsupported encryption padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@EncryptionPaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + SignaturePadding.RSA_PKCS1, + SignaturePadding.RSA_PSS, + }) + public @interface SignaturePaddingEnum {} + + /** + * Padding schemes for signing/verification. + */ + public static abstract class SignaturePadding { + private SignaturePadding() {} + + /** + * RSA PKCS#1 v1.5 padding for signatures. + */ + public static final String RSA_PKCS1 = "PKCS1"; + + /** + * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding. + */ + public static final String RSA_PSS = "PSS"; + + /** + * @hide + */ + static int toKeymaster(@SignaturePaddingEnum String padding) { + switch (padding.toUpperCase(Locale.US)) { + case RSA_PKCS1: + return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; + case RSA_PSS: + return KeymasterDefs.KM_PAD_RSA_PSS; + default: + throw new IllegalArgumentException( + "Unsupported signature padding scheme: " + padding); + } + } + + /** + * @hide + */ + static @SignaturePaddingEnum String fromKeymaster(int padding) { + switch (padding) { + case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: + return RSA_PKCS1; + case KeymasterDefs.KM_PAD_RSA_PSS: + return RSA_PSS; + default: + throw new IllegalArgumentException("Unsupported signature padding: " + padding); + } + } + + /** + * @hide + */ + static int[] allToKeymaster(@SignaturePaddingEnum String[] paddings) { + if ((paddings == null) || (paddings.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[paddings.length]; + for (int i = 0; i < paddings.length; i++) { + result[i] = toKeymaster(paddings[i]); + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + Digest.NONE, + Digest.MD5, + Digest.SHA1, + Digest.SHA224, + Digest.SHA256, + Digest.SHA384, + Digest.SHA512, + }) + public @interface DigestEnum {} + + /** + * Digests that can be used with a key when signing or generating Message Authentication + * Codes (MACs). + */ + public static abstract class Digest { + private Digest() {} + + /** + * No digest: sign/authenticate the raw message. + */ + public static final String NONE = "NONE"; + + /** + * MD5 digest. + */ + public static final String MD5 = "MD5"; + + /** + * SHA-1 digest. + */ + public static final String SHA1 = "SHA-1"; + + /** + * SHA-2 224 (aka SHA-224) digest. + */ + public static final String SHA224 = "SHA-224"; + + /** + * SHA-2 256 (aka SHA-256) digest. + */ + public static final String SHA256 = "SHA-256"; + + /** + * SHA-2 384 (aka SHA-384) digest. + */ + public static final String SHA384 = "SHA-384"; + + /** + * SHA-2 512 (aka SHA-512) digest. + */ + public static final String SHA512 = "SHA-512"; + + /** + * @hide + */ + static int toKeymaster(@DigestEnum String digest) { + switch (digest.toUpperCase(Locale.US)) { + case SHA1: + return KeymasterDefs.KM_DIGEST_SHA1; + case SHA224: + return KeymasterDefs.KM_DIGEST_SHA_2_224; + case SHA256: + return KeymasterDefs.KM_DIGEST_SHA_2_256; + case SHA384: + return KeymasterDefs.KM_DIGEST_SHA_2_384; + case SHA512: + return KeymasterDefs.KM_DIGEST_SHA_2_512; + case NONE: + return KeymasterDefs.KM_DIGEST_NONE; + case MD5: + return KeymasterDefs.KM_DIGEST_MD5; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String fromKeymaster(int digest) { + switch (digest) { + case KeymasterDefs.KM_DIGEST_NONE: + return NONE; + case KeymasterDefs.KM_DIGEST_MD5: + return MD5; + case KeymasterDefs.KM_DIGEST_SHA1: + return SHA1; + case KeymasterDefs.KM_DIGEST_SHA_2_224: + return SHA224; + case KeymasterDefs.KM_DIGEST_SHA_2_256: + return SHA256; + case KeymasterDefs.KM_DIGEST_SHA_2_384: + return SHA384; + case KeymasterDefs.KM_DIGEST_SHA_2_512: + return SHA512; + default: + throw new IllegalArgumentException("Unsupported digest algorithm: " + digest); + } + } + + /** + * @hide + */ + static @DigestEnum String[] allFromKeymaster(Collection<Integer> digests) { + if (digests.isEmpty()) { + return EmptyArray.STRING; + } + String[] result = new String[digests.size()]; + int offset = 0; + for (int digest : digests) { + result[offset] = fromKeymaster(digest); + offset++; + } + return result; + } + + /** + * @hide + */ + static int[] allToKeymaster(@DigestEnum String[] digests) { + if ((digests == null) || (digests.length == 0)) { + return EmptyArray.INT; + } + int[] result = new int[digests.length]; + int offset = 0; + for (@DigestEnum String digest : digests) { + result[offset] = toKeymaster(digest); + offset++; + } + return result; + } + } + + @Retention(RetentionPolicy.SOURCE) @IntDef({Origin.GENERATED, Origin.IMPORTED, Origin.UNKNOWN}) public @interface OriginEnum {} diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java index 96d58d8..7533bdc 100644 --- a/keystore/java/android/security/KeyStoreKeySpec.java +++ b/keystore/java/android/security/KeyStoreKeySpec.java @@ -32,10 +32,10 @@ public class KeyStoreKeySpec implements KeySpec { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserAuthenticationRequirementTeeEnforced; @@ -51,10 +51,10 @@ public class KeyStoreKeySpec implements KeySpec { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, boolean userAuthenticationRequirementTeeEnforced) { @@ -143,28 +143,28 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } /** * Gets the set of padding modes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } /** * Gets the set of padding modes with which the key can be used when signing/verifying. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } /** * Gets the set of digest algorithms with which the key can be used. */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { return ArrayUtils.cloneIfNotEmpty(mDigests); } @@ -179,10 +179,10 @@ public class KeyStoreKeySpec implements KeySpec { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. * * @see #isUserAuthenticationRequired() */ diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java index b4747e9..8d7a19f 100644 --- a/keystore/java/android/security/KeyStoreParameter.java +++ b/keystore/java/android/security/KeyStoreParameter.java @@ -45,10 +45,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; private final @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private final String[] mEncryptionPaddings; - private final String[] mSignaturePaddings; - private final String[] mDigests; - private final String[] mBlockModes; + private final @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private final @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private final @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private final @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; @@ -60,10 +60,10 @@ public final class KeyStoreParameter implements ProtectionParameter { Date keyValidityForOriginationEnd, Date keyValidityForConsumptionEnd, @KeyStoreKeyProperties.PurposeEnum int purposes, - String[] encryptionPaddings, - String[] signaturePaddings, - String[] digests, - String[] blockModes, + @KeyStoreKeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, + @KeyStoreKeyProperties.SignaturePaddingEnum String[] signaturePaddings, + @KeyStoreKeyProperties.DigestEnum String[] digests, + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds) { @@ -151,7 +151,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of padding schemes with which the key can be used when encrypting/decrypting. */ - public String[] getEncryptionPaddings() { + public @KeyStoreKeyProperties.EncryptionPaddingEnum String[] getEncryptionPaddings() { return ArrayUtils.cloneIfNotEmpty(mEncryptionPaddings); } @@ -159,7 +159,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * Gets the set of padding schemes with which the key can be used when signing or verifying * signatures. */ - public String[] getSignaturePaddings() { + public @KeyStoreKeyProperties.SignaturePaddingEnum String[] getSignaturePaddings() { return ArrayUtils.cloneIfNotEmpty(mSignaturePaddings); } @@ -170,7 +170,7 @@ public final class KeyStoreParameter implements ProtectionParameter { * * @see #isDigestsSpecified() */ - public String[] getDigests() { + public @KeyStoreKeyProperties.DigestEnum String[] getDigests() { if (mDigests == null) { throw new IllegalStateException("Digests not specified"); } @@ -190,7 +190,7 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the set of block modes with which the key can be used. */ - public String[] getBlockModes() { + public @KeyStoreKeyProperties.BlockModeEnum String[] getBlockModes() { return ArrayUtils.cloneIfNotEmpty(mBlockModes); } @@ -218,10 +218,12 @@ public final class KeyStoreParameter implements ProtectionParameter { /** * Gets the duration of time (seconds) for which this key can be used after the user is - * successfully authenticated. + * successfully authenticated. This has effect only if user authentication is required. * - * @return duration in seconds or {@code -1} if not restricted. {@code 0} means authentication - * is required for every use of the key. + * @return duration in seconds or {@code -1} if authentication is required for every use of the + * key. + * + * @see #isUserAuthenticationRequired() */ public int getUserAuthenticationValidityDurationSeconds() { return mUserAuthenticationValidityDurationSeconds; @@ -251,10 +253,10 @@ public final class KeyStoreParameter implements ProtectionParameter { private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyStoreKeyProperties.PurposeEnum int mPurposes; - private String[] mEncryptionPaddings; - private String[] mSignaturePaddings; - private String[] mDigests; - private String[] mBlockModes; + private @KeyStoreKeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; + private @KeyStoreKeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; + private @KeyStoreKeyProperties.DigestEnum String[] mDigests; + private @KeyStoreKeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; @@ -292,6 +294,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityEnd(Date) */ public Builder setKeyValidityStart(Date startDate) { @@ -304,6 +308,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityStart(Date) * @see #setKeyValidityForConsumptionEnd(Date) * @see #setKeyValidityForOriginationEnd(Date) @@ -319,6 +325,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityForConsumptionEnd(Date) */ public Builder setKeyValidityForOriginationEnd(Date endDate) { @@ -332,6 +340,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the key is valid at any instant. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setKeyValidityForOriginationEnd(Date) */ public Builder setKeyValidityForConsumptionEnd(Date endDate) { @@ -343,6 +353,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * Sets the set of purposes for which the key can be used. * * <p>This must be specified for all keys. There is no default. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ public Builder setPurposes(@KeyStoreKeyProperties.PurposeEnum int purposes) { mPurposes = purposes; @@ -355,8 +367,11 @@ public final class KeyStoreParameter implements ProtectionParameter { * rejected. * * <p>This must be specified for keys which are used for encryption/decryption. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setEncryptionPaddings(String... paddings) { + public Builder setEncryptionPaddings( + @KeyStoreKeyProperties.EncryptionPaddingEnum String... paddings) { mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -367,8 +382,11 @@ public final class KeyStoreParameter implements ProtectionParameter { * rejected. * * <p>This must be specified for RSA keys which are used for signing/verification. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setSignaturePaddings(String... paddings) { + public Builder setSignaturePaddings( + @KeyStoreKeyProperties.SignaturePaddingEnum String... paddings) { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(paddings); return this; } @@ -380,8 +398,10 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>For HMAC keys, the default is the digest specified in {@link Key#getAlgorithm()}. For * asymmetric signing keys this constraint must be specified. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setDigests(String... digests) { + public Builder setDigests(@KeyStoreKeyProperties.DigestEnum String... digests) { mDigests = ArrayUtils.cloneIfNotEmpty(digests); return this; } @@ -391,8 +411,10 @@ public final class KeyStoreParameter implements ProtectionParameter { * Attempts to use the key with any other block modes will be rejected. * * <p>This must be specified for encryption/decryption keys. + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ - public Builder setBlockModes(String... blockModes) { + public Builder setBlockModes(@KeyStoreKeyProperties.BlockModeEnum String... blockModes) { mBlockModes = ArrayUtils.cloneIfNotEmpty(blockModes); return this; } @@ -430,6 +452,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * <li>If you are using RSA encryption without padding, consider switching to padding * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li> * </ul> + * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. */ public Builder setRandomizedEncryptionRequired(boolean required) { mRandomizedEncryptionRequired = required; @@ -449,6 +473,8 @@ public final class KeyStoreParameter implements ProtectionParameter { * <a href="{@docRoot}training/articles/keystore.html#UserAuthentication">More * information</a>. * + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * * @see #setUserAuthenticationValidityDurationSeconds(int) */ public Builder setUserAuthenticationRequired(boolean required) { @@ -462,7 +488,9 @@ public final class KeyStoreParameter implements ProtectionParameter { * * <p>By default, the user needs to authenticate for every use of the key. * - * @param seconds duration in seconds or {@code 0} if the user needs to authenticate for + * <p><b>NOTE: This has currently no effect on asymmetric key pairs. + * + * @param seconds duration in seconds or {@code -1} if the user needs to authenticate for * every use of the key. * * @see #setUserAuthenticationRequired(boolean) diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java index bfe09e3..ff79b7a 100644 --- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java @@ -79,8 +79,8 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { int keySize; @KeyStoreKeyProperties.PurposeEnum int purposes; String[] encryptionPaddings; - String[] digests; - String[] blockModes; + @KeyStoreKeyProperties.DigestEnum String[] digests; + @KeyStoreKeyProperties.BlockModeEnum String[] blockModes; int keymasterSwEnforcedUserAuthenticators; int keymasterHwEnforcedUserAuthenticators; try { @@ -105,10 +105,10 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { List<String> encryptionPaddingsList = new ArrayList<String>(); for (int keymasterPadding : keyCharacteristics.getInts(KeymasterDefs.KM_TAG_PADDING)) { - String jcaPadding; + @KeyStoreKeyProperties.EncryptionPaddingEnum String jcaPadding; try { - jcaPadding = KeymasterUtils.getJcaEncryptionPaddingFromKeymasterPadding( - keymasterPadding); + jcaPadding = + KeyStoreKeyProperties.EncryptionPadding.fromKeymaster(keymasterPadding); } catch (IllegalArgumentException e) { throw new InvalidKeySpecException( "Unsupported encryption padding: " + keymasterPadding); @@ -118,9 +118,9 @@ public class KeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { encryptionPaddings = encryptionPaddingsList.toArray(new String[encryptionPaddingsList.size()]); - digests = KeymasterUtils.getJcaDigestAlgorithmsFromKeymasterDigests( + digests = KeyStoreKeyProperties.Digest.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_DIGEST)); - blockModes = KeymasterUtils.getJcaBlockModesFromKeymasterBlockModes( + blockModes = KeyStoreKeyProperties.BlockMode.allFromKeymaster( keyCharacteristics.getInts(KeymasterDefs.KM_TAG_BLOCK_MODE)); keymasterSwEnforcedUserAuthenticators = keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); diff --git a/keystore/java/android/security/KeymasterUtils.java b/keystore/java/android/security/KeymasterUtils.java index aa44ecd..df67ae7 100644 --- a/keystore/java/android/security/KeymasterUtils.java +++ b/keystore/java/android/security/KeymasterUtils.java @@ -21,11 +21,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; -import libcore.util.EmptyArray; - -import java.util.Collection; -import java.util.Locale; - /** * @hide */ @@ -33,152 +28,6 @@ public abstract class KeymasterUtils { private KeymasterUtils() {} - public static int getKeymasterAlgorithmFromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - if ("AES".equalsIgnoreCase(jcaKeyAlgorithm)) { - return KeymasterDefs.KM_ALGORITHM_AES; - } else if (jcaKeyAlgorithm.toUpperCase(Locale.US).startsWith("HMAC")) { - return KeymasterDefs.KM_ALGORITHM_HMAC; - } else { - throw new IllegalArgumentException( - "Unsupported secret key algorithm: " + jcaKeyAlgorithm); - } - } - - public static String getJcaSecretKeyAlgorithm(int keymasterAlgorithm, int keymasterDigest) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_AES: - if (keymasterDigest != -1) { - throw new IllegalArgumentException( - "Digest not supported for AES key: " + keymasterDigest); - } - return "AES"; - case KeymasterDefs.KM_ALGORITHM_HMAC: - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_SHA1: - return "HmacSHA1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "HmacSHA224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "HmacSHA256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "HmacSHA384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "HmacSHA512"; - default: - throw new IllegalArgumentException( - "Unsupported HMAC digest: " + keymasterDigest); - } - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static String getJcaKeyPairAlgorithmFromKeymasterAlgorithm(int keymasterAlgorithm) { - switch (keymasterAlgorithm) { - case KeymasterDefs.KM_ALGORITHM_RSA: - return "RSA"; - case KeymasterDefs.KM_ALGORITHM_EC: - return "EC"; - default: - throw new IllegalArgumentException("Unsupported algorithm: " + keymasterAlgorithm); - } - } - - public static int getKeymasterDigestfromJcaSecretKeyAlgorithm(String jcaKeyAlgorithm) { - String algorithmUpper = jcaKeyAlgorithm.toUpperCase(Locale.US); - if (algorithmUpper.startsWith("HMAC")) { - String digestUpper = algorithmUpper.substring("HMAC".length()); - switch (digestUpper) { - case "MD5": - return KeymasterDefs.KM_DIGEST_MD5; - case "SHA1": - return KeymasterDefs.KM_DIGEST_SHA1; - case "SHA224": - return KeymasterDefs.KM_DIGEST_SHA_2_224; - case "SHA256": - return KeymasterDefs.KM_DIGEST_SHA_2_256; - case "SHA384": - return KeymasterDefs.KM_DIGEST_SHA_2_384; - case "SHA512": - return KeymasterDefs.KM_DIGEST_SHA_2_512; - default: - throw new IllegalArgumentException("Unsupported HMAC digest: " + digestUpper); - } - } else { - return -1; - } - } - - public static int getKeymasterDigestFromJcaDigestAlgorithm(String jcaDigestAlgorithm) { - if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-1")) { - return KeymasterDefs.KM_DIGEST_SHA1; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-224")) { - return KeymasterDefs.KM_DIGEST_SHA_2_224; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-256")) { - return KeymasterDefs.KM_DIGEST_SHA_2_256; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-384")) { - return KeymasterDefs.KM_DIGEST_SHA_2_384; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("SHA-512")) { - return KeymasterDefs.KM_DIGEST_SHA_2_512; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("NONE")) { - return KeymasterDefs.KM_DIGEST_NONE; - } else if (jcaDigestAlgorithm.equalsIgnoreCase("MD5")) { - return KeymasterDefs.KM_DIGEST_MD5; - } else { - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + jcaDigestAlgorithm); - } - } - - public static String getJcaDigestAlgorithmFromKeymasterDigest(int keymasterDigest) { - switch (keymasterDigest) { - case KeymasterDefs.KM_DIGEST_NONE: - return "NONE"; - case KeymasterDefs.KM_DIGEST_MD5: - return "MD5"; - case KeymasterDefs.KM_DIGEST_SHA1: - return "SHA-1"; - case KeymasterDefs.KM_DIGEST_SHA_2_224: - return "SHA-224"; - case KeymasterDefs.KM_DIGEST_SHA_2_256: - return "SHA-256"; - case KeymasterDefs.KM_DIGEST_SHA_2_384: - return "SHA-384"; - case KeymasterDefs.KM_DIGEST_SHA_2_512: - return "SHA-512"; - default: - throw new IllegalArgumentException( - "Unsupported digest algorithm: " + keymasterDigest); - } - } - - public static String[] getJcaDigestAlgorithmsFromKeymasterDigests( - Collection<Integer> keymasterDigests) { - if (keymasterDigests.isEmpty()) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterDigests.size()]; - int offset = 0; - for (int keymasterDigest : keymasterDigests) { - result[offset] = getJcaDigestAlgorithmFromKeymasterDigest(keymasterDigest); - offset++; - } - return result; - } - - public static int[] getKeymasterDigestsFromJcaDigestAlgorithms(String[] jcaDigestAlgorithms) { - if ((jcaDigestAlgorithms == null) || (jcaDigestAlgorithms.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaDigestAlgorithms.length]; - int offset = 0; - for (String jcaDigestAlgorithm : jcaDigestAlgorithms) { - result[offset] = getKeymasterDigestFromJcaDigestAlgorithm(jcaDigestAlgorithm); - offset++; - } - return result; - } - public static int getDigestOutputSizeBits(int keymasterDigest) { switch (keymasterDigest) { case KeymasterDefs.KM_DIGEST_NONE: @@ -200,60 +49,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterBlockModeFromJcaBlockMode(String jcaBlockMode) { - if ("ECB".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_ECB; - } else if ("CBC".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CBC; - } else if ("CTR".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_CTR; - } else if ("GCM".equalsIgnoreCase(jcaBlockMode)) { - return KeymasterDefs.KM_MODE_GCM; - } else { - throw new IllegalArgumentException("Unsupported block mode: " + jcaBlockMode); - } - } - - public static String getJcaBlockModeFromKeymasterBlockMode(int keymasterBlockMode) { - switch (keymasterBlockMode) { - case KeymasterDefs.KM_MODE_ECB: - return "ECB"; - case KeymasterDefs.KM_MODE_CBC: - return "CBC"; - case KeymasterDefs.KM_MODE_CTR: - return "CTR"; - case KeymasterDefs.KM_MODE_GCM: - return "GCM"; - default: - throw new IllegalArgumentException("Unsupported block mode: " + keymasterBlockMode); - } - } - - public static String[] getJcaBlockModesFromKeymasterBlockModes( - Collection<Integer> keymasterBlockModes) { - if ((keymasterBlockModes == null) || (keymasterBlockModes.isEmpty())) { - return EmptyArray.STRING; - } - String[] result = new String[keymasterBlockModes.size()]; - int offset = 0; - for (int keymasterBlockMode : keymasterBlockModes) { - result[offset] = getJcaBlockModeFromKeymasterBlockMode(keymasterBlockMode); - offset++; - } - return result; - } - - public static int[] getKeymasterBlockModesFromJcaBlockModes(String[] jcaBlockModes) { - if ((jcaBlockModes == null) || (jcaBlockModes.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaBlockModes.length]; - for (int i = 0; i < jcaBlockModes.length; i++) { - result[i] = getKeymasterBlockModeFromJcaBlockMode(jcaBlockModes[i]); - } - return result; - } - public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) { switch (keymasterBlockMode) { case KeymasterDefs.KM_MODE_ECB: @@ -267,82 +62,6 @@ public abstract class KeymasterUtils { } } - public static int getKeymasterPaddingFromJcaEncryptionPadding(String jcaPadding) { - if ("NoPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_NONE; - } else if ("PKCS7Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_PKCS7; - } else if ("PKCS1Padding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT; - } else if ("OEAPPadding".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_OAEP; - } else { - throw new IllegalArgumentException( - "Unsupported encryption padding scheme: " + jcaPadding); - } - } - - public static String getJcaEncryptionPaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_NONE: - return "NoPadding"; - case KeymasterDefs.KM_PAD_PKCS7: - return "PKCS7Padding"; - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT: - return "PKCS1Padding"; - case KeymasterDefs.KM_PAD_RSA_OAEP: - return "OEAPPadding"; - default: - throw new IllegalArgumentException( - "Unsupported encryption padding: " + keymasterPadding); - } - } - - public static int getKeymasterPaddingFromJcaSignaturePadding(String jcaPadding) { - if ("PKCS#1".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN; - } if ("PSS".equalsIgnoreCase(jcaPadding)) { - return KeymasterDefs.KM_PAD_RSA_PSS; - } else { - throw new IllegalArgumentException( - "Unsupported signature padding scheme: " + jcaPadding); - } - } - - public static String getJcaSignaturePaddingFromKeymasterPadding(int keymasterPadding) { - switch (keymasterPadding) { - case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN: - return "PKCS#1"; - case KeymasterDefs.KM_PAD_RSA_PSS: - return "PSS"; - default: - throw new IllegalArgumentException( - "Unsupported signature padding: " + keymasterPadding); - } - } - - public static int[] getKeymasterPaddingsFromJcaEncryptionPaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaEncryptionPadding(jcaPaddings[i]); - } - return result; - } - - public static int[] getKeymasterPaddingsFromJcaSignaturePaddings(String[] jcaPaddings) { - if ((jcaPaddings == null) || (jcaPaddings.length == 0)) { - return EmptyArray.INT; - } - int[] result = new int[jcaPaddings.length]; - for (int i = 0; i < jcaPaddings.length; i++) { - result[i] = getKeymasterPaddingFromJcaSignaturePadding(jcaPaddings[i]); - } - return result; - } - /** * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 19a5beb..2ae7b08 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -3336,6 +3336,30 @@ status_t ResTable::Theme::setTo(const Theme& other) return NO_ERROR; } +status_t ResTable::Theme::clear() +{ + if (kDebugTableTheme) { + ALOGI("Clearing theme %p...\n", this); + dumpToLog(); + } + + for (size_t i = 0; i < Res_MAXPACKAGE; i++) { + if (mPackages[i] != NULL) { + free_package(mPackages[i]); + mPackages[i] = NULL; + } + } + + mTypeSpecFlags = 0; + + if (kDebugTableTheme) { + ALOGI("Final theme:"); + dumpToLog(); + } + + return NO_ERROR; +} + ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue, uint32_t* outTypeSpecFlags) const { diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp index 882826e..2889d2f 100644 --- a/libs/hwui/AssetAtlas.cpp +++ b/libs/hwui/AssetAtlas.cpp @@ -112,9 +112,6 @@ private: Texture* const mDelegate; }; // struct DelegateTexture -/** - * TODO: This method does not take the rotation flag into account - */ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { const float width = float(mTexture->width); const float height = float(mTexture->height); @@ -128,7 +125,6 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { // pointers on 64 bit architectures. const int x = static_cast<int>(map[i++]); const int y = static_cast<int>(map[i++]); - bool rotated = map[i++] > 0; // Bitmaps should never be null, we're just extra paranoid if (!pixelRef) continue; @@ -142,7 +138,7 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { texture->width = pixelRef->info().width(); texture->height = pixelRef->info().height(); - Entry* entry = new Entry(pixelRef, x, y, rotated, texture, mapper, *this); + Entry* entry = new Entry(pixelRef, texture, mapper, *this); texture->uvMapper = &entry->uvMapper; mEntries.add(entry->pixelRef, entry); diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h index 17c5281..f1cd0b4 100644 --- a/libs/hwui/AssetAtlas.h +++ b/libs/hwui/AssetAtlas.h @@ -45,8 +45,8 @@ class Image; class AssetAtlas { public: /** - * Entry representing the position and rotation of a - * bitmap inside the atlas. + * Entry representing the texture and uvMapper of a PixelRef in the + * atlas */ class Entry { public: @@ -78,30 +78,15 @@ public: SkPixelRef* pixelRef; /** - * Location of the bitmap inside the atlas, in pixels. - */ - int x; - int y; - - /** - * If set, the bitmap is rotated 90 degrees (clockwise) - * inside the atlas. - */ - bool rotated; - - /** * Atlas this entry belongs to. */ const AssetAtlas& atlas; - Entry(SkPixelRef* pixelRef, int x, int y, bool rotated, - Texture* texture, const UvMapper& mapper, const AssetAtlas& atlas) + Entry(SkPixelRef* pixelRef, Texture* texture, const UvMapper& mapper, + const AssetAtlas& atlas) : texture(texture) , uvMapper(mapper) , pixelRef(pixelRef) - , x(x) - , y(y) - , rotated(rotated) , atlas(atlas) { } @@ -120,8 +105,7 @@ public: * Initializes the atlas with the specified buffer and * map. The buffer is a gralloc'd texture that will be * used as an EGLImage. The map is a list of SkBitmap* - * and their (x, y) positions as well as their rotation - * flags. + * and their (x, y) positions * * This method returns immediately if the atlas is already * initialized. To re-initialize the atlas, you must diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 4540bec..e679bff 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -41,10 +41,6 @@ void DisplayListData::cleanupResources() { ResourceCache& resourceCache = ResourceCache::getInstance(); resourceCache.lock(); - for (size_t i = 0; i < bitmapResources.size(); i++) { - resourceCache.decrementRefcountLocked(bitmapResources.itemAt(i)); - } - for (size_t i = 0; i < patchResources.size(); i++) { resourceCache.decrementRefcountLocked(patchResources.itemAt(i)); } @@ -59,7 +55,6 @@ void DisplayListData::cleanupResources() { delete path; } - bitmapResources.clear(); patchResources.clear(); pathResources.clear(); paints.clear(); diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h index 0064236..d997ef4 100644 --- a/libs/hwui/DisplayListCanvas.h +++ b/libs/hwui/DisplayListCanvas.h @@ -350,9 +350,10 @@ private: // correctly, such as creating the bitmap from scratch, drawing with it, changing its // contents, and drawing again. The only fix would be to always copy it the first time, // which doesn't seem worth the extra cycles for this unlikely case. - const SkBitmap* cachedBitmap = mResourceCache.insert(bitmap); - mDisplayListData->bitmapResources.add(cachedBitmap); - return cachedBitmap; + SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap); + alloc().autoDestroy(localBitmap); + mDisplayListData->bitmapResources.push_back(localBitmap); + return localBitmap; } inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) { diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 454fedc..75d8134 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -59,21 +59,6 @@ void ResourceCache::unlock() { mLock.unlock(); } -const SkBitmap* ResourceCache::insert(const SkBitmap& bitmapResource) { - Mutex::Autolock _l(mLock); - - BitmapKey bitmapKey(bitmapResource); - ssize_t index = mBitmapCache.indexOfKey(bitmapKey); - if (index == NAME_NOT_FOUND) { - SkBitmap* cachedBitmap = new SkBitmap(bitmapResource); - index = mBitmapCache.add(bitmapKey, cachedBitmap); - return cachedBitmap; - } - - mBitmapCache.keyAt(index).mRefCount++; - return mBitmapCache.valueAt(index); -} - void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) { Mutex::Autolock _l(mLock); incrementRefcountLocked(resource, resourceType); @@ -98,11 +83,6 @@ void ResourceCache::decrementRefcount(void* resource) { decrementRefcountLocked(resource); } -void ResourceCache::decrementRefcount(const SkBitmap* bitmapResource) { - Mutex::Autolock _l(mLock); - decrementRefcountLocked(bitmapResource); -} - void ResourceCache::decrementRefcount(const Res_png_9patch* patchResource) { decrementRefcount((void*) patchResource); } @@ -120,23 +100,6 @@ void ResourceCache::decrementRefcountLocked(void* resource) { } } -void ResourceCache::decrementRefcountLocked(const SkBitmap* bitmapResource) { - BitmapKey bitmapKey(*bitmapResource); - ssize_t index = mBitmapCache.indexOfKey(bitmapKey); - - LOG_ALWAYS_FATAL_IF(index == NAME_NOT_FOUND, - "Decrementing the reference of an untracked Bitmap"); - - const BitmapKey& cacheEntry = mBitmapCache.keyAt(index); - if (cacheEntry.mRefCount == 1) { - // delete the bitmap and remove it from the cache - delete mBitmapCache.valueAt(index); - mBitmapCache.removeItemsAt(index); - } else { - cacheEntry.mRefCount--; - } -} - void ResourceCache::decrementRefcountLocked(const Res_png_9patch* patchResource) { decrementRefcountLocked((void*) patchResource); } @@ -190,38 +153,5 @@ void ResourceCache::deleteResourceReferenceLocked(const void* resource, Resource delete ref; } -/////////////////////////////////////////////////////////////////////////////// -// Bitmap Key -/////////////////////////////////////////////////////////////////////////////// - -void BitmapKey::operator=(const BitmapKey& other) { - this->mRefCount = other.mRefCount; - this->mBitmapDimensions = other.mBitmapDimensions; - this->mPixelRefOrigin = other.mPixelRefOrigin; - this->mPixelRefStableID = other.mPixelRefStableID; -} - -bool BitmapKey::operator==(const BitmapKey& other) const { - return mPixelRefStableID == other.mPixelRefStableID && - mPixelRefOrigin == other.mPixelRefOrigin && - mBitmapDimensions == other.mBitmapDimensions; -} - -bool BitmapKey::operator<(const BitmapKey& other) const { - if (mPixelRefStableID != other.mPixelRefStableID) { - return mPixelRefStableID < other.mPixelRefStableID; - } - if (mPixelRefOrigin.x() != other.mPixelRefOrigin.x()) { - return mPixelRefOrigin.x() < other.mPixelRefOrigin.x(); - } - if (mPixelRefOrigin.y() != other.mPixelRefOrigin.y()) { - return mPixelRefOrigin.y() < other.mPixelRefOrigin.y(); - } - if (mBitmapDimensions.width() != other.mBitmapDimensions.width()) { - return mBitmapDimensions.width() < other.mBitmapDimensions.width(); - } - return mBitmapDimensions.height() < other.mBitmapDimensions.height(); -} - }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h index 6c483fa..4583c8d 100644 --- a/libs/hwui/ResourceCache.h +++ b/libs/hwui/ResourceCache.h @@ -51,37 +51,6 @@ public: ResourceType resourceType; }; -class BitmapKey { -public: - BitmapKey(const SkBitmap& bitmap) - : mRefCount(1) - , mBitmapDimensions(bitmap.dimensions()) - , mPixelRefOrigin(bitmap.pixelRefOrigin()) - , mPixelRefStableID(bitmap.pixelRef()->getStableID()) { } - - void operator=(const BitmapKey& other); - bool operator==(const BitmapKey& other) const; - bool operator<(const BitmapKey& other) const; - -private: - // This constructor is only used by the KeyedVector implementation - BitmapKey() - : mRefCount(-1) - , mBitmapDimensions(SkISize::Make(0,0)) - , mPixelRefOrigin(SkIPoint::Make(0,0)) - , mPixelRefStableID(0) { } - - // reference count of all HWUI object using this bitmap - mutable int mRefCount; - - SkISize mBitmapDimensions; - SkIPoint mPixelRefOrigin; - uint32_t mPixelRefStableID; - - friend class ResourceCache; - friend struct android::key_value_pair_t<BitmapKey, SkBitmap*>; -}; - class ANDROID_API ResourceCache: public Singleton<ResourceCache> { ResourceCache(); ~ResourceCache(); @@ -97,18 +66,10 @@ public: void lock(); void unlock(); - /** - * The cache stores a copy of the provided resource or refs an existing resource - * if the bitmap has previously been inserted and returns the cached copy. - */ - const SkBitmap* insert(const SkBitmap& resource); - void incrementRefcount(const Res_png_9patch* resource); - void decrementRefcount(const SkBitmap* resource); void decrementRefcount(const Res_png_9patch* resource); - void decrementRefcountLocked(const SkBitmap* resource); void decrementRefcountLocked(const Res_png_9patch* resource); void destructor(Res_png_9patch* resource); @@ -134,7 +95,6 @@ private: mutable Mutex mLock; KeyedVector<const void*, ResourceReference*>* mCache; - KeyedVector<BitmapKey, SkBitmap*> mBitmapCache; }; }; // namespace uirenderer diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index c643e1d..17e47b9 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -353,7 +353,6 @@ CREATE_BRIDGE2(overrideProperty, const char* name, const char* value) { } void RenderProxy::overrideProperty(const char* name, const char* value) { - RenderThread& thread = RenderThread::getInstance(); SETUP_TASK(overrideProperty); args->name = name; args->value = value; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 6ec10c7..b3c1b4d 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -2824,6 +2824,13 @@ public class MediaPlayer implements SubtitleController.Listener mSubtitleController.selectDefaultTrack(); } break; + case MEDIA_INFO_BUFFERING_START: + case MEDIA_INFO_BUFFERING_END: + TimeProvider timeProvider = mTimeProvider; + if (timeProvider != null) { + timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START); + } + break; } if (mOnInfoListener != null) { @@ -3362,6 +3369,7 @@ public class MediaPlayer implements SubtitleController.Listener private MediaPlayer mPlayer; private boolean mPaused = true; private boolean mStopped = true; + private boolean mBuffering; private long mLastReportedTime; private long mTimeAdjustment; // since we are expecting only a handful listeners per stream, there is @@ -3455,12 +3463,22 @@ public class MediaPlayer implements SubtitleController.Listener } /** @hide */ + public void onBuffering(boolean buffering) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "onBuffering: " + buffering); + mBuffering = buffering; + scheduleNotification(REFRESH_AND_NOTIFY_TIME, 0 /* delay */); + } + } + + /** @hide */ public void onStopped() { synchronized(this) { if (DEBUG) Log.d(TAG, "onStopped"); mPaused = true; mStopped = true; mSeeking = false; + mBuffering = false; scheduleNotification(NOTIFY_STOP, 0 /* delay */); } } @@ -3481,6 +3499,7 @@ public class MediaPlayer implements SubtitleController.Listener synchronized(this) { mStopped = false; mSeeking = true; + mBuffering = false; scheduleNotification(NOTIFY_SEEK, 0 /* delay */); } } @@ -3683,7 +3702,7 @@ public class MediaPlayer implements SubtitleController.Listener nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) { try { mLastTimeUs = mPlayer.getCurrentPosition() * 1000L; - mPaused = !mPlayer.isPlaying(); + mPaused = !mPlayer.isPlaying() || mBuffering; if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); } catch (IllegalStateException e) { if (mPausing) { diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java index 35374ed..57607e9 100644 --- a/media/java/android/media/midi/MidiDeviceInfo.java +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -298,6 +298,9 @@ public final class MidiDeviceInfo implements Parcelable { @Override public String toString() { + // This is a hack to force the mProperties Bundle to unparcel so we can + // print all the names and values. + mProperties.getString(PROPERTY_NAME); return ("MidiDeviceInfo[mType=" + mType + ",mInputPortCount=" + mInputPortCount + ",mOutputPortCount=" + mOutputPortCount + diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java index 7522dcf..d4abeff 100644 --- a/media/java/android/media/midi/MidiDeviceStatus.java +++ b/media/java/android/media/midi/MidiDeviceStatus.java @@ -89,10 +89,9 @@ public final class MidiDeviceStatus implements Parcelable { @Override public String toString() { - StringBuilder builder = new StringBuilder(mDeviceInfo.toString()); int inputPortCount = mDeviceInfo.getInputPortCount(); int outputPortCount = mDeviceInfo.getOutputPortCount(); - builder.append(" mInputPortOpen=["); + StringBuilder builder = new StringBuilder("mInputPortOpen=["); for (int i = 0; i < inputPortCount; i++) { builder.append(mInputPortOpen[i]); if (i < inputPortCount -1) { diff --git a/media/jni/android_media_MediaSync.h b/media/jni/android_media_MediaSync.h index fa5e5e0..5823057 100644 --- a/media/jni/android_media_MediaSync.h +++ b/media/jni/android_media_MediaSync.h @@ -26,7 +26,7 @@ namespace android { struct AudioPlaybackRate; class AudioTrack; -struct IGraphicBufferProducer; +class IGraphicBufferProducer; struct MediaClock; class MediaSync; diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp index 4e7c6be..26b41e8 100644 --- a/native/android/sensor.cpp +++ b/native/android/sensor.cpp @@ -35,9 +35,27 @@ using android::Sensor; using android::SensorManager; using android::SensorEventQueue; using android::String8; +using android::String16; /*****************************************************************************/ +android::Mutex android::SensorManager::sLock; +std::map<String16, SensorManager*> android::SensorManager::sPackageInstances; + +ASensorManager* ASensorManager_getInstance() +{ + return ASensorManager_getInstanceForPackage(NULL); +} + +ASensorManager* ASensorManager_getInstanceForPackage(const char* packageName) +{ + if (packageName) { + return &SensorManager::getInstanceForPackage(String16(packageName)); + } else { + return &SensorManager::getInstanceForPackage(String16()); + } +} + int ASensorManager_getSensorList(ASensorManager* manager, ASensorList* list) { diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp index 0521833..6d2de98 100644 --- a/native/graphics/jni/bitmap.cpp +++ b/native/graphics/jni/bitmap.cpp @@ -62,7 +62,7 @@ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } - SkPixelRef* pixelRef = GraphicsJNI::getSkPixelRef(env, jbitmap); + SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap); if (!pixelRef) { return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; } @@ -71,9 +71,9 @@ int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) { void* addr = pixelRef->pixels(); if (NULL == addr) { pixelRef->unlockPixels(); + pixelRef->unref(); return ANDROID_BITMAP_RESULT_ALLOCATION_FAILED; } - pixelRef->ref(); if (addrPtr) { *addrPtr = addr; @@ -86,7 +86,7 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } - SkPixelRef* pixelRef = GraphicsJNI::getSkPixelRef(env, jbitmap); + SkPixelRef* pixelRef = GraphicsJNI::refSkPixelRef(env, jbitmap); if (!pixelRef) { return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; } @@ -98,6 +98,12 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { pixelRef->notifyPixelsChanged(); pixelRef->unlockPixels(); + // Awkward in that we need to double-unref as the call to get the SkPixelRef + // did a ref(), so we need to unref() for the local ref and for the previous + // AndroidBitmap_lockPixels(). However this keeps GraphicsJNI a bit safer + // if others start using it without knowing about android::Bitmap's "fun" + // ref counting mechanism(s). + pixelRef->unref(); pixelRef->unref(); return ANDROID_BITMAP_RESULT_SUCCESS; diff --git a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml index a11bed0..2f0a411 100644 --- a/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml +++ b/packages/CaptivePortalLogin/res/layout/activity_captive_portal_login.xml @@ -10,6 +10,13 @@ android:layout_height="match_parent" android:orientation="vertical" > + <TextView + android:id="@+id/url_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="20sp" + android:singleLine="true" /> + <ProgressBar android:id="@+id/progress_bar" android:layout_width="match_parent" diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index b86fc4b..1019e6c 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -35,13 +35,13 @@ import android.util.ArrayMap; import android.util.Log; import android.view.Menu; import android.view.MenuItem; -import android.view.View; import android.webkit.SslErrorHandler; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ProgressBar; +import android.widget.TextView; import java.io.IOException; import java.net.HttpURLConnection; @@ -221,6 +221,7 @@ public class CaptivePortalLoginActivity extends Activity { } private class MyWebViewClient extends WebViewClient { + private static final String INTERNAL_ASSETS = "file:///android_asset/"; private boolean firstPageLoad = true; @Override @@ -240,6 +241,12 @@ public class CaptivePortalLoginActivity extends Activity { view.loadUrl(mURL.toString()); return; } + // For internally generated pages, leave URL bar listing prior URL as this is the URL + // the page refers to. + if (!url.startsWith(INTERNAL_ASSETS)) { + final TextView myUrlBar = (TextView) findViewById(R.id.url_bar); + myUrlBar.setText(url); + } testForCaptivePortal(); } @@ -252,17 +259,15 @@ public class CaptivePortalLoginActivity extends Activity { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { Log.w(TAG, "SSL error; displaying broken lock icon."); - view.loadDataWithBaseURL("file:///android_asset/", SSL_ERROR_HTML, "text/HTML", - "UTF-8", null); + view.loadDataWithBaseURL(INTERNAL_ASSETS, SSL_ERROR_HTML, "text/HTML", "UTF-8", null); } } private class MyWebChromeClient extends WebChromeClient { @Override public void onProgressChanged(WebView view, int newProgress) { - ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar); + final ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar); myProgressBar.setProgress(newProgress); - myProgressBar.setVisibility(newProgress == 100 ? View.GONE : View.VISIBLE); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java index b5eda90..1c4b963 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import android.annotation.NonNull; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; @@ -25,12 +26,15 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.ViewHierarchyEncoder; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ViewFlipper; import com.android.internal.widget.LockPatternUtils; +import java.lang.Override; + /** * Subclass of the current view flipper that allows us to overload dispatchTouchEvent() so * we can emulate {@link WindowManager.LayoutParams#FLAG_SLIPPERY} within a view hierarchy. @@ -268,5 +272,14 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard R.styleable.KeyguardSecurityViewFlipper_Layout_layout_maxHeight, 0); a.recycle(); } + + /** @hide */ + @Override + protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { + super.encodeProperties(encoder); + + encoder.addProperty("layout:maxWidth", maxWidth); + encoder.addProperty("layout:maxHeight", maxHeight); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index b5c1ca8..f352849 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -33,6 +33,7 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.qs.QSTile.DetailAdapter; @@ -182,8 +183,11 @@ public class QSPanel extends ViewGroup { public void setExpanded(boolean expanded) { if (mExpanded == expanded) return; mExpanded = expanded; + MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, mExpanded); if (!mExpanded) { closeDetail(); + } else { + logTiles(); } } @@ -365,9 +369,11 @@ public class QSPanel extends ViewGroup { mDetailContent.removeAllViews(); mDetail.bringToFront(); mDetailContent.addView(r.detailView); + MetricsLogger.visible(mContext, detailAdapter.getMetricsCategory()); setDetailRecord(r); listener = mHideGridContentWhenDone; } else { + MetricsLogger.hidden(mContext, mDetailRecord.detailAdapter.getMetricsCategory()); mClosingDetail = true; setGridContentVisibility(true); listener = mTeardownDetailWhenDone; @@ -387,9 +393,21 @@ public class QSPanel extends ViewGroup { } } mBrightnessView.setVisibility(newVis); + if (mGridContentVisible != visible) { + MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, newVis); + } mGridContentVisible = visible; } + private void logTiles() { + for (int i = 0; i < mRecords.size(); i++) { + TileRecord tileRecord = mRecords.get(i); + if (tileRecord.tile.getState().visible) { + MetricsLogger.visible(mContext, tileRecord.tile.getMetricsCategory()); + } + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int width = MeasureSpec.getSize(widthMeasureSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index b9574dc..452fd44 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -29,6 +29,7 @@ import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.qs.QSTile.State; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; @@ -66,9 +67,17 @@ public abstract class QSTile<TState extends State> implements Listenable { private boolean mAnnounceNextStateChange; abstract protected TState newTileState(); - abstract protected void handleClick(); abstract protected void handleUpdateState(TState state, Object arg); + /** + * Declare the category of this tile. + * + * Categories are defined in {@link com.android.internal.logging.MetricsLogger} + * or if there is no relevant existing category you may define one in + * {@link com.android.systemui.qs.QSTile}. + */ + abstract public int getMetricsCategory(); + protected QSTile(Host host) { mHost = host; mContext = host.getContext(); @@ -97,6 +106,7 @@ public abstract class QSTile<TState extends State> implements Listenable { View createDetailView(Context context, View convertView, ViewGroup parent); Intent getSettingsIntent(); void setToggleState(boolean state); + int getMetricsCategory(); } // safe to call from any thread @@ -160,6 +170,10 @@ public abstract class QSTile<TState extends State> implements Listenable { handleRefreshState(null); } + protected void handleClick() { + MetricsLogger.action(mContext, getMetricsCategory(), getMetricsPackage()); + }; + protected void handleSecondaryClick() { // optional } @@ -168,6 +182,10 @@ public abstract class QSTile<TState extends State> implements Listenable { // optional } + protected String getMetricsPackage() { + return ""; + } + protected void handleRefreshState(Object arg) { handleUpdateState(mTmpState, arg); final boolean changed = mTmpState.copyTo(mState); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 2bc31fc..6744154 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -23,6 +23,7 @@ import android.content.IntentFilter; import android.net.ConnectivityManager; import android.provider.Settings.Global; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.GlobalSetting; import com.android.systemui.qs.QSTile; @@ -55,6 +56,7 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> { @Override public void handleClick() { + super.handleClick(); setEnabled(!mState.value); mEnable.setAllowAnimation(true); mDisable.setAllowAnimation(true); @@ -85,6 +87,11 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_AIRPLANEMODE; + } + + @Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_on); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index b42b5f6..8eb624f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -25,6 +25,7 @@ import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.R; import com.android.systemui.qs.QSDetailItems; @@ -74,6 +75,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { + super.handleClick(); final boolean isEnabled = (Boolean)mState.value; mController.setBluetoothEnabled(!isEnabled); } @@ -132,6 +134,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_BLUETOOTH; + } + + @Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString(R.string.accessibility_quick_settings_bluetooth_changed_on); @@ -182,6 +189,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_BLUETOOTH_DETAILS; + } + + @Override public View createDetailView(Context context, View convertView, ViewGroup parent) { mItems = QSDetailItems.convertOrInflate(context, convertView, parent); mItems.setTagSuffix("Bluetooth"); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 5bf6fb5..a3d7bcc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -24,6 +24,7 @@ import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.QSDetailItems; import com.android.systemui.qs.QSDetailItems.Item; @@ -85,6 +86,7 @@ public class CastTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { + super.handleClick(); showDetail(true); } @@ -113,6 +115,11 @@ public class CastTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_CAST; + } + + @Override protected String composeChangeAnnouncement() { if (!mState.value) { // We only announce when it's turned off to avoid vocal overflow. @@ -164,6 +171,11 @@ public class CastTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_CAST_DETAILS; + } + + @Override public View createDetailView(Context context, View convertView, ViewGroup parent) { mItems = QSDetailItems.convertOrInflate(context, convertView, parent); mItems.setTagSuffix("Cast"); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 30f92b9..0026141 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -24,6 +24,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.QSTileView; @@ -75,6 +76,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> { @Override protected void handleClick() { + super.handleClick(); if (mDataController.isMobileDataSupported()) { showDetail(true); } else { @@ -118,6 +120,11 @@ public class CellularTile extends QSTile<QSTile.SignalState> { state.label); } + @Override + public int getMetricsCategory() { + return MetricsLogger.QS_CELLULAR; + } + // Remove the period from the network name public static String removeTrailingPeriod(String string) { if (string == null) return null; @@ -227,6 +234,11 @@ public class CellularTile extends QSTile<QSTile.SignalState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_DATAUSAGEDETAIL; + } + + @Override public View createDetailView(Context context, View convertView, ViewGroup parent) { final DataUsageDetailView v = (DataUsageDetailView) (convertView != null ? convertView diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index 4a33f55..6fa094e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles; import android.provider.Settings.Secure; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.qs.QSTile; @@ -86,6 +87,7 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { + super.handleClick(); mSetting.setValue(mState.value ? 0 : 1); mEnable.setAllowAnimation(true); mDisable.setAllowAnimation(true); @@ -115,6 +117,11 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_COLORINVERSION; + } + + @Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 5145bc7..e708a72 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -29,6 +29,7 @@ import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.qs.QSTile; @@ -88,6 +89,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> { @Override public void handleClick() { + super.handleClick(); if (mState.value) { mController.setZen(Global.ZEN_MODE_OFF, null, TAG); } else { @@ -135,6 +137,11 @@ public class DndTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_DND; + } + + @Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on); @@ -209,6 +216,11 @@ public class DndTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_DND_DETAILS; + } + + @Override public View createDetailView(Context context, View convertView, ViewGroup parent) { final ZenModePanel zmp = convertView != null ? (ZenModePanel) convertView : (ZenModePanel) LayoutInflater.from(context).inflate( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index cb78deb..a1f3cde 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles; import android.app.ActivityManager; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.statusbar.policy.FlashlightController; @@ -59,6 +60,7 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements @Override protected void handleClick() { + super.handleClick(); if (ActivityManager.isUserAMonkey()) { return; } @@ -84,6 +86,11 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_FLASHLIGHT; + } + + @Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString(R.string.accessibility_quick_settings_flashlight_changed_on); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 6063f80..b864ff4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.qs.UsageTracker; @@ -68,6 +69,7 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { + super.handleClick(); final boolean isEnabled = (Boolean) mState.value; mController.setHotspotEnabled(!isEnabled); mEnable.setAllowAnimation(true); @@ -97,6 +99,11 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_HOTSPOT; + } + + @Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString(R.string.accessibility_quick_settings_hotspot_changed_on); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java index 2736530..20b5f04 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java @@ -29,6 +29,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.qs.QSTile; import java.util.Arrays; @@ -42,6 +43,7 @@ public class IntentTile extends QSTile<QSTile.State> { private PendingIntent mOnLongClick; private String mOnLongClickUri; private int mCurrentUserId; + private String mIntentPackage; private IntentTile(Host host, String action) { super(host); @@ -82,6 +84,7 @@ public class IntentTile extends QSTile<QSTile.State> { @Override protected void handleClick() { + super.handleClick(); sendIntent("click", mOnClick, mOnClickUri); } @@ -133,6 +136,17 @@ public class IntentTile extends QSTile<QSTile.State> { mOnClickUri = intent.getStringExtra("onClickUri"); mOnLongClick = intent.getParcelableExtra("onLongClick"); mOnLongClickUri = intent.getStringExtra("onLongClickUri"); + mIntentPackage = intent.getStringExtra("package"); + } + + @Override + public int getMetricsCategory() { + return MetricsLogger.QS_INTENT; + } + + @Override + protected String getMetricsPackage() { + return mIntentPackage == null ? "" : mIntentPackage; } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index 11ec722..ab22ada 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.statusbar.policy.KeyguardMonitor; @@ -58,6 +59,7 @@ public class LocationTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { + super.handleClick(); final boolean wasEnabled = (Boolean) mState.value; mController.setLocationEnabled(!wasEnabled); mEnable.setAllowAnimation(true); @@ -87,6 +89,11 @@ public class LocationTile extends QSTile<QSTile.BooleanState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_LOCATION; + } + + @Override protected String composeChangeAnnouncement() { if (mState.value) { return mContext.getString(R.string.accessibility_quick_settings_location_changed_on); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index f46b9a6..7e3fe76 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles; import android.content.res.Configuration; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.statusbar.policy.RotationLockController; @@ -58,6 +59,7 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { + super.handleClick(); if (mController == null) return; final boolean newState = !mState.value; mController.setRotationLocked(newState); @@ -92,6 +94,11 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> { R.string.accessibility_rotation_lock_off); } + @Override + public int getMetricsCategory() { + return MetricsLogger.QS_ROTATIONLOCK; + } + /** * Get the correct accessibility string based on the state * diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index d589366..228c293 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -26,6 +26,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.internal.logging.MetricsLogger; import com.android.settingslib.wifi.AccessPoint; import com.android.systemui.R; import com.android.systemui.qs.QSDetailItems; @@ -93,6 +94,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> { @Override protected void handleClick() { + super.handleClick(); mState.copyTo(mStateBeforeClick); mController.setWifiEnabled(!mState.enabled); } @@ -159,6 +161,11 @@ public class WifiTile extends QSTile<QSTile.SignalState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_WIFI; + } + + @Override protected boolean shouldAnnouncementBeDelayed() { return mStateBeforeClick.enabled == mState.enabled; } @@ -274,6 +281,11 @@ public class WifiTile extends QSTile<QSTile.SignalState> { } @Override + public int getMetricsCategory() { + return MetricsLogger.QS_WIFI_DETAILS; + } + + @Override public View createDetailView(Context context, View convertView, ViewGroup parent) { if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null)); mAccessPoints = null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 4542054..80fdd28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -157,7 +157,8 @@ public class CommandQueue extends IStatusBar.Stub { public void setSystemUiVisibility(int vis, int mask) { synchronized (mList) { - mHandler.removeMessages(MSG_SET_SYSTEMUI_VISIBILITY); + // Don't coalesce these, since it might have one time flags set such as + // STATUS_BAR_UNHIDE which might get lost. mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 351977b..471196c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -529,6 +529,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, = new HashMap<>(); private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>(); private RankingMap mLatestRankingMap; + private boolean mNoAnimationOnNextBarModeChange; @Override public void start() { @@ -1709,6 +1710,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, * State is one or more of the DISABLE constants from StatusBarManager. */ public void disable(int state1, int state2, boolean animate) { + animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN; mDisabledUnmodified1 = state1; mDisabledUnmodified2 = state2; state1 = adjustDisableFlags(state1); @@ -1868,6 +1870,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { if (inPinnedMode) { mStatusBarWindowManager.setHeadsUpShowing(true); + mStatusBarWindowManager.setForceStatusBarVisible(true); } else { Runnable endRunnable = new Runnable() { @Override @@ -2132,6 +2135,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Shrink the window to the size of the status bar only mStatusBarWindowManager.setStatusBarExpanded(false); + mStatusBarWindowManager.setForceStatusBarVisible(false); mStatusBarView.setFocusable(true); // Close any "App info" popups that might have snuck on-screen @@ -2272,6 +2276,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, setAreThereNotifications(); } + // ready to unhide + if ((vis & View.STATUS_BAR_UNHIDE) != 0) { + mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE; + mNoAnimationOnNextBarModeChange = true; + } + // update status bar mode final int sbMode = computeBarMode(oldVal, newVal, mStatusBarView.getBarTransitions(), View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_TRANSLUCENT); @@ -2303,10 +2313,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - // ready to unhide - if ((vis & View.STATUS_BAR_UNHIDE) != 0) { - mSystemUiVisibility &= ~View.STATUS_BAR_UNHIDE; - } if ((vis & View.NAVIGATION_BAR_UNHIDE) != 0) { mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE; } @@ -2351,17 +2357,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private void checkBarModes() { if (mDemoMode) return; - checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarView.getBarTransitions()); + checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarView.getBarTransitions(), + mNoAnimationOnNextBarModeChange); if (mNavigationBarView != null) { checkBarMode(mNavigationBarMode, - mNavigationBarWindowState, mNavigationBarView.getBarTransitions()); + mNavigationBarWindowState, mNavigationBarView.getBarTransitions(), + mNoAnimationOnNextBarModeChange); } + mNoAnimationOnNextBarModeChange = false; } - private void checkBarMode(int mode, int windowState, BarTransitions transitions) { + private void checkBarMode(int mode, int windowState, BarTransitions transitions, + boolean noAnimation) { final boolean powerSave = mBatteryController.isPowerSave(); - final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN - && !powerSave; + final boolean anim = !noAnimation && (mScreenOn == null || mScreenOn) + && windowState != WINDOW_STATE_HIDDEN && !powerSave; if (powerSave && getBarState() == StatusBarState.SHADE) { mode = MODE_WARNING; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index 84a9f64..e7e4384 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -166,6 +166,7 @@ public class StatusBarWindowManager { private void apply(State state) { applyKeyguardFlags(state); + applyForceStatusBarVisibleFlag(state); applyFocusableFlag(state); adjustScreenOrientation(state); applyHeight(state); @@ -178,6 +179,16 @@ public class StatusBarWindowManager { } } + private void applyForceStatusBarVisibleFlag(State state) { + if (state.forceStatusBarVisible) { + mLpChanged.privateFlags |= WindowManager + .LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT; + } else { + mLpChanged.privateFlags &= ~WindowManager + .LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT; + } + } + private void applyModalFlag(State state) { if (state.headsUpShowing) { mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; @@ -240,6 +251,11 @@ public class StatusBarWindowManager { apply(mCurrentState); } + public void setForceStatusBarVisible(boolean forceStatusBarVisible) { + mCurrentState.forceStatusBarVisible = forceStatusBarVisible; + apply(mCurrentState); + } + private static class State { boolean keyguardShowing; boolean keyguardOccluded; @@ -250,6 +266,7 @@ public class StatusBarWindowManager { boolean keyguardFadingAway; boolean qsExpanded; boolean headsUpShowing; + boolean forceStatusBarVisible; /** * The {@link BaseStatusBar} state from the status bar. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 194bcfa..ad27c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -43,6 +43,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import com.android.internal.logging.MetricsLogger; import com.android.internal.util.UserIcons; import com.android.systemui.BitmapHelper; import com.android.systemui.GuestResumeSessionReceiver; @@ -548,6 +549,11 @@ public class UserSwitcherController { @Override public void setToggleState(boolean state) { } + + @Override + public int getMetricsCategory() { + return MetricsLogger.QS_USERDETAIL; + } }; private final KeyguardMonitor.Callback mCallback = new KeyguardMonitor.Callback() { diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index 58d0fce..a49fb76 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -1857,7 +1857,7 @@ nScriptForEach(JNIEnv *_env, jobject _this, jlong con, jlong script, jint slot, jintArray limits) { if (kLogApi) { - ALOGD("nScriptForEach, con(%p), s(%p), slot(%i) ains(%p) aout(%lli)", (RsContext)con, (void *)script, slot, ains, aout); + ALOGD("nScriptForEach, con(%p), s(%p), slot(%i) ains(%p) aout(%" PRId64 ")", (RsContext)con, (void *)script, slot, ains, aout); } jint in_len = 0; diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java index 26f4232..ebc810f 100644 --- a/services/core/java/com/android/server/AssetAtlasService.java +++ b/services/core/java/com/android/server/AssetAtlasService.java @@ -119,7 +119,6 @@ public class AssetAtlasService extends IAssetAtlas.Stub { // long0: SkBitmap*, the native bitmap object // long1: x position // long2: y position - // long3: rotated, 1 if the bitmap must be rotated, 0 otherwise private long[] mAtlasMap; /** @@ -236,7 +235,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub { /** * Renders a list of bitmaps into the atlas. The position of each bitmap * was decided by the packing algorithm and will be honored by this - * method. If need be this method will also rotate bitmaps. + * method. * * @param buffer The buffer to render the atlas entries into * @param atlas The atlas to pack the bitmaps into @@ -280,16 +279,11 @@ public class AssetAtlasService extends IAssetAtlas.Stub { canvas.save(); canvas.translate(entry.x, entry.y); - if (entry.rotated) { - canvas.translate(bitmap.getHeight(), 0.0f); - canvas.rotate(90.0f); - } canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); canvas.restore(); atlasMap[mapIndex++] = bitmap.refSkPixelRef(); atlasMap[mapIndex++] = entry.x; atlasMap[mapIndex++] = entry.y; - atlasMap[mapIndex++] = entry.rotated ? 1 : 0; } } diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index e434f39..8c12060 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -214,6 +214,10 @@ final class Constants { // values which denotes the device type in HDMI Spec 1.4. static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type"; + // TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event. + // True by default. + static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug"; + // Set to false to allow playback device to go to suspend mode even // when it's an active source. True by default. static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake"; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 89ffe45..fd3364a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -34,6 +34,9 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { private static final String TAG = "HdmiCecLocalDevicePlayback"; + private static final boolean WAKE_ON_HOTPLUG = + SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true); + private boolean mIsActiveSource = false; // Used to keep the device awake while it is the active source. For devices that @@ -130,7 +133,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { assertRunOnServiceThread(); mCecMessageCache.flushAll(); // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3. - if (connected && mService.isPowerStandbyOrTransient()) { + if (WAKE_ON_HOTPLUG && connected && mService.isPowerStandbyOrTransient()) { mService.wakeUp(); } if (!connected) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f30a5674..a120c1f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -11853,6 +11853,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { if (deletedPs != null) { if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { + clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL); if (outInfo != null) { mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName); outInfo.removedAppId = mSettings.removePackageLPw(packageName); @@ -11883,7 +11884,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL); - clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL); } // make sure to preserve per-user disabled state if this removal was just // a downgrade of a system app to the factory package @@ -12744,13 +12744,16 @@ public class PackageManagerService extends IPackageManager.Stub { /** This method takes a specific user id as well as UserHandle.USER_ALL. */ void clearIntentFilterVerificationsLPw(String packageName, int userId) { if (userId == UserHandle.USER_ALL) { - mSettings.removeIntentFilterVerificationLPw(packageName, sUserManager.getUserIds()); - for (int oneUserId : sUserManager.getUserIds()) { - scheduleWritePackageRestrictionsLocked(oneUserId); + if (mSettings.removeIntentFilterVerificationLPw(packageName, + sUserManager.getUserIds())) { + for (int oneUserId : sUserManager.getUserIds()) { + scheduleWritePackageRestrictionsLocked(oneUserId); + } } } else { - mSettings.removeIntentFilterVerificationLPw(packageName, userId); - scheduleWritePackageRestrictionsLocked(userId); + if (mSettings.removeIntentFilterVerificationLPw(packageName, userId)) { + scheduleWritePackageRestrictionsLocked(userId); + } } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index d476bfde..fd70ce1 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -1067,19 +1067,22 @@ final class Settings { return result; } - void removeIntentFilterVerificationLPw(String packageName, int userId) { + boolean removeIntentFilterVerificationLPw(String packageName, int userId) { PackageSetting ps = mPackages.get(packageName); if (ps == null) { Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName); - return; + return false; } ps.clearDomainVerificationStatusForUser(userId); + return true; } - void removeIntentFilterVerificationLPw(String packageName, int[] userIds) { + boolean removeIntentFilterVerificationLPw(String packageName, int[] userIds) { + boolean result = false; for (int userId : userIds) { - removeIntentFilterVerificationLPw(packageName, userId); + result |= removeIntentFilterVerificationLPw(packageName, userId); } + return result; } boolean setDefaultBrowserPackageNameLPr(String packageName, int userId) { diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java index e972ec7..5877b3e 100644 --- a/services/core/java/com/android/server/policy/BarController.java +++ b/services/core/java/com/android/server/policy/BarController.java @@ -58,6 +58,9 @@ public class BarController { private int mTransientBarState; private boolean mPendingShow; private long mLastTranslucent; + private boolean mShowTransparent; + private boolean mSetUnHideFlagWhenNextTransparent; + private boolean mNoAnimationOnNextShow; public BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag, int statusBarManagerId, int translucentWmFlag) { @@ -74,6 +77,14 @@ public class BarController { mWin = win; } + public void setShowTransparent(boolean transparent) { + if (transparent != mShowTransparent) { + mShowTransparent = transparent; + mSetUnHideFlagWhenNextTransparent = transparent; + mNoAnimationOnNextShow = true; + } + } + public void showTransient() { if (mWin != null) { setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED); @@ -135,7 +146,9 @@ public class BarController { } final boolean wasVis = mWin.isVisibleLw(); final boolean wasAnim = mWin.isAnimatingLw(); - final boolean change = show ? mWin.showLw(true) : mWin.hideLw(true); + final boolean change = show ? mWin.showLw(!mNoAnimationOnNextShow) + : mWin.hideLw(!mNoAnimationOnNextShow); + mNoAnimationOnNextShow = false; final int state = computeStateLw(wasVis, wasAnim, mWin, change); final boolean stateChanged = updateStateLw(state); return change || stateChanged; @@ -233,6 +246,13 @@ public class BarController { setTransientBarState(TRANSIENT_BAR_NONE); // request denied } } + if (mShowTransparent) { + vis |= View.SYSTEM_UI_TRANSPARENT; + if (mSetUnHideFlagWhenNextTransparent) { + vis |= mUnhideFlag; + mSetUnHideFlagWhenNextTransparent = false; + } + } if (mTransientBarState != TRANSIENT_BAR_NONE) { vis |= mTransientFlag; // ignore clear requests until transition completes vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; // never show transient bars in low profile diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java index b431b33..3cee927 100644 --- a/services/core/java/com/android/server/policy/GlobalActions.java +++ b/services/core/java/com/android/server/policy/GlobalActions.java @@ -1138,7 +1138,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public GlobalActionsDialog(Context context, AlertParams params) { super(context, getDialogTheme(context)); - mContext = context; + mContext = getContext(); mAlert = new AlertController(mContext, this, getWindow()); mAdapter = (MyAdapter) params.mAdapter; mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index dbd3676..185ef03 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -476,6 +476,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mTopIsFullscreen; boolean mForceStatusBar; boolean mForceStatusBarFromKeyguard; + private boolean mForceStatusBarTransparent; boolean mHideLockScreen; boolean mForcingShowNavBar; int mForcingShowNavBarLayer; @@ -4129,6 +4130,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mAppsThatDismissKeyguard.clear(); mForceStatusBar = false; mForceStatusBarFromKeyguard = false; + mForceStatusBarTransparent = false; mForcingShowNavBar = false; mForcingShowNavBarLayer = -1; @@ -4154,8 +4156,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { mForcingShowNavBar = true; mForcingShowNavBarLayer = win.getSurfaceLayer(); } - if (attrs.type == TYPE_STATUS_BAR && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { - mForceStatusBarFromKeyguard = true; + if (attrs.type == TYPE_STATUS_BAR) { + if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { + mForceStatusBarFromKeyguard = true; + } + if ((attrs.privateFlags & PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT) != 0) { + mForceStatusBarTransparent = true; + } } boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW @@ -4302,7 +4309,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_LAYOUT) Slog.i(TAG, "force=" + mForceStatusBar + " forcefkg=" + mForceStatusBarFromKeyguard + " top=" + mTopFullscreenOpaqueWindowState); - if (mForceStatusBar || mForceStatusBarFromKeyguard) { + boolean shouldBeTransparent = mForceStatusBarTransparent + && !mForceStatusBar + && !mForceStatusBarFromKeyguard; + if (!shouldBeTransparent) { + mStatusBarController.setShowTransparent(false /* transparent */); + } else if (!mStatusBar.isVisibleLw()) { + mStatusBarController.setShowTransparent(true /* transparent */); + } + if (mForceStatusBar || mForceStatusBarFromKeyguard || mForceStatusBarTransparent) { if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced"); if (mStatusBarController.setBarShowingLw(true)) { changes |= FINISH_LAYOUT_REDO_LAYOUT; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2d265e2..925a609 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources.Theme; import android.os.Build; import android.os.Environment; import android.os.FactoryTest; @@ -291,7 +292,7 @@ public final class SystemServer { private void createSystemContext() { ActivityThread activityThread = ActivityThread.systemMain(); mSystemContext = activityThread.getSystemContext(); - mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); + mSystemContext.setTheme(android.R.style.Theme_Material_DayNight_DarkActionBar); } /** @@ -1026,6 +1027,12 @@ public final class SystemServer { w.getDefaultDisplay().getMetrics(metrics); context.getResources().updateConfiguration(config, metrics); + // The system context's theme may be configuration-dependent. + final Theme systemTheme = context.getTheme(); + if (systemTheme.getChangingConfigurations() != 0) { + systemTheme.rebase(); + } + try { // TODO: use boot phase mPowerManagerService.systemReady(mActivityManagerService.getAppOpsService()); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index c1c5c56..176f54b 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -294,8 +294,10 @@ public class MidiService extends IMidiManager.Stub { @Override public String toString() { - StringBuilder sb = new StringBuilder("Device: "); + StringBuilder sb = new StringBuilder("Device Info: "); sb.append(mDeviceInfo); + sb.append(" Status: "); + sb.append(mDeviceStatus); sb.append(" UID: "); sb.append(mUid); return sb.toString(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 2897c61..fcdb6d6 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -754,7 +754,7 @@ public class VoiceInteractionManagerService extends SystemService { public boolean activeServiceSupportsAssist() { enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); synchronized (this) { - return mImpl != null && mImpl.mInfo.getSupportsAssist(); + return mImpl != null && mImpl.mInfo != null && mImpl.mInfo.getSupportsAssist(); } } @@ -762,7 +762,8 @@ public class VoiceInteractionManagerService extends SystemService { public boolean activeServiceSupportsLaunchFromKeyguard() throws RemoteException { enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); synchronized (this) { - return mImpl != null && mImpl.mInfo.getSupportsLaunchFromKeyguard(); + return mImpl != null && mImpl.mInfo != null + && mImpl.mInfo.getSupportsLaunchFromKeyguard(); } } diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java index fd0c06d..d3df151 100644 --- a/telecomm/java/android/telecom/DefaultDialerManager.java +++ b/telecomm/java/android/telecom/DefaultDialerManager.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.provider.Settings; import android.text.TextUtils; @@ -151,14 +152,14 @@ public class DefaultDialerManager { for (ResolveInfo resolveInfo : resolveInfoList) { final ActivityInfo activityInfo = resolveInfo.activityInfo; - if (activityInfo == null) { - continue; + if (activityInfo != null && !packageNames.contains(activityInfo.packageName)) { + packageNames.add(activityInfo.packageName); } - packageNames.add(activityInfo.packageName); } - // TODO: Filter for apps that don't handle DIAL intent with tel scheme - return packageNames; + final Intent dialIntentWithTelScheme = new Intent(Intent.ACTION_DIAL); + dialIntentWithTelScheme.setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, "", null)); + return filterByIntent(context, packageNames, dialIntentWithTelScheme); } /** @@ -182,6 +183,36 @@ public class DefaultDialerManager { || packageName.equals(tm.getSystemDialerPackage()); } + /** + * Filter a given list of package names for those packages that contain an activity that has + * an intent filter for a given intent. + * + * @param context A valid context + * @param packageNames List of package names to filter. + * @return The filtered list. + */ + private static List<String> filterByIntent(Context context, List<String> packageNames, + Intent intent) { + if (packageNames == null || packageNames.isEmpty()) { + return new ArrayList<>(); + } + + final List<String> result = new ArrayList<>(); + final List<ResolveInfo> resolveInfoList = + context.getPackageManager().queryIntentActivities(intent, 0); + final int length = resolveInfoList.size(); + for (int i = 0; i < length; i++) { + final ActivityInfo info = resolveInfoList.get(i).activityInfo; + if (info != null && packageNames.contains(info.packageName) + && !result.contains(info.packageName)) { + result.add(info.packageName); + } + } + + return result; + } + + private static TelecomManager getTelecomManager(Context context) { return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); } diff --git a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java index dd823ae..b54f9be 100644 --- a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java +++ b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java @@ -30,6 +30,7 @@ import android.util.Log; import junit.framework.Assert; +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -102,7 +103,11 @@ public class AppCompatibility extends InstrumentationTestCase { // otherwise raise an // exception with the first error encountered. assertNull(getStackTrace(err), err); - assertTrue("App crashed after launch.", processStillUp(packageName)); + try { + assertTrue("App crashed after launch.", processStillUp(packageName)); + } finally { + returnHome(); + } } else { Log.d(TAG, "Missing argument, use " + PACKAGE_TO_LAUNCH + " to specify the package to launch"); @@ -138,6 +143,19 @@ public class AppCompatibility extends InstrumentationTestCase { } } + private void returnHome() { + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // Send the "home" intent and wait 2 seconds for us to get there + mContext.startActivity(homeIntent); + try { + Thread.sleep(mWorkspaceLaunchTimeout); + } catch (InterruptedException e) { + // ignore + } + } + /** * Launches and activity and queries for errors. * @@ -150,9 +168,6 @@ public class AppCompatibility extends InstrumentationTestCase { // the recommended way to see if this is a tv or not. boolean isleanback = !mPackageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) && !mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); - Intent homeIntent = new Intent(Intent.ACTION_MAIN); - homeIntent.addCategory(Intent.CATEGORY_HOME); - homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Intent intent; if (isleanback) { Log.d(TAG, "Leanback and relax! " + packageName); @@ -173,14 +188,6 @@ public class AppCompatibility extends InstrumentationTestCase { // ignore } - // Send the "home" intent and wait 2 seconds for us to get there - mContext.startActivity(homeIntent); - try { - Thread.sleep(mWorkspaceLaunchTimeout); - } catch (InterruptedException e) { - // ignore - } - // See if there are any errors. We wait until down here to give ANRs as // much time as // possible to occur. @@ -198,6 +205,12 @@ public class AppCompatibility extends InstrumentationTestCase { return null; } + private boolean ensureForegroundActivity(RunningAppProcessInfo info) { + Log.d(TAG, String.format("ensureForegroundActivity: proc=%s, pid=%d, state=%d", + info.processName, info.pid, info.processState)); + return info.processState == ActivityManager.PROCESS_STATE_TOP; + } + /** * Determine if a given package is still running. * @@ -207,19 +220,32 @@ public class AppCompatibility extends InstrumentationTestCase { private boolean processStillUp(String packageName) { String processName = getProcessName(packageName); List<RunningAppProcessInfo> runningApps = mActivityManager.getRunningAppProcesses(); + List<RunningAppProcessInfo> relatedProcs = new ArrayList<>(); for (RunningAppProcessInfo app : runningApps) { if (app.processName.equalsIgnoreCase(processName)) { - Log.d(TAG, "Found process " + app.processName); + if (!ensureForegroundActivity(app)) { + Log.w(TAG, "Found process but it's not top activity."); + return false; + } return true; } for (String relatedPackage : app.pkgList) { - if (relatedPackage.equalsIgnoreCase(processName)) { - Log.d(TAG, "Found process " + app.processName); + if (relatedPackage.equalsIgnoreCase(packageName)) { + relatedProcs.add(app); + } + } + } + // now that we are here, we've found no RAPI's directly matching processName, but + // potentially a List of them with one of related packages being processName + if (!relatedProcs.isEmpty()) { + for (RunningAppProcessInfo app : relatedProcs) { + if (ensureForegroundActivity(app)) { return true; } } + Log.w(TAG, "Found related processes, but none has top activity."); } - Log.d(TAG, "Failed to find process " + processName + " with package name " + Log.w(TAG, "Failed to find process " + processName + " with package name " + packageName); return false; } diff --git a/tests/HierarchyViewerTest/.gitignore b/tests/HierarchyViewerTest/.gitignore new file mode 100644 index 0000000..75eec98 --- /dev/null +++ b/tests/HierarchyViewerTest/.gitignore @@ -0,0 +1,6 @@ +.gradle +.idea +*.iml +gradle* +build +local.properties diff --git a/tests/HierarchyViewerTest/Android.mk b/tests/HierarchyViewerTest/Android.mk new file mode 100644 index 0000000..07b90f0 --- /dev/null +++ b/tests/HierarchyViewerTest/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := HierarchyViewerTest + +LOCAL_JAVA_LIBRARIES := android.test.runner + +include $(BUILD_PACKAGE) diff --git a/tests/HierarchyViewerTest/AndroidManifest.xml b/tests/HierarchyViewerTest/AndroidManifest.xml new file mode 100644 index 0000000..65f2fd3 --- /dev/null +++ b/tests/HierarchyViewerTest/AndroidManifest.xml @@ -0,0 +1,36 @@ +<!-- + ~ 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 + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.hierarchyviewer"> + + <application> + <uses-library android:name="android.test.runner" /> + + <activity + android:name=".MainActivity" + android:label="HvTest" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + + <instrumentation + android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.test.hierarchyviewer" /> +</manifest> diff --git a/tests/HierarchyViewerTest/build.gradle b/tests/HierarchyViewerTest/build.gradle new file mode 100644 index 0000000..e8cdfa2 --- /dev/null +++ b/tests/HierarchyViewerTest/build.gradle @@ -0,0 +1,31 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0+' + + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 21 + buildToolsVersion "22.0.0" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 21 + versionCode 1 + versionName "1.0" + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} diff --git a/tests/HierarchyViewerTest/res/layout/activity_main.xml b/tests/HierarchyViewerTest/res/layout/activity_main.xml new file mode 100644 index 0000000..410a776 --- /dev/null +++ b/tests/HierarchyViewerTest/res/layout/activity_main.xml @@ -0,0 +1,12 @@ +<RelativeLayout 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"> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleX="10" + android:text="@string/test" /> + +</RelativeLayout> diff --git a/tests/HierarchyViewerTest/res/menu/menu_main.xml b/tests/HierarchyViewerTest/res/menu/menu_main.xml new file mode 100644 index 0000000..9b78a1e --- /dev/null +++ b/tests/HierarchyViewerTest/res/menu/menu_main.xml @@ -0,0 +1,5 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + <item android:id="@+id/action_settings" android:title="Settings" + android:orderInCategory="100" android:showAsAction="never" /> +</menu> diff --git a/tests/HierarchyViewerTest/res/values/strings.xml b/tests/HierarchyViewerTest/res/values/strings.xml new file mode 100644 index 0000000..800ee1c --- /dev/null +++ b/tests/HierarchyViewerTest/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="test">Hello World</string> +</resources>
\ No newline at end of file diff --git a/tests/HierarchyViewerTest/run_tests.sh b/tests/HierarchyViewerTest/run_tests.sh new file mode 100644 index 0000000..094bb4c --- /dev/null +++ b/tests/HierarchyViewerTest/run_tests.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# Runs the tests in this apk +adb install $OUT/data/app/HierarchyViewerTest/HierarchyViewerTest.apk +adb shell am instrument -w com.android.test.hierarchyviewer/android.test.InstrumentationTestRunner diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/Decoder.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/Decoder.java new file mode 100644 index 0000000..c6f1470 --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/Decoder.java @@ -0,0 +1,101 @@ +package com.android.test.hierarchyviewer; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +public class Decoder { + // Prefixes for simple primitives. These match the JNI definitions. + public static final byte SIG_BOOLEAN = 'Z'; + public static final byte SIG_BYTE = 'B'; + public static final byte SIG_SHORT = 'S'; + public static final byte SIG_INT = 'I'; + public static final byte SIG_LONG = 'J'; + public static final byte SIG_FLOAT = 'F'; + public static final byte SIG_DOUBLE = 'D'; + + // Prefixes for some commonly used objects + public static final byte SIG_STRING = 'R'; + + public static final byte SIG_MAP = 'M'; // a map with an short key + public static final short SIG_END_MAP = 0; + + private final ByteBuffer mBuf; + + public Decoder(byte[] buf) { + this(ByteBuffer.wrap(buf)); + } + + public Decoder(ByteBuffer buf) { + mBuf = buf; + } + + public boolean hasRemaining() { + return mBuf.hasRemaining(); + } + + public Object readObject() { + byte sig = mBuf.get(); + + switch (sig) { + case SIG_BOOLEAN: + return mBuf.get() == 0 ? Boolean.FALSE : Boolean.TRUE; + case SIG_BYTE: + return mBuf.get(); + case SIG_SHORT: + return mBuf.getShort(); + case SIG_INT: + return mBuf.getInt(); + case SIG_LONG: + return mBuf.getLong(); + case SIG_FLOAT: + return mBuf.getFloat(); + case SIG_DOUBLE: + return mBuf.getDouble(); + case SIG_STRING: + return readString(); + case SIG_MAP: + return readMap(); + default: + throw new DecoderException(sig, mBuf.position() - 1); + } + } + + private String readString() { + short len = mBuf.getShort(); + byte[] b = new byte[len]; + mBuf.get(b, 0, len); + return new String(b, Charset.forName("utf-8")); + } + + private Map<Short, Object> readMap() { + Map<Short, Object> m = new HashMap<Short, Object>(); + + while (true) { + Object o = readObject(); + if (!(o instanceof Short)) { + throw new DecoderException("Expected short key, got " + o.getClass()); + } + + Short key = (Short)o; + if (key == SIG_END_MAP) { + break; + } + + m.put(key, readObject()); + } + + return m; + } + + public static class DecoderException extends RuntimeException { + public DecoderException(byte seen, int pos) { + super(String.format("Unexpected byte %c seen at position %d", (char)seen, pos)); + } + + public DecoderException(String msg) { + super(msg); + } + } +} diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivity.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivity.java new file mode 100644 index 0000000..3a67273 --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivity.java @@ -0,0 +1,44 @@ +package com.android.test.hierarchyviewer; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + + +public class MainActivity extends Activity { + private static final String TAG = "Main"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + View textView = findViewById(R.id.textView); + Log.d(TAG, "x, y = " + textView.getX() + ", " + textView.getY()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivityTest.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivityTest.java new file mode 100644 index 0000000..ea3710d --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/MainActivityTest.java @@ -0,0 +1,81 @@ +package com.android.test.hierarchyviewer; + +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; + +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> { + private MainActivity mActivity; + private View mTextView; + + + public MainActivityTest() { + super(MainActivity.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mActivity = getActivity(); + mTextView = mActivity.findViewById(R.id.textView); + } + + private byte[] encode(View view) throws ClassNotFoundException, NoSuchMethodException, + IllegalAccessException, InstantiationException, InvocationTargetException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 1024); + + Object encoder = createEncoder(baos); + invokeMethod(View.class, view, "encode", encoder); + invokeMethod(encoder.getClass(), encoder, "endStream"); + + return baos.toByteArray(); + } + + private Object invokeMethod(Class targetClass, Object target, String methodName, Object... params) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class[] paramClasses = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + paramClasses[i] = params[i].getClass(); + } + Method method = targetClass.getDeclaredMethod(methodName, paramClasses); + method.setAccessible(true); + return method.invoke(target, params); + } + + private Object createEncoder(ByteArrayOutputStream baos) throws ClassNotFoundException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException, + InstantiationException { + Class clazz = Class.forName("android.view.ViewHierarchyEncoder"); + Constructor constructor = clazz.getConstructor(ByteArrayOutputStream.class); + return constructor.newInstance(baos); + } + + public void testTextView() throws Exception { + byte[] data = encode(mTextView); + assertNotNull(data); + assertTrue(data.length > 0); + + ViewDumpParser parser = new ViewDumpParser(); + parser.parse(data); + + List<Map<Short, Object>> views = parser.getViews(); + Map<String, Short> propertyNameTable = parser.getIds(); + + assertEquals(1, views.size()); + assertNotNull(propertyNameTable); + + Map<Short, Object> textViewProperties = views.get(0); + assertEquals("android.widget.TextView", + textViewProperties.get(propertyNameTable.get("meta:__name__"))); + + assertEquals(mActivity.getString(R.string.test), + textViewProperties.get(propertyNameTable.get("text:text"))); + } +} diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java new file mode 100644 index 0000000..0111bc6 --- /dev/null +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java @@ -0,0 +1,73 @@ +package com.android.test.hierarchyviewer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class ViewDumpParser { + private Map<String, Short> mIds; + private List<Map<Short,Object>> mViews; + + public void parse(byte[] data) { + Decoder d = new Decoder(ByteBuffer.wrap(data)); + + mViews = new ArrayList<>(100); + while (d.hasRemaining()) { + Object o = d.readObject(); + if (o instanceof Map) { + //noinspection unchecked + mViews.add((Map<Short, Object>) o); + } + } + + if (mViews.isEmpty()) { + return; + } + + // the last one is the property map + Map<Short,Object> idMap = mViews.remove(mViews.size() - 1); + mIds = reverse(idMap); + } + + public String getFirstView() { + if (mViews.isEmpty()) { + return null; + } + + Map<Short, Object> props = mViews.get(0); + Object name = getProperty(props, "__name__"); + Object hash = getProperty(props, "__hash__"); + + if (name instanceof String && hash instanceof Integer) { + return String.format(Locale.US, "%s@%x", name, hash); + } else { + return null; + } + } + + private Object getProperty(Map<Short, Object> props, String key) { + return props.get(mIds.get(key)); + } + + private static Map<String, Short> reverse(Map<Short, Object> m) { + Map<String, Short> r = new HashMap<String, Short>(m.size()); + + for (Map.Entry<Short, Object> e : m.entrySet()) { + r.put((String)e.getValue(), e.getKey()); + } + + return r; + } + + public List<Map<Short, Object>> getViews() { + return mViews; + } + + public Map<String, Short> getIds() { + return mIds; + } + +} diff --git a/tests/OneMedia/Android.mk b/tests/OneMedia/Android.mk index b7d7f98..9fc6403 100644 --- a/tests/OneMedia/Android.mk +++ b/tests/OneMedia/Android.mk @@ -9,9 +9,6 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) \ LOCAL_PACKAGE_NAME := OneMedia LOCAL_CERTIFICATE := platform -LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-media-protocols - LOCAL_JAVA_LIBRARIES += org.apache.http.legacy LOCAL_PROGUARD_ENABLED := disabled diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml index ef3fad5..c6824ec 100644 --- a/tests/OneMedia/AndroidManifest.xml +++ b/tests/OneMedia/AndroidManifest.xml @@ -27,15 +27,6 @@ android:name="com.android.onemedia.OnePlayerService" android:exported="true" android:process="com.android.onemedia.service" /> - <service - android:name=".provider.OneMediaRouteProvider" - android:permission="android.permission.BIND_MEDIA_ROUTE_SERVICE" - android:exported="true" - android:process="com.android.onemedia.provider"> - <intent-filter> - <action android:name="android.media.routing.MediaRouteService" /> - </intent-filter> - </service> </application> </manifest> diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 141a209..2455c9c 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -19,25 +19,17 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.media.MediaMetadata; -import android.media.routing.MediaRouteSelector; -import android.media.routing.MediaRouter; -import android.media.routing.MediaRouter.ConnectionRequest; -import android.media.routing.MediaRouter.DestinationInfo; -import android.media.routing.MediaRouter.RouteInfo; import android.media.session.MediaSession; import android.media.session.MediaSession.QueueItem; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Bundle; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaStatus; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; import com.android.onemedia.playback.LocalRenderer; -import com.android.onemedia.playback.OneMRPRenderer; import com.android.onemedia.playback.Renderer; import com.android.onemedia.playback.RequestUtils; @@ -48,7 +40,6 @@ public class PlayerSession { private static final String TAG = "PlayerSession"; protected MediaSession mSession; - protected MediaRouter mRouter; protected Context mContext; protected Renderer mRenderer; protected MediaSession.Callback mCallback; @@ -84,22 +75,11 @@ public class PlayerSession { .getSystemService(Context.MEDIA_SESSION_SERVICE); Log.d(TAG, "Creating session for package " + mContext.getBasePackageName()); - mRouter = new MediaRouter(mContext); - mRouter.addSelector(new MediaRouteSelector.Builder() - .addRequiredProtocol(MediaPlayerProtocol.class) - .build()); - mRouter.addSelector(new MediaRouteSelector.Builder() - .setRequiredFeatures(MediaRouter.ROUTE_FEATURE_LIVE_AUDIO) - .setOptionalFeatures(MediaRouter.ROUTE_FEATURE_LIVE_VIDEO) - .build()); - mRouter.setRoutingCallback(new RoutingCallback(), null); - mSession = new MediaSession(mContext, "OneMedia"); mSession.setCallback(mCallback); mSession.setPlaybackState(mPlaybackState); mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); - mSession.setMediaRouter(mRouter); mSession.setActive(true); updateMetadata(); } @@ -117,10 +97,6 @@ public class PlayerSession { mSession.release(); mSession = null; } - if (mRouter != null) { - mRouter.release(); - mRouter = null; - } } public void setListener(Listener listener) { @@ -278,63 +254,4 @@ public class PlayerSession { mRenderer.onPause(); } } - - private class RoutingCallback extends MediaRouter.RoutingCallback { - @Override - public void onConnectionStateChanged(int state) { - if (state == MediaRouter.CONNECTION_STATE_CONNECTING) { - if (mRenderer != null) { - mRenderer.onStop(); - } - mRenderer = null; - updateState(PlaybackState.STATE_CONNECTING); - return; - } - - MediaRouter.ConnectionInfo connection = mRouter.getConnection(); - if (connection != null) { - MediaPlayerProtocol protocol = - connection.getProtocolObject(MediaPlayerProtocol.class); - if (protocol != null) { - Log.d(TAG, "Connected to route using media player protocol"); - - protocol.setCallback(new PlayerCallback(), null); - mRenderer = new OneMRPRenderer(protocol); - updateState(PlaybackState.STATE_NONE); - return; - } - } - - // Use local route - mRenderer = new LocalRenderer(mContext, null); - mRenderer.registerListener(mRenderListener); - updateState(PlaybackState.STATE_NONE); - } - } - - private class PlayerCallback extends MediaPlayerProtocol.Callback { - @Override - public void onStatusUpdated(MediaStatus status, Bundle extras) { - if (status != null) { - Log.d(TAG, "Received status update: " + status.toBundle()); - switch (status.getPlayerState()) { - case MediaStatus.PLAYER_STATE_BUFFERING: - updateState(PlaybackState.STATE_BUFFERING); - break; - case MediaStatus.PLAYER_STATE_IDLE: - updateState(PlaybackState.STATE_STOPPED); - break; - case MediaStatus.PLAYER_STATE_PAUSED: - updateState(PlaybackState.STATE_PAUSED); - break; - case MediaStatus.PLAYER_STATE_PLAYING: - updateState(PlaybackState.STATE_PLAYING); - break; - case MediaStatus.PLAYER_STATE_UNKNOWN: - updateState(PlaybackState.STATE_NONE); - break; - } - } - } - } } diff --git a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java b/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java deleted file mode 100644 index 55eb92c..0000000 --- a/tests/OneMedia/src/com/android/onemedia/playback/OneMRPRenderer.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.android.onemedia.playback; - -import android.os.Bundle; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaInfo; - -/** - * Renderer for communicating with the OneMRP route - */ -public class OneMRPRenderer extends Renderer { - private final MediaPlayerProtocol mProtocol; - - public OneMRPRenderer(MediaPlayerProtocol protocol) { - super(null, null); - mProtocol = protocol; - } - - @Override - public void setContent(Bundle request) { - MediaInfo mediaInfo = new MediaInfo(request.getString(RequestUtils.EXTRA_KEY_SOURCE), - MediaInfo.STREAM_TYPE_BUFFERED, "audio/mp3"); - mProtocol.load(mediaInfo, true, 0, null); - } - - @Override - public boolean onStop() { - mProtocol.stop(null); - return true; - } - - @Override - public boolean onPlay() { - mProtocol.play(null); - return true; - } - - @Override - public boolean onPause() { - mProtocol.pause(null); - return true; - } - - @Override - public long getSeekPosition() { - return -1; - } -} diff --git a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java b/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java deleted file mode 100644 index 5845e48..0000000 --- a/tests/OneMedia/src/com/android/onemedia/provider/OneMediaRouteProvider.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * 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 com.android.onemedia.provider; - -import android.media.routing.MediaRouteSelector; -import android.media.routing.MediaRouteService; -import android.media.routing.MediaRouter.ConnectionInfo; -import android.media.routing.MediaRouter.ConnectionRequest; -import android.media.routing.MediaRouter.DestinationInfo; -import android.media.routing.MediaRouter.DiscoveryRequest; -import android.media.routing.MediaRouter.RouteInfo; -import android.media.session.PlaybackState; -import android.os.Bundle; -import android.os.Handler; -import android.os.Process; -import android.support.media.protocols.MediaPlayerProtocol; -import android.support.media.protocols.MediaPlayerProtocol.MediaInfo; -import android.support.media.protocols.MediaPlayerProtocol.MediaStatus; -import android.os.Looper; -import android.os.ResultReceiver; -import android.os.SystemClock; -import android.util.Log; - -import com.android.onemedia.playback.LocalRenderer; -import com.android.onemedia.playback.Renderer; -import com.android.onemedia.playback.RequestUtils; - -import java.util.ArrayList; - -/** - * Test of MediaRouteProvider. Show a dummy provider with a simple interface for - * playing music. - */ -public class OneMediaRouteProvider extends MediaRouteService { - private static final String TAG = "OneMRP"; - private static final boolean DEBUG = true; - - private static final String TEST_DESTINATION_ID = "testDestination"; - private static final String TEST_ROUTE_ID = "testRoute"; - - private Renderer mRenderer; - private RenderListener mRenderListener; - private PlaybackState mPlaybackState; - private Handler mHandler; - - private OneStub mStub; - - @Override - public void onCreate() { - mHandler = new Handler(); - mRenderer = new LocalRenderer(this, null); - mRenderListener = new RenderListener(); - PlaybackState.Builder bob = new PlaybackState.Builder(); - bob.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY); - mPlaybackState = bob.build(); - - mRenderer.registerListener(mRenderListener); - } - - @Override - public ClientSession onCreateClientSession(ClientInfo client) { - if (client.getUid() != Process.myUid()) { - // for testing purposes, only allow connections from this application - // since this provider is not fully featured - return null; - } - return new OneSession(client); - } - - private final class OneSession extends ClientSession { - private final ClientInfo mClient; - - public OneSession(ClientInfo client) { - mClient = client; - } - - @Override - public boolean onStartDiscovery(DiscoveryRequest req, DiscoveryCallback callback) { - for (MediaRouteSelector selector : req.getSelectors()) { - if (isMatch(selector)) { - DestinationInfo destination = new DestinationInfo.Builder( - TEST_DESTINATION_ID, getServiceMetadata(), "OneMedia") - .setDescription("Test route from OneMedia app.") - .build(); - ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>(); - routes.add(new RouteInfo.Builder( - TEST_ROUTE_ID, destination, selector).build()); - callback.onDestinationFound(destination, routes); - return true; - } - } - return false; - } - - @Override - public void onStopDiscovery() { - } - - @Override - public boolean onConnect(ConnectionRequest req, ConnectionCallback callback) { - if (req.getRoute().getId().equals(TEST_ROUTE_ID)) { - mStub = new OneStub(); - ConnectionInfo connection = new ConnectionInfo.Builder(req.getRoute()) - .setProtocolStub(MediaPlayerProtocol.class, mStub) - .build(); - callback.onConnected(connection); - return true; - } - return false; - } - - @Override - public void onDisconnect() { - mStub = null; - } - - private boolean isMatch(MediaRouteSelector selector) { - if (!selector.containsProtocol(MediaPlayerProtocol.class)) { - return false; - } - for (String protocol : selector.getRequiredProtocols()) { - if (!protocol.equals(MediaPlayerProtocol.class.getName())) { - return false; - } - } - return true; - } - } - - private final class OneStub extends MediaPlayerProtocol.Stub { - MediaInfo mMediaInfo; - - public OneStub() { - super(mHandler); - } - - @Override - public void onLoad(MediaInfo mediaInfo, boolean autoplay, long playPosition, - Bundle extras) { - if (DEBUG) { - Log.d(TAG, "Attempting to play " + mediaInfo.getContentId()); - } - // look up the route and send a play command to it - mMediaInfo = mediaInfo; - Bundle bundle = new Bundle(); - bundle.putString(RequestUtils.EXTRA_KEY_SOURCE, mediaInfo.getContentId()); - mRenderer.setContent(bundle); - } - - @Override - public void onPlay(Bundle extras) { - mRenderer.onPlay(); - } - - @Override - public void onPause(Bundle extras) { - mRenderer.onPause(); - } - } - - private class RenderListener implements Renderer.Listener { - - @Override - public void onError(int type, int extra, Bundle extras, Throwable error) { - Log.d(TAG, "Sending onError with type " + type + " and extra " + extra); - sendStatusUpdate(PlaybackState.STATE_ERROR); - } - - @Override - public void onStateChanged(int newState) { - long position = -1; - if (mRenderer != null) { - position = mRenderer.getSeekPosition(); - } - int pbState; - float rate = 0; - String errorMsg = null; - switch (newState) { - case Renderer.STATE_ENDED: - case Renderer.STATE_STOPPED: - pbState = PlaybackState.STATE_STOPPED; - break; - case Renderer.STATE_INIT: - case Renderer.STATE_PREPARING: - pbState = PlaybackState.STATE_BUFFERING; - break; - case Renderer.STATE_ERROR: - pbState = PlaybackState.STATE_ERROR; - break; - case Renderer.STATE_PAUSED: - pbState = PlaybackState.STATE_PAUSED; - break; - case Renderer.STATE_PLAYING: - pbState = PlaybackState.STATE_PLAYING; - rate = 1; - break; - default: - pbState = PlaybackState.STATE_ERROR; - errorMsg = "unknown state"; - break; - } - PlaybackState.Builder bob = new PlaybackState.Builder(mPlaybackState); - bob.setState(pbState, position, rate, SystemClock.elapsedRealtime()); - bob.setErrorMessage(errorMsg); - mPlaybackState = bob.build(); - - sendStatusUpdate(mPlaybackState.getState()); - } - - @Override - public void onBufferingUpdate(int percent) { - } - - @Override - public void onFocusLost() { - Log.d(TAG, "Focus lost, pausing"); - // Don't update state here, we'll get a separate call to - // onStateChanged when it pauses - mRenderer.onPause(); - } - - @Override - public void onNextStarted() { - } - - private void sendStatusUpdate(int state) { - if (mStub != null) { - MediaStatus status = new MediaStatus(1, mStub.mMediaInfo); - switch (state) { - case PlaybackState.STATE_BUFFERING: - case PlaybackState.STATE_FAST_FORWARDING: - case PlaybackState.STATE_REWINDING: - case PlaybackState.STATE_SKIPPING_TO_NEXT: - case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: - status.setPlayerState(MediaStatus.PLAYER_STATE_BUFFERING); - break; - case PlaybackState.STATE_CONNECTING: - case PlaybackState.STATE_STOPPED: - status.setPlayerState(MediaStatus.PLAYER_STATE_IDLE); - break; - case PlaybackState.STATE_PAUSED: - status.setPlayerState(MediaStatus.PLAYER_STATE_PAUSED); - break; - case PlaybackState.STATE_PLAYING: - status.setPlayerState(MediaStatus.PLAYER_STATE_PLAYING); - break; - case PlaybackState.STATE_NONE: - case PlaybackState.STATE_ERROR: - default: - status.setPlayerState(MediaStatus.PLAYER_STATE_UNKNOWN); - break; - } - mStub.sendStatusUpdatedEvent(status, null); - } - } - } -} diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java index 4bd83e9..41d94b7 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java @@ -27,6 +27,7 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.res.Resources.NotFoundException; import android.content.res.Resources.Theme; +import android.content.res.Resources.ThemeKey; import android.util.AttributeSet; import android.util.TypedValue; @@ -110,22 +111,16 @@ public class Resources_Theme_Delegate { private static boolean setupResources(Theme thisTheme) { // Key is a space-separated list of theme ids applied that have been merged into the // BridgeContext's theme to make thisTheme. - String[] appliedStyles = thisTheme.getKey().split(" "); + final ThemeKey key = thisTheme.getKey(); + final int[] resId = key.mResId; + final boolean[] force = key.mForce; + boolean changed = false; - for (String s : appliedStyles) { - if (s.isEmpty()) { - continue; - } - // See the definition of force parameter in Theme.applyStyle(). - boolean force = false; - if (s.charAt(s.length() - 1) == '!') { - force = true; - s = s.substring(0, s.length() - 1); - } - int styleId = Integer.parseInt(s, 16); - StyleResourceValue style = resolveStyle(styleId); + for (int i = 0, N = key.mCount; i < N; i++) { + StyleResourceValue style = resolveStyle(resId[i]); if (style != null) { - RenderSessionImpl.getCurrentContext().getRenderResources().applyStyle(style, force); + RenderSessionImpl.getCurrentContext().getRenderResources().applyStyle( + style, force[i]); changed = true; } |