Merge "Optionally add a default MOBILE request." into mnc-dev
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 @@
   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 @@
   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 @@
   }
 
   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.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 @@
   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 @@
   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 @@
   }
 
   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.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 @@
                     }
                     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 @@
             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 @@
         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.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 @@
         // 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 @@
         // 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 @@
                 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.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 @@
          * 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.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 @@
             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 @@
             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 @@
             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 @@
                 // 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.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 @@
      * @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 @@
      * @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 @@
      * @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..8d96f5c2 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -785,6 +785,7 @@
     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.
+     * If the resource is cached, creates and returns a new instance of it.
      *
-     * @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.
-     *
+     * @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);
-                }
-            }
-        }
+    @Override
+    public boolean shouldInvalidateEntry(ConstantState<T> entry, int configChanges) {
+        return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
     }
-
-    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);
-            }
-        }
-    }
-
 }
diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java
new file mode 100644
index 0000000..fc70bc60
--- /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.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.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 @@
     // 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 @@
             AssetManager.applyThemeStyle(mTheme, resId, force);
 
             mThemeResId = resId;
-            mKey += Integer.toHexString(resId) + (force ? "! " : " ");
+            mKey.append(resId, force);
         }
 
         /**
@@ -1457,7 +1457,7 @@
             AssetManager.copyTheme(mTheme, other.mTheme);
 
             mThemeResId = other.mThemeResId;
-            mKey = other.mKey;
+            mKey.setTo(other.getKey());
         }
 
         /**
@@ -1765,6 +1765,9 @@
             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 @@
         /** 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 @@
             return mThemeResId;
         }
 
-        /*package*/ String getKey() {
+        /*package*/ ThemeKey getKey() {
             return mKey;
         }
 
@@ -1793,29 +1793,134 @@
         }
 
         /**
-         * 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 @@
                         + " 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 @@
         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 @@
         }
 
         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 @@
         // 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 @@
         // 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 @@
         // 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 @@
             }
         } 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));
+                caches.put(key, theme, cs, usesTheme);
             }
         }
     }
 
     /**
-     * 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);
-            }
-        }
-    }
-
-    /**
-     * 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 @@
         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 @@
         }
 
         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 @@
     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 @@
      * <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 @@
      * @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 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");
+            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");
-        }
-
-        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 @@
     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 @@
      *
      * <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 @@
                                         "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 @@
                         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 @@
                 }
                 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 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 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 @@
         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 @@
                 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 @@
                                         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 @@
                 // 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 @@
                         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 @@
                 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 @@
 
                 // 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 @@
         } 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 @@
         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 @@
                     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 @@
  * {@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 @@
      *
      * @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 @@
      *
      * @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 @@
      *
      * @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 @@
      *
      * @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 @@
      *
      * @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 @@
      *
      * @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 @@
      * @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 @@
      * @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 @@
         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 @@
         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 @@
 
     /**
      * 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 @@
     }
 
     /**
+     * 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 @@
             }
             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 @@
                     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 @@
 
         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 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 @@
         }
 
         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.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 @@
         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 @@
 
 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.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 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 @@
         }
     }
 
+    /** @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 @@
         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 @@
     @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.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 @@
             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.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 @@
             }
         }
     }
+
+    /** @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 @@
         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 @@
     @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 @@
     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;
-        return retOffset;
-    }
-
-    private int getWordEnd(int offset, boolean includePunctuation) {
-        int retOffset = getWordIteratorWithText().getEnd(offset);
-        if (retOffset == BreakIterator.DONE) {
-            retOffset = offset;
-        } else if (includePunctuation) {
-            retOffset = handlePunctuation(retOffset);
-        }
-        return retOffset;
-    }
-
-    private boolean isEndBoundary(int offset) {
-        int thisEnd = getWordEnd(offset, false);
-        return offset == thisEnd;
-    }
-
-    private boolean isStartBoundary(int offset) {
-        int thisStart = getWordStart(offset);
-        return thisStart == offset;
-    }
-
-    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.
-        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);
+        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);
             }
         }
-        return offset;
+        if (retOffset == BreakIterator.DONE) {
+            return offset;
+        }
+        return retOffset;
+    }
+
+    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) {
+            return offset;
+        }
+        return retOffset;
+    }
+
+    /**
+     * 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);
+
+        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;
+    }
+
+    /**
+     * 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 == 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 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 @@
             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 @@
         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 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 @@
                     }
                 }
                 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 @@
                     offset = end;
                 }
                 offset -= mTouchWordOffset;
-                mInWord = !isEndBoundary(offset);
+                mInWord = !getWordIteratorWithText().isBoundary(offset);
                 positionCursor = true;
             }
 
@@ -3946,7 +3995,7 @@
                     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 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 @@
                     }
                 }
                 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 @@
                 }
                 offset += mTouchWordOffset;
                 positionCursor = true;
-                mInWord = !isStartBoundary(offset);
+                mInWord = !getWordIteratorWithText().isBoundary(offset);
             }
 
             if (positionCursor) {
@@ -4049,7 +4093,7 @@
                     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 @@
     }
 
     /**
-     * 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 @@
         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 @@
                         }
                     }
 
-                    // New selection, reset line.
-                    mPrevLine = mTextView.getLineAtCoordinate(y);
                     mDownPositionX = x;
                     mDownPositionY = y;
                     mGestureStayedInTapRegion = true;
@@ -4318,7 +4328,7 @@
                         // 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 @@
                             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 @@
 
                             // 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.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 @@
         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.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 @@
                 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.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 @@
         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.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 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 @@
 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.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 @@
         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 @@
             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 @@
 
     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.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.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 @@
                 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.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 @@
         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.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 @@
             // 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.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 @@
         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.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 @@
                 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.text.format.DateFormat;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
+import android.view.ViewHierarchyEncoder;
 
 import com.android.internal.R;
 
@@ -546,4 +548,18 @@
         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.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 @@
             @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 @@
         }
     }
 
+    /** @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 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.view.View;
 
 /**
  * Log all the things.
@@ -33,6 +34,10 @@
     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 @@
         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 @@
         } 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 @@
      * @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 @@
     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::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 @@
     }
 
     void* pixels() {
-        return mBitmap->pixelRef()->pixels();
+        return mBitmap->peekAtPixelRef()->pixels();
     }
 
     bool valid() {
@@ -780,7 +797,7 @@
 
 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 @@
         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 @@
     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 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 @@
     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 @@
     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 @@
         }
 
         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 @@
     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 @@
     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 @@
     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 @@
     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 @@
         (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 @@
             (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
deleted file mode 100644
index 0179433..0000000
--- a/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index e5760be..0000000
--- a/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index 3939214..0000000
--- a/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index 432c385..0000000
--- a/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png
+++ /dev/null
Binary files differ
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 @@
       "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 @@
   GOOGLE_RESOURCES,
   GUIDE_RESOURCES,
   SAMPLES_RESOURCES,
+  PREVIEW_RESOURCES,
   TOOLS_RESOURCES,
   TRAINING_RESOURCES,
   YOUTUBE_RESOURCES,
@@ -70,6 +71,7 @@
   '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 @@
   {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..dde3c7be 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 @@
 
 <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 @@
  */
 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 @@
 
     /**
      * 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 @@
          * 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 @@
 
         private final SplitDecision mSplitDecision;
 
-        private final boolean mAllowRotation;
         private final int mPadding;
 
         /**
@@ -263,7 +257,6 @@
         }
 
         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 @@
          *
          * @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 @@
             // 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 @@
 
         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 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 @@
     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 @@
         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 @@
         }
     }
 
-    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 @@
         }
 
         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 @@
                 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 @@
         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 @@
         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 @@
         }
 
         @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 @@
                     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 @@
             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 @@
      *     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 @@
      *     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 @@
      * 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 @@
      * 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 @@
     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 @@
             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 @@
     /**
      * 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 @@
 
     /**
      * 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 @@
         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 @@
          *
          * <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 @@
          *
          * <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 @@
          *
          * <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 @@
 
     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 @@
             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 @@
     /**
      * 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 @@
     /**
      * 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 @@
     }
 
     /**
-     * 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 @@
 
         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 @@
         /**
          * 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 @@
          *
          * <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 @@
          *
          * <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 @@
          *
          * <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 @@
          *
          * <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 @@
          * 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 @@
          * 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 @@
          * 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 @@
          * 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 @@
          * 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 @@
          * <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 @@
          * <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 @@
          * <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 @@
         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.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 @@
             } 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 @@
         }
         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 @@
                 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 @@
             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 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 @@
  * @hide
  */
 abstract class KeyStoreCryptoOperationUtils {
+
+    private static volatile SecureRandom sRng;
+
     private KeyStoreCryptoOperationUtils() {}
 
     /**
@@ -81,4 +85,28 @@
         // 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 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 @@
         }
 
         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 @@
         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 @@
         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.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 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 @@
     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 @@
         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 @@
         }
 
         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 @@
                 (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);
+        String keyAlgorithmJCA;
+        try {
+            keyAlgorithmJCA = KeyStoreKeyProperties.Algorithm.fromKeymasterSecretKeyAlgorithm(
+                    mKeymasterAlgorithm, mKeymasterDigest);
+        } catch (IllegalArgumentException e) {
+            throw new ProviderException("Failed to obtain JCA secret key algorithm name", e);
+        }
         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");
-        }
-
-        mSpec = spec;
-        mRng = random;
-    }
-
-    @Override
-    protected void engineInit(int keySize, SecureRandom random) {
-        throw new UnsupportedOperationException("Cannot initialize without a "
-                + KeyGeneratorSpec.class.getName() + " parameter");
-    }
 }
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 @interface PurposeEnum {}
 
     /**
-     * Purpose of key.
+     * Purposes of key.
      */
     public static abstract class Purpose {
         private Purpose() {}
@@ -122,6 +132,514 @@
     }
 
     @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 @@
     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 @@
             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 @@
     /**
      * 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 @@
 
     /**
      * 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 @@
     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 @@
             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 @@
     /**
      * 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 @@
      * 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 @@
      *
      * @see #isDigestsSpecified()
      */
-    public String[] getDigests() {
+    public @KeyStoreKeyProperties.DigestEnum String[] getDigests() {
         if (mDigests == null) {
             throw new IllegalStateException("Digests not specified");
         }
@@ -190,7 +190,7 @@
     /**
      * 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 @@
 
     /**
      * 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 @@
         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 @@
          *
          * <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 @@
          *
          * <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 @@
          *
          * <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 @@
          *
          * <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 @@
          * 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 @@
          * 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 @@
          * 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 @@
          *
          * <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 @@
          * 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 @@
          * <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 @@
          * <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 @@
          *
          * <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 @@
         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 @@
 
             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 @@
             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.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 @@
 
     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 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 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 @@
     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 @@
     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 @@
         // 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 @@
         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 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 @@
         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 @@
      * 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 @@
     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 @@
         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 @@
         // 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 @@
     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 @@
     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(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 @@
     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 @@
     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 @@
     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 @@
     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 @@
 }
 
 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 @@
                         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 @@
         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 @@
         }
 
         /** @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 @@
                 synchronized(this) {
                     mStopped = false;
                     mSeeking = true;
+                    mBuffering = false;
                     scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
                 }
             }
@@ -3683,7 +3702,7 @@
                         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 @@
 
     @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 @@
 
     @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 @@
 
 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::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 @@
         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 @@
     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 @@
         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 @@
     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.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 @@
     }
 
     private class MyWebViewClient extends WebViewClient {
+        private static final String INTERNAL_ASSETS = "file:///android_asset/";
         private boolean firstPageLoad = true;
 
         @Override
@@ -240,6 +241,12 @@
                 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 @@
         @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.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 @@
                     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.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 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 @@
             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 @@
             }
         }
         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.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 @@
     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 @@
         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 @@
         handleRefreshState(null);
     }
 
+    protected void handleClick() {
+        MetricsLogger.action(mContext, getMetricsCategory(), getMetricsPackage());
+    };
+
     protected void handleSecondaryClick() {
         // optional
     }
@@ -168,6 +182,10 @@
         // 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.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 @@
 
     @Override
     public void handleClick() {
+        super.handleClick();
         setEnabled(!mState.value);
         mEnable.setAllowAnimation(true);
         mDisable.setAllowAnimation(true);
@@ -85,6 +87,11 @@
     }
 
     @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.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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         final boolean isEnabled = (Boolean)mState.value;
         mController.setBluetoothEnabled(!isEnabled);
     }
@@ -132,6 +134,11 @@
     }
 
     @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 @@
         }
 
         @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.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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         showDetail(true);
     }
 
@@ -113,6 +115,11 @@
     }
 
     @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 @@
         }
 
         @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.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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         if (mDataController.isMobileDataSupported()) {
             showDetail(true);
         } else {
@@ -118,6 +120,11 @@
                 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 @@
         }
 
         @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 @@
 
 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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         mSetting.setValue(mState.value ? 0 : 1);
         mEnable.setAllowAnimation(true);
         mDisable.setAllowAnimation(true);
@@ -115,6 +117,11 @@
     }
 
     @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.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 @@
 
     @Override
     public void handleClick() {
+        super.handleClick();
         if (mState.value) {
             mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
         } else {
@@ -135,6 +137,11 @@
     }
 
     @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 @@
         }
 
         @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 @@
 
 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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         if (ActivityManager.isUserAMonkey()) {
             return;
         }
@@ -84,6 +86,11 @@
     }
 
     @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.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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         final boolean isEnabled = (Boolean) mState.value;
         mController.setHotspotEnabled(!isEnabled);
         mEnable.setAllowAnimation(true);
@@ -97,6 +99,11 @@
     }
 
     @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.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 @@
     private PendingIntent mOnLongClick;
     private String mOnLongClickUri;
     private int mCurrentUserId;
+    private String mIntentPackage;
 
     private IntentTile(Host host, String action) {
         super(host);
@@ -82,6 +84,7 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         sendIntent("click", mOnClick, mOnClickUri);
     }
 
@@ -133,6 +136,17 @@
         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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         final boolean wasEnabled = (Boolean) mState.value;
         mController.setLocationEnabled(!wasEnabled);
         mEnable.setAllowAnimation(true);
@@ -87,6 +89,11 @@
     }
 
     @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 @@
 
 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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         if (mController == null) return;
         final boolean newState = !mState.value;
         mController.setRotationLocked(newState);
@@ -92,6 +94,11 @@
                 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.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 @@
 
     @Override
     protected void handleClick() {
+        super.handleClick();
         mState.copyTo(mStateBeforeClick);
         mController.setWifiEnabled(!mState.enabled);
     }
@@ -159,6 +161,11 @@
     }
 
     @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_WIFI;
+    }
+
+    @Override
     protected boolean shouldAnnouncementBeDelayed() {
         return mStateBeforeClick.enabled == mState.enabled;
     }
@@ -274,6 +281,11 @@
         }
 
         @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 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 @@
             = new HashMap<>();
     private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
     private RankingMap mLatestRankingMap;
+    private boolean mNoAnimationOnNextBarModeChange;
 
     @Override
     public void start() {
@@ -1709,6 +1710,7 @@
      * 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 void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
         if (inPinnedMode) {
             mStatusBarWindowManager.setHeadsUpShowing(true);
+            mStatusBarWindowManager.setForceStatusBarVisible(true);
         } else {
             Runnable endRunnable = new Runnable() {
                 @Override
@@ -2132,6 +2135,7 @@
 
         // 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 @@
                 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 @@
                 }
             }
 
-            // 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 @@
 
     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 @@
 
     private void apply(State state) {
         applyKeyguardFlags(state);
+        applyForceStatusBarVisibleFlag(state);
         applyFocusableFlag(state);
         adjustScreenOrientation(state);
         applyHeight(state);
@@ -178,6 +179,16 @@
         }
     }
 
+    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 @@
         apply(mCurrentState);
     }
 
+    public void setForceStatusBarVisible(boolean forceStatusBarVisible) {
+        mCurrentState.forceStatusBarVisible = forceStatusBarVisible;
+        apply(mCurrentState);
+    }
+
     private static class State {
         boolean keyguardShowing;
         boolean keyguardOccluded;
@@ -250,6 +266,7 @@
         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.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 @@
         @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 @@
                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 @@
     // 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 @@
         /**
          * 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 @@
 
                     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 @@
     //     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 @@
 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 @@
         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 f30a567..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 @@
         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 @@
                         }
                     }
                     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 @@
     /** 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 @@
         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 @@
     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 @@
         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 @@
         }
         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 @@
                 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 @@
 
         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 @@
     boolean mTopIsFullscreen;
     boolean mForceStatusBar;
     boolean mForceStatusBarFromKeyguard;
+    private boolean mForceStatusBarTransparent;
     boolean mHideLockScreen;
     boolean mForcingShowNavBar;
     int mForcingShowNavBarLayer;
@@ -4129,6 +4130,7 @@
         mAppsThatDismissKeyguard.clear();
         mForceStatusBar = false;
         mForceStatusBarFromKeyguard = false;
+        mForceStatusBarTransparent = false;
         mForcingShowNavBar = false;
         mForcingShowNavBarLayer = -1;
 
@@ -4154,8 +4156,13 @@
             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 @@
             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.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 @@
     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 @@
         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..176f54b1 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 @@
 
         @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 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 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.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 @@
 
         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 @@
                 || 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 junit.framework.Assert;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
@@ -102,7 +103,11 @@
             // 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 @@
         }
     }
 
+    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 @@
         // 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 @@
             // 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 @@
         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 @@
     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);
-                    return true;
+                if (relatedPackage.equalsIgnoreCase(packageName)) {
+                    relatedProcs.add(app);
                 }
             }
         }
-        Log.d(TAG, "Failed to find process " + processName + " with package name "
+        // 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.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_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.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 @@
     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 @@
                 .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 @@
             mSession.release();
             mSession = null;
         }
-        if (mRouter != null) {
-            mRouter.release();
-            mRouter = null;
-        }
     }
 
     public void setListener(Listener listener) {
@@ -278,63 +254,4 @@
             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 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 @@
     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;
             }