Merge "Implementation of data usage callbacks." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 9e96249..1ebec4e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8006,6 +8006,7 @@
     method public final int getColor(int);
     method public final android.content.res.ColorStateList getColorStateList(int);
     method public abstract android.content.ContentResolver getContentResolver();
+    method public abstract java.io.File getDataDir();
     method public abstract java.io.File getDatabasePath(java.lang.String);
     method public abstract java.io.File getDir(java.lang.String, int);
     method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8198,6 +8199,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
@@ -8534,7 +8536,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29365,11 +29366,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
@@ -31469,6 +31486,8 @@
   }
 
   protected static abstract interface ContactsContract.PhoneLookupColumns {
+    field public static final java.lang.String CONTACT_ID = "contact_id";
+    field public static final java.lang.String DATA_ID = "data_id";
     field public static final java.lang.String LABEL = "label";
     field public static final java.lang.String NORMALIZED_NUMBER = "normalized_number";
     field public static final java.lang.String NUMBER = "number";
@@ -32157,7 +32176,6 @@
     field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
     field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
     field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS";
-    field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
     field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
     field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
     field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
@@ -34077,6 +34095,7 @@
     method public boolean isDigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUserAuthenticationRequired();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public static final class KeyGenParameterSpec.Builder {
@@ -34099,6 +34118,7 @@
     method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
+    method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
   }
 
@@ -34118,6 +34138,7 @@
     method public boolean isInsideSecureHardware();
     method public boolean isUserAuthenticationRequired();
     method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public class KeyNotYetValidException extends java.security.InvalidKeyException {
@@ -34180,6 +34201,7 @@
     method public boolean isDigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUserAuthenticationRequired();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public static final class KeyProtection.Builder {
@@ -34195,6 +34217,7 @@
     method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
     method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
     method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
+    method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
     method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
   }
 
@@ -37611,6 +37634,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
diff --git a/api/system-current.txt b/api/system-current.txt
index c7b2604..55df364 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8297,6 +8297,7 @@
     method public final int getColor(int);
     method public final android.content.res.ColorStateList getColorStateList(int);
     method public abstract android.content.ContentResolver getContentResolver();
+    method public abstract java.io.File getDataDir();
     method public abstract java.io.File getDatabasePath(java.lang.String);
     method public abstract java.io.File getDir(java.lang.String, int);
     method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8499,6 +8500,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
@@ -8840,7 +8842,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -24282,19 +24283,26 @@
 package android.media.soundtrigger {
 
   public final class SoundTriggerDetector {
-    method public boolean startRecognition();
+    method public boolean startRecognition(int);
     method public boolean stopRecognition();
+    field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
+    field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
   }
 
-  public abstract class SoundTriggerDetector.Callback {
+  public static abstract class SoundTriggerDetector.Callback {
     ctor public SoundTriggerDetector.Callback();
     method public abstract void onAvailabilityChanged(int);
-    method public abstract void onDetected();
+    method public abstract void onDetected(android.media.soundtrigger.SoundTriggerDetector.EventPayload);
     method public abstract void onError();
     method public abstract void onRecognitionPaused();
     method public abstract void onRecognitionResumed();
   }
 
+  public static class SoundTriggerDetector.EventPayload {
+    method public android.media.AudioFormat getCaptureAudioFormat();
+    method public byte[] getTriggerAudio();
+  }
+
   public final class SoundTriggerManager {
     method public android.media.soundtrigger.SoundTriggerDetector createSoundTriggerDetector(java.util.UUID, android.media.soundtrigger.SoundTriggerDetector.Callback, android.os.Handler);
     method public void deleteModel(java.util.UUID);
@@ -26807,7 +26815,6 @@
     field public static final int REASON_UNSPECIFIED = -1; // 0xffffffff
     field public static final deprecated int REPORT_EVENT_AFTER_BUFFER_FULL = 0; // 0x0
     field public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; // 0x1
-    field public static final int REPORT_EVENT_CONTEXT_HUB = 8; // 0x8
     field public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; // 0x2
     field public static final int REPORT_EVENT_NO_BATCH = 4; // 0x4
     field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
@@ -31713,11 +31720,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
@@ -33848,6 +33871,8 @@
   }
 
   protected static abstract interface ContactsContract.PhoneLookupColumns {
+    field public static final java.lang.String CONTACT_ID = "contact_id";
+    field public static final java.lang.String DATA_ID = "data_id";
     field public static final java.lang.String LABEL = "label";
     field public static final java.lang.String NORMALIZED_NUMBER = "normalized_number";
     field public static final java.lang.String NUMBER = "number";
@@ -34638,7 +34663,6 @@
     field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
     field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
     field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS";
-    field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
     field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
     field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
     field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
@@ -36560,6 +36584,7 @@
     method public boolean isDigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUserAuthenticationRequired();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public static final class KeyGenParameterSpec.Builder {
@@ -36582,6 +36607,7 @@
     method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
+    method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
   }
 
@@ -36601,6 +36627,7 @@
     method public boolean isInsideSecureHardware();
     method public boolean isUserAuthenticationRequired();
     method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public class KeyNotYetValidException extends java.security.InvalidKeyException {
@@ -36663,6 +36690,7 @@
     method public boolean isDigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUserAuthenticationRequired();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public static final class KeyProtection.Builder {
@@ -36678,6 +36706,7 @@
     method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
     method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
     method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
+    method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
     method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
   }
 
@@ -40353,6 +40382,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
diff --git a/api/test-current.txt b/api/test-current.txt
index 74c6787..8fcb9bd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8009,6 +8009,7 @@
     method public final int getColor(int);
     method public final android.content.res.ColorStateList getColorStateList(int);
     method public abstract android.content.ContentResolver getContentResolver();
+    method public abstract java.io.File getDataDir();
     method public abstract java.io.File getDatabasePath(java.lang.String);
     method public abstract java.io.File getDir(java.lang.String, int);
     method public final android.graphics.drawable.Drawable getDrawable(int);
@@ -8202,6 +8203,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
@@ -8539,7 +8541,6 @@
     field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
     field public static final java.lang.String ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
-    field public static final java.lang.String ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
     field public static final java.lang.String ACTION_PACKAGES_SUSPENDED = "android.intent.action.PACKAGES_SUSPENDED";
     field public static final java.lang.String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
     field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
@@ -29375,11 +29376,27 @@
 
   public class StorageManager {
     method public java.lang.String getMountedObbPath(java.lang.String);
+    method public android.os.storage.StorageVolume getPrimaryVolume();
+    method public android.os.storage.StorageVolume[] getVolumeList();
     method public boolean isObbMounted(java.lang.String);
     method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
     method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
   }
 
+  public class StorageVolume implements android.os.Parcelable {
+    method public android.content.Intent createAccessIntent(java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getDescription(android.content.Context);
+    method public java.lang.String getState();
+    method public java.lang.String getUuid();
+    method public boolean isEmulated();
+    method public boolean isPrimary();
+    method public boolean isRemovable();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.storage.StorageVolume> CREATOR;
+    field public static final java.lang.String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
+  }
+
 }
 
 package android.preference {
@@ -31482,6 +31499,8 @@
   }
 
   protected static abstract interface ContactsContract.PhoneLookupColumns {
+    field public static final java.lang.String CONTACT_ID = "contact_id";
+    field public static final java.lang.String DATA_ID = "data_id";
     field public static final java.lang.String LABEL = "label";
     field public static final java.lang.String NORMALIZED_NUMBER = "normalized_number";
     field public static final java.lang.String NUMBER = "number";
@@ -32170,7 +32189,6 @@
     field public static final java.lang.String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
     field public static final java.lang.String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
     field public static final java.lang.String ACTION_SETTINGS = "android.settings.SETTINGS";
-    field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
     field public static final java.lang.String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
     field public static final java.lang.String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
     field public static final java.lang.String ACTION_SYNC_SETTINGS = "android.settings.SYNC_SETTINGS";
@@ -34092,6 +34110,7 @@
     method public boolean isDigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUserAuthenticationRequired();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public static final class KeyGenParameterSpec.Builder {
@@ -34114,6 +34133,7 @@
     method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
+    method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
   }
 
@@ -34133,6 +34153,7 @@
     method public boolean isInsideSecureHardware();
     method public boolean isUserAuthenticationRequired();
     method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public class KeyNotYetValidException extends java.security.InvalidKeyException {
@@ -34195,6 +34216,7 @@
     method public boolean isDigestsSpecified();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isUserAuthenticationRequired();
+    method public boolean isUserAuthenticationValidWhileOnBody();
   }
 
   public static final class KeyProtection.Builder {
@@ -34210,6 +34232,7 @@
     method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
     method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
     method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
+    method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
     method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
   }
 
@@ -37626,6 +37649,7 @@
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
     method public android.content.ContentResolver getContentResolver();
+    method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
     method public java.io.File getExternalCacheDir();
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 3385a17..980329f 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -807,8 +807,6 @@
     }
 
     /**
-     * AnimatorSet is only reversible when the set contains no sequential animation, and no child
-     * animators have a start delay.
      * @hide
      */
     @Override
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ea58e29..e3adbda 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -49,6 +49,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -3988,8 +3989,12 @@
             a.recycle();
             if (colorPrimary != 0) {
                 ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
-                td.setPrimaryColor(colorPrimary);
-                td.setBackgroundColor(colorBg);
+                if (Color.alpha(colorPrimary) == 0xFF) {
+                    td.setPrimaryColor(colorPrimary);
+                }
+                if (Color.alpha(colorBg) == 0xFF) {
+                    td.setBackgroundColor(colorBg);
+                }
                 setTaskDescription(td);
             }
         }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ab7d708..91eabcc 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.annotation.XmlRes;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Intent;
@@ -1759,7 +1760,7 @@
         return candidates;
     }
 
-    private static boolean isPackageCandidateVolume(
+    private boolean isPackageCandidateVolume(
             ContextImpl context, ApplicationInfo app, VolumeInfo vol) {
         final boolean forceAllowOnExternal = Settings.Global.getInt(
                 context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
@@ -1789,6 +1790,15 @@
             return app.isInternal();
         }
 
+        // Some apps can't be moved. (e.g. device admins)
+        try {
+            if (mPM.isPackageDeviceAdminOnAnyUser(app.packageName)) {
+                return false;
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Package manager has died", e);
+        }
+
         // Otherwise we can move to any private volume
         return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5e8d190..8884949 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -460,7 +460,7 @@
     private File getPreferencesDir() {
         synchronized (mSync) {
             if (mPreferencesDir == null) {
-                mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
+                mPreferencesDir = new File(getDataDir(), "shared_prefs");
             }
             return ensurePrivateDirExists(mPreferencesDir);
         }
@@ -525,7 +525,7 @@
     public File getFilesDir() {
         synchronized (mSync) {
             if (mFilesDir == null) {
-                mFilesDir = new File(getDataDirFile(), "files");
+                mFilesDir = new File(getDataDir(), "files");
             }
             return ensurePrivateDirExists(mFilesDir);
         }
@@ -535,7 +535,7 @@
     public File getNoBackupFilesDir() {
         synchronized (mSync) {
             if (mNoBackupFilesDir == null) {
-                mNoBackupFilesDir = new File(getDataDirFile(), "no_backup");
+                mNoBackupFilesDir = new File(getDataDir(), "no_backup");
             }
             return ensurePrivateDirExists(mNoBackupFilesDir);
         }
@@ -587,7 +587,7 @@
     public File getCacheDir() {
         synchronized (mSync) {
             if (mCacheDir == null) {
-                mCacheDir = new File(getDataDirFile(), "cache");
+                mCacheDir = new File(getDataDir(), "cache");
             }
             return ensurePrivateDirExists(mCacheDir);
         }
@@ -597,7 +597,7 @@
     public File getCodeCacheDir() {
         synchronized (mSync) {
             if (mCodeCacheDir == null) {
-                mCodeCacheDir = new File(getDataDirFile(), "code_cache");
+                mCodeCacheDir = new File(getDataDir(), "code_cache");
             }
             return ensurePrivateDirExists(mCodeCacheDir);
         }
@@ -724,7 +724,7 @@
                 if ("android".equals(getPackageName())) {
                     mDatabasesDir = new File("/data/system");
                 } else {
-                    mDatabasesDir = new File(getDataDirFile(), "databases");
+                    mDatabasesDir = new File(getDataDir(), "databases");
                 }
             }
             return ensurePrivateDirExists(mDatabasesDir);
@@ -1920,7 +1920,8 @@
         return mDisplayAdjustments;
     }
 
-    private File getDataDirFile() {
+    @Override
+    public File getDataDir() {
         if (mPackageInfo != null) {
             File res = null;
             if (isCredentialEncryptedStorage()) {
@@ -1947,7 +1948,7 @@
     public File getDir(String name, int mode) {
         checkMode(mode);
         name = "app_" + name;
-        File file = makeFilename(getDataDirFile(), name);
+        File file = makeFilename(getDataDir(), name);
         if (!file.exists()) {
             file.mkdir();
             setFilePermissionsFromMode(file.getPath(), mode,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 35b7c39..52631d1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -263,8 +263,8 @@
      * The view that will represent this notification in the notification list (which is pulled
      * down from the status bar).
      *
-     * As of N, this field is not used. The notification view is determined by the inputs to
-     * {@link Notification.Builder}; a custom RemoteViews can optionally be
+     * As of N, this field may be null. The notification view is determined by the inputs
+     * to {@link Notification.Builder}; a custom RemoteViews can optionally be
      * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}.
      */
     @Deprecated
@@ -275,7 +275,7 @@
      * opportunity to show more detail. The system UI may choose to show this
      * instead of the normal content view at its discretion.
      *
-     * As of N, this field is not used. The expanded notification view is determined by the
+     * As of N, this field may be null. The expanded notification view is determined by the
      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
      * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}.
      */
@@ -289,7 +289,7 @@
      * choose to show this as a heads-up notification, which will pop up so the user can see
      * it without leaving their current activity.
      *
-     * As of N, this field is not used. The heads-up notification view is determined by the
+     * As of N, this field may be null. The heads-up notification view is determined by the
      * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
      * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}.
      */
@@ -2129,8 +2129,23 @@
      * </pre>
      */
     public static class Builder {
+        /**
+         * @hide
+         */
+        public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT =
+                "android.rebuild.contentViewActionCount";
+        /**
+         * @hide
+         */
+        public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT
+                = "android.rebuild.bigViewActionCount";
+        /**
+         * @hide
+         */
+        public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
+                = "android.rebuild.hudViewActionCount";
+
         private static final int MAX_ACTION_BUTTONS = 3;
-        private static final float LARGE_TEXT_SCALE = 1.3f;
 
         private Context mContext;
         private Notification mN;
@@ -3566,19 +3581,6 @@
             return null;
         }
 
-        private void setBuilderContentView(Notification n, RemoteViews contentView) {
-            n.contentView = contentView;
-        }
-
-        private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) {
-            n.bigContentView = bigContentView;
-        }
-
-        private void setBuilderHeadsUpContentView(Notification n,
-                RemoteViews headsUpContentView) {
-            n.headsUpContentView = headsUpContentView;
-        }
-
         /**
          * @deprecated Use {@link #build()} instead.
          */
@@ -3606,6 +3608,28 @@
                 mStyle.buildStyled(mN);
             }
 
+            if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+                if (mN.contentView == null) {
+                    mN.contentView = makeContentView();
+                    mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
+                            mN.contentView.getSequenceNumber());
+                }
+                if (mN.bigContentView == null) {
+                    mN.bigContentView = makeBigContentView();
+                    if (mN.bigContentView != null) {
+                        mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
+                                mN.bigContentView.getSequenceNumber());
+                    }
+                }
+                if (mN.headsUpContentView == null) {
+                    mN.headsUpContentView = makeHeadsUpContentView();
+                    if (mN.headsUpContentView != null) {
+                        mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
+                                mN.headsUpContentView.getSequenceNumber());
+                    }
+                }
+            }
+
             if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
                 mN.flags |= FLAG_SHOW_LIGHTS;
             }
@@ -3623,6 +3647,40 @@
             return n;
         }
 
+        /**
+         * @hide
+         */
+        public static void stripForDelivery(Notification n) {
+            String templateClass = n.extras.getString(EXTRA_TEMPLATE);
+            if (TextUtils.isEmpty(templateClass)) {
+                return;
+            }
+            // Only strip views for known Styles because we won't know how to
+            // re-create them otherwise.
+            if (getNotificationStyleClass(templateClass) == null) {
+                return;
+            }
+            // Get rid of unmodified BuilderRemoteViews.
+            if (n.contentView instanceof BuilderRemoteViews &&
+                    n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) ==
+                            n.contentView.getSequenceNumber()) {
+                n.contentView = null;
+                n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT);
+            }
+            if (n.bigContentView instanceof BuilderRemoteViews &&
+                    n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) ==
+                            n.bigContentView.getSequenceNumber()) {
+                n.bigContentView = null;
+                n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT);
+            }
+            if (n.headsUpContentView instanceof BuilderRemoteViews &&
+                    n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) ==
+                            n.headsUpContentView.getSequenceNumber()) {
+                n.headsUpContentView = null;
+                n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
+            }
+        }
+
         private int getBaseLayoutResource() {
             return R.layout.notification_template_material_base;
         }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 1f17024..ff2cfd6 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -246,6 +246,7 @@
         }
         if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
         final Notification copy = notification.clone();
+        Builder.stripForDelivery(copy);
         try {
             service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                     copy, idOut, user.getIdentifier());
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5b8e09c..7e7c5ec 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1033,9 +1033,18 @@
      * @hide
      */
     public boolean packageHasActiveAdmins(String packageName) {
+        return packageHasActiveAdmins(packageName, myUserId());
+    }
+
+    /**
+     * Used by package administration code to determine if a package can be stopped
+     * or uninstalled.
+     * @hide
+     */
+    public boolean packageHasActiveAdmins(String packageName, int userId) {
         if (mService != null) {
             try {
-                return mService.packageHasActiveAdmins(packageName, myUserId());
+                return mService.packageHasActiveAdmins(packageName, userId);
             } catch (RemoteException e) {
                 Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
             }
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 63f1425..aeb3156 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -308,14 +308,31 @@
         final String packageName = getPackageName();
         final ApplicationInfo appInfo = getApplicationInfo();
 
-        String rootDir = new File(appInfo.dataDir).getCanonicalPath();
-        String filesDir = getFilesDir().getCanonicalPath();
-        String nobackupDir = getNoBackupFilesDir().getCanonicalPath();
-        String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
-        String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
-        String cacheDir = getCacheDir().getCanonicalPath();
-        String codeCacheDir = getCodeCacheDir().getCanonicalPath();
-        String libDir = (appInfo.nativeLibraryDir != null)
+        // System apps have control over where their default storage context
+        // is pointed, so we're always explicit when building paths.
+        final Context ceContext = createCredentialEncryptedStorageContext();
+        final String rootDir = ceContext.getDataDir().getCanonicalPath();
+        final String filesDir = ceContext.getFilesDir().getCanonicalPath();
+        final String noBackupDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
+        final String databaseDir = ceContext.getDatabasePath("foo").getParentFile()
+                .getCanonicalPath();
+        final String sharedPrefsDir = ceContext.getSharedPreferencesPath("foo").getParentFile()
+                .getCanonicalPath();
+        final String cacheDir = ceContext.getCacheDir().getCanonicalPath();
+        final String codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
+
+        final Context deContext = createDeviceEncryptedStorageContext();
+        final String deviceRootDir = deContext.getDataDir().getCanonicalPath();
+        final String deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
+        final String deviceNoBackupDir = deContext.getNoBackupFilesDir().getCanonicalPath();
+        final String deviceDatabaseDir = deContext.getDatabasePath("foo").getParentFile()
+                .getCanonicalPath();
+        final String deviceSharedPrefsDir = deContext.getSharedPreferencesPath("foo")
+                .getParentFile().getCanonicalPath();
+        final String deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
+        final String deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
+
+        final String libDir = (appInfo.nativeLibraryDir != null)
                 ? new File(appInfo.nativeLibraryDir).getCanonicalPath()
                 : null;
 
@@ -325,30 +342,48 @@
         final ArraySet<String> traversalExcludeSet = new ArraySet<String>();
 
         // Add the directories we always exclude.
+        traversalExcludeSet.add(filesDir);
+        traversalExcludeSet.add(noBackupDir);
+        traversalExcludeSet.add(databaseDir);
+        traversalExcludeSet.add(sharedPrefsDir);
         traversalExcludeSet.add(cacheDir);
         traversalExcludeSet.add(codeCacheDir);
-        traversalExcludeSet.add(nobackupDir);
+
+        traversalExcludeSet.add(deviceFilesDir);
+        traversalExcludeSet.add(deviceNoBackupDir);
+        traversalExcludeSet.add(deviceDatabaseDir);
+        traversalExcludeSet.add(deviceSharedPrefsDir);
+        traversalExcludeSet.add(deviceCacheDir);
+        traversalExcludeSet.add(deviceCodeCacheDir);
+
         if (libDir != null) {
             traversalExcludeSet.add(libDir);
         }
 
-        traversalExcludeSet.add(databaseDir);
-        traversalExcludeSet.add(sharedPrefsDir);
-        traversalExcludeSet.add(filesDir);
-
         // Root dir first.
         applyXmlFiltersAndDoFullBackupForDomain(
                 packageName, FullBackup.ROOT_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(rootDir);
 
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_ROOT_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceRootDir);
+
         // Data dir next.
         traversalExcludeSet.remove(filesDir);
         applyXmlFiltersAndDoFullBackupForDomain(
-                packageName, FullBackup.DATA_TREE_TOKEN, manifestIncludeMap,
+                packageName, FullBackup.FILES_TREE_TOKEN, manifestIncludeMap,
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(filesDir);
 
+        traversalExcludeSet.remove(deviceFilesDir);
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_FILES_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceFilesDir);
+
         // Database directory.
         traversalExcludeSet.remove(databaseDir);
         applyXmlFiltersAndDoFullBackupForDomain(
@@ -356,6 +391,12 @@
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(databaseDir);
 
+        traversalExcludeSet.remove(deviceDatabaseDir);
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_DATABASE_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceDatabaseDir);
+
         // SharedPrefs.
         traversalExcludeSet.remove(sharedPrefsDir);
         applyXmlFiltersAndDoFullBackupForDomain(
@@ -363,6 +404,12 @@
                 manifestExcludeSet, traversalExcludeSet, data);
         traversalExcludeSet.add(sharedPrefsDir);
 
+        traversalExcludeSet.remove(deviceSharedPrefsDir);
+        applyXmlFiltersAndDoFullBackupForDomain(
+                packageName, FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN, manifestIncludeMap,
+                manifestExcludeSet, traversalExcludeSet, data);
+        traversalExcludeSet.add(deviceSharedPrefsDir);
+
         // getExternalFilesDir() location associated with this app.  Technically there should
         // not be any files here if the app does not properly have permission to access
         // external storage, but edge cases happen. fullBackupFileTree() catches
@@ -445,27 +492,49 @@
      */
     public final void fullBackupFile(File file, FullBackupDataOutput output) {
         // Look up where all of our various well-defined dir trees live on this device
-        String mainDir;
-        String filesDir;
-        String nbFilesDir;
-        String dbDir;
-        String spDir;
-        String cacheDir;
-        String codeCacheDir;
-        String libDir;
+        final String rootDir;
+        final String filesDir;
+        final String nbFilesDir;
+        final String dbDir;
+        final String spDir;
+        final String cacheDir;
+        final String codeCacheDir;
+        final String deviceRootDir;
+        final String deviceFilesDir;
+        final String deviceNbFilesDir;
+        final String deviceDbDir;
+        final String deviceSpDir;
+        final String deviceCacheDir;
+        final String deviceCodeCacheDir;
+        final String libDir;
+
         String efDir = null;
         String filePath;
 
         ApplicationInfo appInfo = getApplicationInfo();
 
         try {
-            mainDir = new File(appInfo.dataDir).getCanonicalPath();
-            filesDir = getFilesDir().getCanonicalPath();
-            nbFilesDir = getNoBackupFilesDir().getCanonicalPath();
-            dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
-            spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
-            cacheDir = getCacheDir().getCanonicalPath();
-            codeCacheDir = getCodeCacheDir().getCanonicalPath();
+            // System apps have control over where their default storage context
+            // is pointed, so we're always explicit when building paths.
+            final Context ceContext = createCredentialEncryptedStorageContext();
+            rootDir = ceContext.getDataDir().getCanonicalPath();
+            filesDir = ceContext.getFilesDir().getCanonicalPath();
+            nbFilesDir = ceContext.getNoBackupFilesDir().getCanonicalPath();
+            dbDir = ceContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
+            spDir = ceContext.getSharedPreferencesPath("foo").getParentFile().getCanonicalPath();
+            cacheDir = ceContext.getCacheDir().getCanonicalPath();
+            codeCacheDir = ceContext.getCodeCacheDir().getCanonicalPath();
+
+            final Context deContext = createDeviceEncryptedStorageContext();
+            deviceRootDir = deContext.getDataDir().getCanonicalPath();
+            deviceFilesDir = deContext.getFilesDir().getCanonicalPath();
+            deviceNbFilesDir = deContext.getNoBackupFilesDir().getCanonicalPath();
+            deviceDbDir = deContext.getDatabasePath("foo").getParentFile().getCanonicalPath();
+            deviceSpDir = deContext.getSharedPreferencesPath("foo").getParentFile()
+                    .getCanonicalPath();
+            deviceCacheDir = deContext.getCacheDir().getCanonicalPath();
+            deviceCodeCacheDir = deContext.getCodeCacheDir().getCanonicalPath();
+
             libDir = (appInfo.nativeLibraryDir == null)
                     ? null
                     : new File(appInfo.nativeLibraryDir).getCanonicalPath();
@@ -489,8 +558,11 @@
 
         if (filePath.startsWith(cacheDir)
                 || filePath.startsWith(codeCacheDir)
-                || filePath.startsWith(libDir)
-                || filePath.startsWith(nbFilesDir)) {
+                || filePath.startsWith(nbFilesDir)
+                || filePath.startsWith(deviceCacheDir)
+                || filePath.startsWith(deviceCodeCacheDir)
+                || filePath.startsWith(deviceNbFilesDir)
+                || filePath.startsWith(libDir)) {
             Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
             return;
         }
@@ -504,11 +576,23 @@
             domain = FullBackup.SHAREDPREFS_TREE_TOKEN;
             rootpath = spDir;
         } else if (filePath.startsWith(filesDir)) {
-            domain = FullBackup.DATA_TREE_TOKEN;
+            domain = FullBackup.FILES_TREE_TOKEN;
             rootpath = filesDir;
-        } else if (filePath.startsWith(mainDir)) {
+        } else if (filePath.startsWith(rootDir)) {
             domain = FullBackup.ROOT_TREE_TOKEN;
-            rootpath = mainDir;
+            rootpath = rootDir;
+        } else if (filePath.startsWith(deviceDbDir)) {
+            domain = FullBackup.DEVICE_DATABASE_TREE_TOKEN;
+            rootpath = deviceDbDir;
+        } else if (filePath.startsWith(deviceSpDir)) {
+            domain = FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
+            rootpath = deviceSpDir;
+        } else if (filePath.startsWith(deviceFilesDir)) {
+            domain = FullBackup.DEVICE_FILES_TREE_TOKEN;
+            rootpath = deviceFilesDir;
+        } else if (filePath.startsWith(deviceRootDir)) {
+            domain = FullBackup.DEVICE_ROOT_TREE_TOKEN;
+            rootpath = deviceRootDir;
         } else if ((efDir != null) && filePath.startsWith(efDir)) {
             domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
             rootpath = efDir;
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 9ea2ba2..cdc80e3 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -55,13 +55,22 @@
 
     public static final String APK_TREE_TOKEN = "a";
     public static final String OBB_TREE_TOKEN = "obb";
+
     public static final String ROOT_TREE_TOKEN = "r";
-    public static final String DATA_TREE_TOKEN = "f";
+    public static final String FILES_TREE_TOKEN = "f";
     public static final String NO_BACKUP_TREE_TOKEN = "nb";
     public static final String DATABASE_TREE_TOKEN = "db";
     public static final String SHAREDPREFS_TREE_TOKEN = "sp";
-    public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
     public static final String CACHE_TREE_TOKEN = "c";
+
+    public static final String DEVICE_ROOT_TREE_TOKEN = "d_r";
+    public static final String DEVICE_FILES_TREE_TOKEN = "d_f";
+    public static final String DEVICE_NO_BACKUP_TREE_TOKEN = "d_nb";
+    public static final String DEVICE_DATABASE_TREE_TOKEN = "d_db";
+    public static final String DEVICE_SHAREDPREFS_TREE_TOKEN = "d_sp";
+    public static final String DEVICE_CACHE_TREE_TOKEN = "d_c";
+
+    public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
     public static final String SHARED_STORAGE_TOKEN = "shared";
 
     public static final String APPS_PREFIX = "apps/";
@@ -201,10 +210,18 @@
         private final File DATABASE_DIR;
         private final File ROOT_DIR;
         private final File SHAREDPREF_DIR;
-        private final File EXTERNAL_DIR;
         private final File CACHE_DIR;
         private final File NOBACKUP_DIR;
 
+        private final File DEVICE_FILES_DIR;
+        private final File DEVICE_DATABASE_DIR;
+        private final File DEVICE_ROOT_DIR;
+        private final File DEVICE_SHAREDPREF_DIR;
+        private final File DEVICE_CACHE_DIR;
+        private final File DEVICE_NOBACKUP_DIR;
+
+        private final File EXTERNAL_DIR;
+
         final int mFullBackupContent;
         final PackageManager mPackageManager;
         final String mPackageName;
@@ -214,7 +231,7 @@
          */
         String tokenToDirectoryPath(String domainToken) {
             try {
-                if (domainToken.equals(FullBackup.DATA_TREE_TOKEN)) {
+                if (domainToken.equals(FullBackup.FILES_TREE_TOKEN)) {
                     return FILES_DIR.getCanonicalPath();
                 } else if (domainToken.equals(FullBackup.DATABASE_TREE_TOKEN)) {
                     return DATABASE_DIR.getCanonicalPath();
@@ -224,14 +241,26 @@
                     return SHAREDPREF_DIR.getCanonicalPath();
                 } else if (domainToken.equals(FullBackup.CACHE_TREE_TOKEN)) {
                     return CACHE_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
+                    return NOBACKUP_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_FILES_TREE_TOKEN)) {
+                    return DEVICE_FILES_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_DATABASE_TREE_TOKEN)) {
+                    return DEVICE_DATABASE_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_ROOT_TREE_TOKEN)) {
+                    return DEVICE_ROOT_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN)) {
+                    return DEVICE_SHAREDPREF_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_CACHE_TREE_TOKEN)) {
+                    return DEVICE_CACHE_DIR.getCanonicalPath();
+                } else if (domainToken.equals(FullBackup.DEVICE_NO_BACKUP_TREE_TOKEN)) {
+                    return DEVICE_NOBACKUP_DIR.getCanonicalPath();
                 } else if (domainToken.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
                     if (EXTERNAL_DIR != null) {
                         return EXTERNAL_DIR.getCanonicalPath();
                     } else {
                         return null;
                     }
-                } else if (domainToken.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) {
-                    return NOBACKUP_DIR.getCanonicalPath();
                 }
                 // Not a supported location
                 Log.i(TAG, "Unrecognized domain " + domainToken);
@@ -257,12 +286,25 @@
             mFullBackupContent = context.getApplicationInfo().fullBackupContent;
             mPackageManager = context.getPackageManager();
             mPackageName = context.getPackageName();
-            FILES_DIR = context.getFilesDir();
-            DATABASE_DIR = context.getDatabasePath("foo").getParentFile();
-            ROOT_DIR = new File(context.getApplicationInfo().dataDir);
-            SHAREDPREF_DIR = context.getSharedPrefsFile("foo").getParentFile();
-            CACHE_DIR = context.getCacheDir();
-            NOBACKUP_DIR = context.getNoBackupFilesDir();
+
+            // System apps have control over where their default storage context
+            // is pointed, so we're always explicit when building paths.
+            final Context ceContext = context.createCredentialEncryptedStorageContext();
+            FILES_DIR = ceContext.getFilesDir();
+            DATABASE_DIR = ceContext.getDatabasePath("foo").getParentFile();
+            ROOT_DIR = ceContext.getDataDir();
+            SHAREDPREF_DIR = ceContext.getSharedPreferencesPath("foo").getParentFile();
+            CACHE_DIR = ceContext.getCacheDir();
+            NOBACKUP_DIR = ceContext.getNoBackupFilesDir();
+
+            final Context deContext = context.createDeviceEncryptedStorageContext();
+            DEVICE_FILES_DIR = deContext.getFilesDir();
+            DEVICE_DATABASE_DIR = deContext.getDatabasePath("foo").getParentFile();
+            DEVICE_ROOT_DIR = deContext.getDataDir();
+            DEVICE_SHAREDPREF_DIR = deContext.getSharedPreferencesPath("foo").getParentFile();
+            DEVICE_CACHE_DIR = deContext.getCacheDir();
+            DEVICE_NOBACKUP_DIR = deContext.getNoBackupFilesDir();
+
             if (android.os.Process.myUid() != Process.SYSTEM_UID) {
                 EXTERNAL_DIR = context.getExternalFilesDir(null);
             } else {
@@ -403,6 +445,13 @@
                                 Log.v(TAG_XML_PARSER, "...automatically generated "
                                         + canonicalJournalPath + ". Ignore if nonexistent.");
                             }
+                            final String canonicalWalPath =
+                                    canonicalFile.getCanonicalPath() + "-wal";
+                            activeSet.add(canonicalWalPath);
+                            if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
+                                Log.v(TAG_XML_PARSER, "...automatically generated "
+                                        + canonicalWalPath + ". Ignore if nonexistent.");
+                            }
                         }
 
                         // Special case for sharedpref files (not dirs) also add ".xml" suffix file.
@@ -485,11 +534,19 @@
             if ("root".equals(xmlDomain)) {
                 return FullBackup.ROOT_TREE_TOKEN;
             } else if ("file".equals(xmlDomain)) {
-                return FullBackup.DATA_TREE_TOKEN;
+                return FullBackup.FILES_TREE_TOKEN;
             } else if ("database".equals(xmlDomain)) {
                 return FullBackup.DATABASE_TREE_TOKEN;
             } else if ("sharedpref".equals(xmlDomain)) {
                 return FullBackup.SHAREDPREFS_TREE_TOKEN;
+            } else if ("device_root".equals(xmlDomain)) {
+                return FullBackup.DEVICE_ROOT_TREE_TOKEN;
+            } else if ("device_file".equals(xmlDomain)) {
+                return FullBackup.DEVICE_FILES_TREE_TOKEN;
+            } else if ("device_database".equals(xmlDomain)) {
+                return FullBackup.DEVICE_DATABASE_TREE_TOKEN;
+            } else if ("device_sharedpref".equals(xmlDomain)) {
+                return FullBackup.DEVICE_SHAREDPREFS_TREE_TOKEN;
             } else if ("external".equals(xmlDomain)) {
                 return FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
             } else {
@@ -542,6 +599,14 @@
                 return ROOT_DIR;
             } else if ("sharedpref".equals(domain)) {
                 return SHAREDPREF_DIR;
+            } else if ("device_file".equals(domain)) {
+                return DEVICE_FILES_DIR;
+            } else if ("device_database".equals(domain)) {
+                return DEVICE_DATABASE_DIR;
+            } else if ("device_root".equals(domain)) {
+                return DEVICE_ROOT_DIR;
+            } else if ("device_sharedpref".equals(domain)) {
+                return DEVICE_SHAREDPREF_DIR;
             } else if ("external".equals(domain)) {
                 return EXTERNAL_DIR;
             } else {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fff0c14..0cdbef0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -811,6 +811,25 @@
     public abstract File getSharedPreferencesPath(String name);
 
     /**
+     * Returns the absolute path to the directory on the filesystem where all
+     * private files belonging to this app are stored. This is the top-level
+     * directory under which {@link #getFilesDir()}, {@link #getCacheDir()}, etc
+     * are contained. Apps should <em>not</em> create any files or directories
+     * as direct children of this directory, since it's a reserved namespace
+     * belonging to the platform. Instead, use {@link #getDir(String, int)} or
+     * other storage APIs.
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * <p>
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path.
+     *
+     * @see #getDir(String, int)
+     */
+    public abstract File getDataDir();
+
+    /**
      * Returns the absolute path to the directory on the filesystem where files
      * created with {@link #openFileOutput} are stored.
      * <p>
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 61b87a9..323c9bf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -212,6 +212,11 @@
     }
 
     @Override
+    public File getDataDir() {
+        return mBase.getDataDir();
+    }
+
+    @Override
     public File getFilesDir() {
         return mBase.getFilesDir();
     }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b476a25..8f2b9c8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3187,38 +3187,6 @@
             ACTION_OPEN_DOCUMENT_TREE = "android.intent.action.OPEN_DOCUMENT_TREE";
 
     /**
-     * Activity Action: Give access to a standard storage directory after obtaining the user's
-     * approval.
-     * <p>
-     * When invoked, the system will ask the user to grant access to the requested directory (and
-     * its descendants).
-     * <p>
-     * To gain access to descendant (child, grandchild, etc) documents, use
-     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)} and
-     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
-     * <p>
-     * Input: full path to a standard directory, in the form of
-     * {@code STORAGE_ROOT + STANDARD_DIRECTORY}, where {@code STORAGE_ROOT} is the physical path of
-     * a storage container, and {@code STANDARD_DIRECTORY} is one of
-     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
-     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
-     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
-     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
-     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
-     * <p>
-     * For example, to open the "Pictures" folder in the default external storage, the intent's data
-     * would be: {@code Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
-     * Environment.DIRECTORY_PICTURES))}.
-     * <p>
-     * Output: The URI representing the requested directory tree.
-     *
-     * @see DocumentsContract
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String
-            ACTION_OPEN_EXTERNAL_DIRECTORY = "android.intent.action.OPEN_EXTERNAL_DIRECTORY";
-
-    /**
      * Broadcast Action: List of dynamic sensor is changed due to new sensor being connected or
      * exisiting sensor being disconnected.
      *
@@ -8952,7 +8920,6 @@
                 case ACTION_MEDIA_SCANNER_SCAN_FILE:
                 case ACTION_PACKAGE_NEEDS_VERIFICATION:
                 case ACTION_PACKAGE_VERIFIED:
-                case ACTION_OPEN_EXTERNAL_DIRECTORY: // TODO: temporary until bug 26742218 is fixed
                     // Ignore legacy actions
                     break;
                 default:
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ccb5f82..d6b674c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -537,4 +537,6 @@
     boolean setRequiredForSystemUser(String packageName, boolean systemUserApp);
 
     String getServicesSystemSharedLibraryPackageName();
+
+    boolean isPackageDeviceAdminOnAnyUser(String packageName);
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c9ee4f3..0967608 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1233,6 +1233,14 @@
     public static final int MOVE_FAILED_OPERATION_PENDING = -7;
 
     /**
+     * Error code that is passed to the {@link IPackageMoveObserver} if the
+     * specified package cannot be moved since it contains a device admin.
+     *
+     * @hide
+     */
+    public static final int MOVE_FAILED_DEVICE_ADMIN = -8;
+
+    /**
      * Flag parameter for {@link #movePackage} to indicate that
      * the package should be moved to internal storage if its
      * been installed on external media.
diff --git a/core/java/android/net/BaseDhcpStateMachine.java b/core/java/android/net/BaseDhcpStateMachine.java
deleted file mode 100644
index a25847d..0000000
--- a/core/java/android/net/BaseDhcpStateMachine.java
+++ /dev/null
@@ -1,35 +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 android.net;
-
-import com.android.internal.util.StateMachine;
-
-/**
- * Interface that must be implemented by DHCP state machines.
- *
- * This is an abstract class instead of a Java interface so that callers can just declare an object
- * of this type and be able to call all the methods defined by either StateMachine or this class.
- *
- * @hide
- */
-public abstract class BaseDhcpStateMachine extends StateMachine {
-    protected BaseDhcpStateMachine(String tag) {
-        super(tag);
-    }
-    public abstract void registerForPreDhcpNotification();
-    public abstract void doQuit();
-}
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
deleted file mode 100644
index 73ef78e..0000000
--- a/core/java/android/net/DhcpStateMachine.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2011 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.net;
-
-import com.android.internal.util.Protocol;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.DhcpResults;
-import android.net.NetworkUtils;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * StateMachine that interacts with the native DHCP client and can talk to
- * a controller that also needs to be a StateMachine
- *
- * The DhcpStateMachine provides the following features:
- * - Wakeup and renewal using the native DHCP client  (which will not renew
- *   on its own when the device is in suspend state and this can lead to device
- *   holding IP address beyond expiry)
- * - A notification right before DHCP request or renewal is started. This
- *   can be used for any additional setup before DHCP. For example, wifi sets
- *   BT-Wifi coex settings right before DHCP is initiated
- *
- * @hide
- */
-public class DhcpStateMachine extends BaseDhcpStateMachine {
-
-    private static final String TAG = "DhcpStateMachine";
-    private static final boolean DBG = false;
-
-
-    /* A StateMachine that controls the DhcpStateMachine */
-    private StateMachine mController;
-
-    private Context mContext;
-    private BroadcastReceiver mBroadcastReceiver;
-    private AlarmManager mAlarmManager;
-    private PendingIntent mDhcpRenewalIntent;
-    private PowerManager.WakeLock mDhcpRenewWakeLock;
-    private static final String WAKELOCK_TAG = "DHCP";
-
-    //Remember DHCP configuration from first request
-    private DhcpResults mDhcpResults;
-
-    private static final int DHCP_RENEW = 0;
-    private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
-
-    //Used for sanity check on setting up renewal
-    private static final int MIN_RENEWAL_TIME_SECS = 5 * 60;  // 5 minutes
-
-    private final String mInterfaceName;
-    private boolean mRegisteredForPreDhcpNotification = false;
-
-    private static final int BASE = Protocol.BASE_DHCP;
-
-    /* Commands from controller to start/stop DHCP */
-    public static final int CMD_START_DHCP                  = BASE + 1;
-    public static final int CMD_STOP_DHCP                   = BASE + 2;
-    public static final int CMD_RENEW_DHCP                  = BASE + 3;
-
-    /* Notification from DHCP state machine prior to DHCP discovery/renewal */
-    public static final int CMD_PRE_DHCP_ACTION             = BASE + 4;
-    /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
-     * success/failure */
-    public static final int CMD_POST_DHCP_ACTION            = BASE + 5;
-    /* Notification from DHCP state machine before quitting */
-    public static final int CMD_ON_QUIT                     = BASE + 6;
-
-    /* Command from controller to indicate DHCP discovery/renewal can continue
-     * after pre DHCP action is complete */
-    public static final int CMD_PRE_DHCP_ACTION_COMPLETE    = BASE + 7;
-
-    /* Command from ourselves to see if DHCP results are available */
-    private static final int CMD_GET_DHCP_RESULTS           = BASE + 8;
-
-    /* Message.arg1 arguments to CMD_POST_DHCP notification */
-    public static final int DHCP_SUCCESS = 1;
-    public static final int DHCP_FAILURE = 2;
-
-    private State mDefaultState = new DefaultState();
-    private State mStoppedState = new StoppedState();
-    private State mWaitBeforeStartState = new WaitBeforeStartState();
-    private State mRunningState = new RunningState();
-    private State mWaitBeforeRenewalState = new WaitBeforeRenewalState();
-    private State mPollingState = new PollingState();
-
-    private DhcpStateMachine(Context context, StateMachine controller, String intf) {
-        super(TAG);
-
-        mContext = context;
-        mController = controller;
-        mInterfaceName = intf;
-
-        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
-        Intent dhcpRenewalIntent = new Intent(ACTION_DHCP_RENEW, null);
-        mDhcpRenewalIntent = PendingIntent.getBroadcast(mContext, DHCP_RENEW, dhcpRenewalIntent, 0);
-
-        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
-        mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
-        mDhcpRenewWakeLock.setReferenceCounted(false);
-
-        mBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                //DHCP renew
-                if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
-                //Lock released after 40s in worst case scenario
-                mDhcpRenewWakeLock.acquire(40000);
-                sendMessage(CMD_RENEW_DHCP);
-            }
-        };
-        mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_DHCP_RENEW));
-
-        addState(mDefaultState);
-            addState(mStoppedState, mDefaultState);
-            addState(mWaitBeforeStartState, mDefaultState);
-            addState(mPollingState, mDefaultState);
-            addState(mRunningState, mDefaultState);
-            addState(mWaitBeforeRenewalState, mDefaultState);
-
-        setInitialState(mStoppedState);
-    }
-
-    public static DhcpStateMachine makeDhcpStateMachine(Context context, StateMachine controller,
-            String intf) {
-        DhcpStateMachine dsm = new DhcpStateMachine(context, controller, intf);
-        dsm.start();
-        return dsm;
-    }
-
-    /**
-     * This sends a notification right before DHCP request/renewal so that the
-     * controller can do certain actions before DHCP packets are sent out.
-     * When the controller is ready, it sends a CMD_PRE_DHCP_ACTION_COMPLETE message
-     * to indicate DHCP can continue
-     *
-     * This is used by Wifi at this time for the purpose of doing BT-Wifi coex
-     * handling during Dhcp
-     */
-    @Override
-    public void registerForPreDhcpNotification() {
-        mRegisteredForPreDhcpNotification = true;
-    }
-
-    /**
-     * Quit the DhcpStateMachine.
-     *
-     * @hide
-     */
-    @Override
-    public void doQuit() {
-        quit();
-    }
-
-    protected void onQuitting() {
-        mController.sendMessage(CMD_ON_QUIT);
-    }
-
-    class DefaultState extends State {
-        @Override
-        public void exit() {
-            mContext.unregisterReceiver(mBroadcastReceiver);
-        }
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            switch (message.what) {
-                case CMD_RENEW_DHCP:
-                    Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
-                    mDhcpRenewWakeLock.release();
-                    break;
-                default:
-                    Log.e(TAG, "Error! unhandled message  " + message);
-                    break;
-            }
-            return HANDLED;
-        }
-    }
-
-
-    class StoppedState extends State {
-        @Override
-        public void enter() {
-            if (DBG) Log.d(TAG, getName() + "\n");
-            if (!NetworkUtils.stopDhcp(mInterfaceName)) {
-                Log.e(TAG, "Failed to stop Dhcp on " + mInterfaceName);
-            }
-            mDhcpResults = null;
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            boolean retValue = HANDLED;
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            switch (message.what) {
-                case CMD_START_DHCP:
-                    if (mRegisteredForPreDhcpNotification) {
-                        /* Notify controller before starting DHCP */
-                        mController.sendMessage(CMD_PRE_DHCP_ACTION);
-                        transitionTo(mWaitBeforeStartState);
-                    } else {
-                        if (runDhcpStart()) {
-                            transitionTo(mRunningState);
-                        }
-                    }
-                    break;
-                case CMD_STOP_DHCP:
-                    //ignore
-                    break;
-                default:
-                    retValue = NOT_HANDLED;
-                    break;
-            }
-            return retValue;
-        }
-    }
-
-    class WaitBeforeStartState extends State {
-        @Override
-        public void enter() {
-            if (DBG) Log.d(TAG, getName() + "\n");
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            boolean retValue = HANDLED;
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            switch (message.what) {
-                case CMD_PRE_DHCP_ACTION_COMPLETE:
-                    if (runDhcpStart()) {
-                        transitionTo(mRunningState);
-                    } else {
-                        transitionTo(mPollingState);
-                    }
-                    break;
-                case CMD_STOP_DHCP:
-                    transitionTo(mStoppedState);
-                    break;
-                case CMD_START_DHCP:
-                    //ignore
-                    break;
-                default:
-                    retValue = NOT_HANDLED;
-                    break;
-            }
-            return retValue;
-        }
-    }
-
-    class PollingState extends State {
-        private static final long MAX_DELAY_SECONDS = 32;
-        private long delaySeconds;
-
-        private void scheduleNextResultsCheck() {
-            sendMessageDelayed(obtainMessage(CMD_GET_DHCP_RESULTS), delaySeconds * 1000);
-            delaySeconds *= 2;
-            if (delaySeconds > MAX_DELAY_SECONDS) {
-                delaySeconds = MAX_DELAY_SECONDS;
-            }
-        }
-
-        @Override
-        public void enter() {
-            if (DBG) Log.d(TAG, "Entering " + getName() + "\n");
-            delaySeconds = 1;
-            scheduleNextResultsCheck();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            boolean retValue = HANDLED;
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            switch (message.what) {
-                case CMD_GET_DHCP_RESULTS:
-                    if (DBG) Log.d(TAG, "GET_DHCP_RESULTS on " + mInterfaceName);
-                    if (dhcpSucceeded()) {
-                        transitionTo(mRunningState);
-                    } else {
-                        scheduleNextResultsCheck();
-                    }
-                    break;
-                case CMD_STOP_DHCP:
-                    transitionTo(mStoppedState);
-                    break;
-                default:
-                    retValue = NOT_HANDLED;
-                    break;
-            }
-            return retValue;
-        }
-
-        @Override
-        public void exit() {
-            if (DBG) Log.d(TAG, "Exiting " + getName() + "\n");
-            removeMessages(CMD_GET_DHCP_RESULTS);
-        }
-    }
-
-    class RunningState extends State {
-        @Override
-        public void enter() {
-            if (DBG) Log.d(TAG, getName() + "\n");
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            boolean retValue = HANDLED;
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            switch (message.what) {
-                case CMD_STOP_DHCP:
-                    mAlarmManager.cancel(mDhcpRenewalIntent);
-                    transitionTo(mStoppedState);
-                    break;
-                case CMD_RENEW_DHCP:
-                    if (mRegisteredForPreDhcpNotification) {
-                        /* Notify controller before starting DHCP */
-                        mController.sendMessage(CMD_PRE_DHCP_ACTION);
-                        transitionTo(mWaitBeforeRenewalState);
-                        //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
-                    } else {
-                        if (!runDhcpRenew()) {
-                            transitionTo(mStoppedState);
-                        }
-                        mDhcpRenewWakeLock.release();
-                    }
-                    break;
-                case CMD_START_DHCP:
-                    //ignore
-                    break;
-                default:
-                    retValue = NOT_HANDLED;
-            }
-            return retValue;
-        }
-    }
-
-    class WaitBeforeRenewalState extends State {
-        @Override
-        public void enter() {
-            if (DBG) Log.d(TAG, getName() + "\n");
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            boolean retValue = HANDLED;
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            switch (message.what) {
-                case CMD_STOP_DHCP:
-                    mAlarmManager.cancel(mDhcpRenewalIntent);
-                    transitionTo(mStoppedState);
-                    break;
-                case CMD_PRE_DHCP_ACTION_COMPLETE:
-                    if (runDhcpRenew()) {
-                       transitionTo(mRunningState);
-                    } else {
-                       transitionTo(mStoppedState);
-                    }
-                    break;
-                case CMD_START_DHCP:
-                    //ignore
-                    break;
-                default:
-                    retValue = NOT_HANDLED;
-                    break;
-            }
-            return retValue;
-        }
-        @Override
-        public void exit() {
-            mDhcpRenewWakeLock.release();
-        }
-    }
-
-    private boolean dhcpSucceeded() {
-        DhcpResults dhcpResults = new DhcpResults();
-        if (!NetworkUtils.getDhcpResults(mInterfaceName, dhcpResults)) {
-            return false;
-        }
-
-        if (DBG) Log.d(TAG, "DHCP results found for " + mInterfaceName);
-        long leaseDuration = dhcpResults.leaseDuration; //int to long conversion
-
-        //Sanity check for renewal
-        if (leaseDuration >= 0) {
-            //TODO: would be good to notify the user that his network configuration is
-            //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
-            if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
-                leaseDuration = MIN_RENEWAL_TIME_SECS;
-            }
-            //Do it a bit earlier than half the lease duration time
-            //to beat the native DHCP client and avoid extra packets
-            //48% for one hour lease time = 29 minutes
-            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    SystemClock.elapsedRealtime() +
-                    leaseDuration * 480, //in milliseconds
-                    mDhcpRenewalIntent);
-        } else {
-            //infinite lease time, no renewal needed
-        }
-
-        // Fill in any missing fields in dhcpResults from the previous results.
-        // If mDhcpResults is null (i.e. this is the first server response),
-        // this is a noop.
-        dhcpResults.updateFromDhcpRequest(mDhcpResults);
-        mDhcpResults = dhcpResults;
-        mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults)
-            .sendToTarget();
-        return true;
-    }
-
-    private boolean runDhcpStart() {
-        /* Stop any existing DHCP daemon before starting new */
-        NetworkUtils.stopDhcp(mInterfaceName);
-        mDhcpResults = null;
-
-        if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
-        if (!NetworkUtils.startDhcp(mInterfaceName) || !dhcpSucceeded()) {
-            Log.e(TAG, "DHCP request failed on " + mInterfaceName + ": " +
-                    NetworkUtils.getDhcpError());
-            mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
-                    .sendToTarget();
-            return false;
-        }
-        return true;
-    }
-
-    private boolean runDhcpRenew() {
-        if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
-        if (!NetworkUtils.startDhcpRenew(mInterfaceName) || !dhcpSucceeded()) {
-            Log.e(TAG, "DHCP renew failed on " + mInterfaceName + ": " +
-                    NetworkUtils.getDhcpError());
-            mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
-                    .sendToTarget();
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 70f9cc5..1085b1e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -339,7 +339,7 @@
      * <p>
      * Writing to this path requires the
      * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission,
-     * and starting in read access requires the
+     * and starting in {@link android.os.Build.VERSION_CODES#KITKAT}, read access requires the
      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
      * which is automatically granted if you hold the write permission.
      * <p>
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index e7dfbd7..97ee90d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -865,7 +865,12 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * Gets the list of shared/external storage volumes available to the current user.
+     *
+     * <p>It always contains the primary storage volume, plus any additional external volume(s)
+     * available in the device, such as SD cards or attached USB drives.
+     */
     public @NonNull StorageVolume[] getVolumeList() {
         return getVolumeList(mContext.getUserId(), 0);
     }
@@ -914,7 +919,9 @@
         return paths;
     }
 
-    /** {@hide} */
+    /**
+     * Gets the primary shared/external storage volume available to the current user.
+     */
     public @NonNull StorageVolume getPrimaryVolume() {
         return getPrimaryVolume(getVolumeList());
     }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1408202..d860c7d 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -16,11 +16,17 @@
 
 package android.os.storage;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.Intent;
 import android.net.TrafficStats;
+import android.net.Uri;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.DocumentsContract;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -29,14 +35,47 @@
 import java.io.File;
 
 /**
- * Information about a storage volume that may be mounted. This is a legacy
- * specialization of {@link VolumeInfo} which describes the volume for a
- * specific user.
- * <p>
- * This class may be deprecated in the future.
+ * Information about a shared/external storage volume for a specific user.
  *
- * @hide
+ * <p>
+ * A device always has one (and one only) primary storage volume, but it could have extra volumes,
+ * like SD cards and USB drives. This object represents the logical view of a storage
+ * volume for a specific user: different users might have different views for the same physical
+ * volume (for example, if the volume is a built-in emulated storage).
+ *
+ * <p>
+ * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
+ * verify its state.
+ *
+ * <p>
+ * Applications willing to read or write to this storage volume needs to get a permission from the
+ * user first, which can be achieved in the following ways:
+ *
+ * <ul>
+ * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
+ * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
+ * simpler API and narrows the access to the given directory (and its descendants).
+ * <li>To get access to any directory (and its descendants), they can use the Storage Acess
+ * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
+ * select this specific volume.
+ * <li>To get read and write access to the primary storage volume, applications can declare the
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
+ * latter including the former. This approach is discouraged, since users may be hesitant to grant
+ * broad access to all files contained on a storage device.
+ * </ul>
+ *
+ * <p>It can be obtained through {@link StorageManager#getVolumeList()} and
+ * {@link StorageManager#getPrimaryVolume()} and also as an extra in some broadcasts
+ * (see {@link #EXTRA_STORAGE_VOLUME}).
+ *
+ * <p>
+ * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
+ * storage semantics.
  */
+// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
+// user, but is now part of the public API.
 public class StorageVolume implements Parcelable {
 
     private final String mId;
@@ -53,14 +92,36 @@
     private final String mFsUuid;
     private final String mState;
 
-    // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
-    // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
-    // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
-    public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
+    /**
+     * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
+     * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
+     * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
+     * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
+     * contains a {@link StorageVolume}.
+     */
+    // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
+    public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
 
+    /**
+     * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
+
+    /**
+     * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
+     */
+    private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
+            "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
+
+    /** {@hide} */
     public static final int STORAGE_ID_INVALID = 0x00000000;
+    /** {@hide} */
     public static final int STORAGE_ID_PRIMARY = 0x00010001;
 
+    /** {@hide} */
     public StorageVolume(String id, int storageId, File path, String description, boolean primary,
             boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
             long maxFileSize, UserHandle owner, String fsUuid, String state) {
@@ -95,6 +156,7 @@
         mState = in.readString();
     }
 
+    /** {@hide} */
     public String getId() {
         return mId;
     }
@@ -103,17 +165,19 @@
      * Returns the mount path for the volume.
      *
      * @return the mount path
+     * @hide
      */
     public String getPath() {
         return mPath.toString();
     }
 
+    /** {@hide} */
     public File getPathFile() {
         return mPath;
     }
 
     /**
-     * Returns a user visible description of the volume.
+     * Returns a user-visible description of the volume.
      *
      * @return the volume description
      */
@@ -121,6 +185,10 @@
         return mDescription;
     }
 
+    /**
+     * Returns true if the volume is the primary shared/external storage, which is the volume
+     * backed by {@link Environment#getExternalStorageDirectory()}.
+     */
     public boolean isPrimary() {
         return mPrimary;
     }
@@ -148,6 +216,7 @@
      * this is also used for the storage_id column in the media provider.
      *
      * @return MTP storage ID
+     * @hide
      */
     public int getStorageId() {
         return mStorageId;
@@ -164,6 +233,7 @@
      * too close to full.
      *
      * @return MTP reserve space
+     * @hide
      */
     public int getMtpReserveSpace() {
         return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
@@ -173,6 +243,7 @@
      * Returns true if this volume can be shared via USB mass storage.
      *
      * @return whether mass storage is allowed
+     * @hide
      */
     public boolean allowMassStorage() {
         return mAllowMassStorage;
@@ -182,22 +253,28 @@
      * Returns maximum file size for the volume, or zero if it is unbounded.
      *
      * @return maximum file size
+     * @hide
      */
     public long getMaxFileSize() {
         return mMaxFileSize;
     }
 
+    /** {@hide} */
     public UserHandle getOwner() {
         return mOwner;
     }
 
-    public String getUuid() {
+    /**
+     * Gets the volume UUID, if any.
+     */
+    public @Nullable String getUuid() {
         return mFsUuid;
     }
 
     /**
      * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
      * parse or UUID is unknown.
+     * @hide
      */
     public int getFatVolumeId() {
         if (mFsUuid == null || mFsUuid.length() != 9) {
@@ -210,14 +287,57 @@
         }
     }
 
+    /** {@hide} */
     public String getUserLabel() {
         return mDescription;
     }
 
+    /**
+     * Returns the current state of the volume.
+     *
+     * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
+     *         {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
+     *         {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
+     *         {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
+     *         {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
+     */
     public String getState() {
         return mState;
     }
 
+    /**
+     * Builds an intent to give access to a standard storage directory after obtaining the user's
+     * approval.
+     * <p>
+     * When invoked, the system will ask the user to grant access to the requested directory (and
+     * its descendants). The result of the request will be returned to the activity through the
+     * {@code onActivityResult} method.
+     * <p>
+     * To gain access to descendants (child, grandchild, etc) documents, use
+     * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
+     * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
+     *
+     * <b>If your application only needs to store internal data, consider using
+     * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
+     * {@link Context#getExternalCacheDirs()}, or
+     * {@link Context#getExternalMediaDirs()}, which require no permissions to read or write.
+     *
+     * @param directoryName must be one of
+     * {@link Environment#DIRECTORY_MUSIC}, {@link Environment#DIRECTORY_PODCASTS},
+     * {@link Environment#DIRECTORY_RINGTONES}, {@link Environment#DIRECTORY_ALARMS},
+     * {@link Environment#DIRECTORY_NOTIFICATIONS}, {@link Environment#DIRECTORY_PICTURES},
+     * {@link Environment#DIRECTORY_MOVIES}, {@link Environment#DIRECTORY_DOWNLOADS},
+     * {@link Environment#DIRECTORY_DCIM}, or {@link Environment#DIRECTORY_DOCUMENTS}
+     *
+     * @see DocumentsContract
+     */
+    public Intent createAccessIntent(@NonNull String directoryName) {
+        final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
+        intent.putExtra(EXTRA_STORAGE_VOLUME, this);
+        intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
+        return intent;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (obj instanceof StorageVolume && mPath != null) {
@@ -234,11 +354,23 @@
 
     @Override
     public String toString() {
+        final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
+        if (mFsUuid != null) {
+            buffer.append(" (").append(mFsUuid).append(")");
+        }
+        return buffer.toString();
+    }
+
+    /** {@hide} */
+    // TODO(b/26742218): find out where toString() is called internally and replace these calls by
+    // dump().
+    public String dump() {
         final CharArrayWriter writer = new CharArrayWriter();
         dump(new IndentingPrintWriter(writer, "    ", 80));
         return writer.toString();
     }
 
+    /** {@hide} */
     public void dump(IndentingPrintWriter pw) {
         pw.println("StorageVolume:");
         pw.increaseIndent();
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index dfdd36d..904b393 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -4982,6 +4982,17 @@
      */
     protected interface PhoneLookupColumns {
         /**
+         *  The ID of the data row.
+         *  <P>Type: INTEGER</P>
+         */
+        public static final String DATA_ID = "data_id";
+        /**
+         * A reference to the {@link ContactsContract.Contacts#_ID} that this
+         * data belongs to.
+         * <P>Type: INTEGER</P>
+         */
+        public static final String CONTACT_ID = "contact_id";
+        /**
          * The phone number as the user entered it.
          * <P>Type: TEXT</P>
          */
@@ -5055,6 +5066,18 @@
      * <td>Contact ID.</td>
      * </tr>
      * <tr>
+     * <td>long</td>
+     * <td>{@link #CONTACT_ID}</td>
+     * <td>read-only</td>
+     * <td>Contact ID.</td>
+     * </tr>
+     * <tr>
+     * <td>long</td>
+     * <td>{@link #DATA_ID}</td>
+     * <td>read-only</td>
+     * <td>Data ID.</td>
+     * </tr>
+     * <tr>
      * <td>String</td>
      * <td>{@link #LOOKUP_KEY}</td>
      * <td>read-only</td>
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index b7f071d..3700098 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -39,6 +39,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.OnCloseListener;
 import android.os.RemoteException;
+import android.os.storage.StorageVolume;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -62,7 +63,8 @@
  * All client apps must hold a valid URI permission grant to access documents,
  * typically issued when a user makes a selection through
  * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT},
- * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or {@link Intent#ACTION_OPEN_EXTERNAL_DIRECTORY}.
+ * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or
+ * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}.
  *
  * @see DocumentsProvider
  */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5ab2b00..9632ff7 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1218,6 +1218,8 @@
      * Input: Nothing.
      * <p>
      * Output: Nothing.
+     *
+     * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e01f2a0..eb3d031 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -72,6 +72,7 @@
     public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
     public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
     public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
+    public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
 
     public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
     public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 982ed68..81cad79 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -40,6 +40,9 @@
     // TODO: Replace this with a general X509TrustManager and use duck-typing.
     private final TrustManagerImpl mDelegate;
     private final NetworkSecurityConfig mNetworkSecurityConfig;
+    private final Object mIssuersLock = new Object();
+
+    private X509Certificate[] mIssuers;
 
     public NetworkSecurityTrustManager(NetworkSecurityConfig config) {
         if (config == null) {
@@ -139,6 +142,19 @@
 
     @Override
     public X509Certificate[] getAcceptedIssuers() {
-        return mDelegate.getAcceptedIssuers();
+        // TrustManagerImpl only looks at the provided KeyStore and not the TrustedCertificateStore
+        // for getAcceptedIssuers, so implement it here instead of delegating.
+        synchronized (mIssuersLock) {
+            if (mIssuers == null) {
+                Set<TrustAnchor> anchors = mNetworkSecurityConfig.getTrustAnchors();
+                X509Certificate[] issuers = new X509Certificate[anchors.size()];
+                int i = 0;
+                for (TrustAnchor anchor : anchors) {
+                    issuers[i++] = anchor.certificate;
+                }
+                mIssuers = issuers;
+            }
+            return mIssuers.clone();
+        }
     }
 }
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 81c8c45..728f723 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -72,8 +72,7 @@
      * <p>The attribute contains a comma-separated set of signature scheme IDs.
      */
     public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
-    // TODO: Change the value when signing scheme finalized.
-    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 1234567890;
+    public static final int SF_ATTRIBUTE_ANDROID_APK_SIGNED_ID = 2;
 
     /**
      * Verifies APK Signature Scheme v2 signatures of the provided APK and returns the certificates
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index a4cb703..7017ff5 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -775,10 +775,6 @@
         mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
     }
 
-    public boolean isAttached() {
-        return mOwningView != null && mOwningView.mAttachInfo != null;
-    }
-
     public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimator animatorSet) {
         if (mOwningView == null || mOwningView.mAttachInfo == null) {
             throw new IllegalStateException("Cannot start this animator on a detached view!");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9e9ad67..70a0e01 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -21584,7 +21584,7 @@
      */
     public void setPointerIcon(PointerIcon pointerIcon) {
         mPointerIcon = pointerIcon;
-        if (mAttachInfo == null) {
+        if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
             return;
         }
         try {
@@ -22637,6 +22637,11 @@
         boolean mHighContrastText;
 
         /**
+         * Set to true if a pointer event is currently being handled.
+         */
+        boolean mHandlingPointerEvent;
+
+        /**
          * Global to the view hierarchy used as a temporary for dealing with
          * x/y points in the transparent region computations.
          */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 96853e0..0517788 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1606,10 +1606,6 @@
                         frame.height() < desiredWindowHeight && frame.height() != mHeight));
         windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
 
-        // If the backdrop frame doesn't equal to a frame, we are starting a resize operation, so
-        // force it to be resized.
-        windowShouldResize |= !mPendingBackDropFrame.equals(mWinFrame);
-
         // If the activity was just relaunched, it might have unfrozen the task bounds (while
         // relaunching), so we need to force a call into window manager to pick up the latest
         // bounds.
@@ -4315,6 +4311,24 @@
         private int processPointerEvent(QueuedInputEvent q) {
             final MotionEvent event = (MotionEvent)q.mEvent;
 
+            mAttachInfo.mUnbufferedDispatchRequested = false;
+            final View eventTarget =
+                    (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
+                            mCapturingView : mView;
+            mAttachInfo.mHandlingPointerEvent = true;
+            boolean handled = eventTarget.dispatchPointerEvent(event);
+            maybeUpdatePointerIcon(event);
+            mAttachInfo.mHandlingPointerEvent = false;
+            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
+                mUnbufferedInputDispatch = true;
+                if (mConsumeBatchedInputScheduled) {
+                    scheduleConsumeBatchedInputImmediately();
+                }
+            }
+            return handled ? FINISH_HANDLED : FORWARD;
+        }
+
+        private void maybeUpdatePointerIcon(MotionEvent event) {
             if (event.getPointerCount() == 1
                     && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
                 if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
@@ -4331,19 +4345,6 @@
                     }
                 }
             }
-
-            mAttachInfo.mUnbufferedDispatchRequested = false;
-            final View eventTarget =
-                    (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
-                            mCapturingView : mView;
-            boolean handled = eventTarget.dispatchPointerEvent(event);
-            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
-                mUnbufferedInputDispatch = true;
-                if (mConsumeBatchedInputScheduled) {
-                    scheduleConsumeBatchedInputImmediately();
-                }
-            }
-            return handled ? FINISH_HANDLED : FORWARD;
         }
 
         private int processTrackballEvent(QueuedInputEvent q) {
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 1d6e52c..05fd4c8 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -69,10 +69,8 @@
     private static final int[] ATTRS_TEXT_COLOR = new int[] {R.attr.textColor};
     private static final int[] ATTRS_DISABLED_ALPHA = new int[] {R.attr.disabledAlpha};
 
-    // LayoutLib relies on these constants. Change TimePickerClockDelegate_Delegate if
-    // modifying these.
-    static final int AM = 0;
-    static final int PM = 1;
+    private static final int AM = 0;
+    private static final int PM = 1;
 
     private static final int HOURS_IN_HALF_DAY = 12;
 
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index 9de4a6c..f4c18c3 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -33,10 +33,11 @@
 
     void deleteSoundModel(in ParcelUuid soundModelId);
 
-    void startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+    int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
+         in SoundTrigger.RecognitionConfig config);
 
     /**
      * Stops recognition.
      */
-    void stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+    int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 4c63941..62e149a 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -985,7 +985,7 @@
             }
         }
 
-        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+        public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
             return createEnabledInputMethodListLocked(
                     getEnabledInputMethodsAndSubtypeListLocked());
         }
@@ -1092,7 +1092,7 @@
             return isRemoved;
         }
 
-        private List<InputMethodInfo> createEnabledInputMethodListLocked(
+        private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
                 List<Pair<String, ArrayList<String>>> imsList) {
             final ArrayList<InputMethodInfo> res = new ArrayList<>();
             for (Pair<String, ArrayList<String>> ims: imsList) {
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index a106f48..5992f7a 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -64,5 +64,6 @@
     public static final int BASE_NETWORK_AGENT                                      = 0x00081000;
     public static final int BASE_NETWORK_MONITOR                                    = 0x00082000;
     public static final int BASE_NETWORK_FACTORY                                    = 0x00083000;
+    public static final int BASE_ETHERNET                                           = 0x00084000;
     //TODO: define all used protocols
 }
diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index bcd0b60..0c30fdc 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -212,17 +212,16 @@
 
     android::Parcel* p = android::parcelForJavaObject(env, parcel);
 
-    const size_t size = p->readInt32();
-    const void* regionData = p->readInplace(size);
-    if (regionData == nullptr) {
+    std::vector<int32_t> rects;
+    p->readInt32Vector(&rects);
+
+    if ((rects.size() % 4) != 0) {
         return 0;
     }
 
     SkRegion* region = new SkRegion;
-    size_t actualSize = region->readFromMemory(regionData, size);
-    if (size != actualSize) {
-        delete region;
-        return 0;
+    for (size_t x = 0; x + 4 <= rects.size(); x += 4) {
+        region->op(rects[x], rects[x+1], rects[x+2], rects[x+3], SkRegion::kUnion_Op);
     }
 
     return reinterpret_cast<jlong>(region);
@@ -237,19 +236,18 @@
 
     android::Parcel* p = android::parcelForJavaObject(env, parcel);
 
-    const size_t size = region->writeToMemory(nullptr);
-    p->writeInt32(size);
-    void* dst = p->writeInplace(size);
-    if (dst == nullptr) {
-        ALOGE("Region.writeToParcel could not write %zi bytes", size);
-        return JNI_FALSE;
-    }
-    const size_t sizeWritten = region->writeToMemory(dst);
-    if (sizeWritten != size) {
-        ALOGE("SkRegion::writeToMemory should have written %zi bytes but wrote %zi",
-                size, sizeWritten);
+    std::vector<int32_t> rects;
+    SkRegion::Iterator it(*region);
+    while (!it.done()) {
+        const SkIRect& r = it.rect();
+        rects.push_back(r.fLeft);
+        rects.push_back(r.fTop);
+        rects.push_back(r.fRight);
+        rects.push_back(r.fBottom);
+        it.next();
     }
 
+    p->writeInt32Vector(rects);
     return JNI_TRUE;
 }
 
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
index 14badb7..7a3c598 100644
--- a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -43,13 +43,12 @@
     return env;
 }
 
-static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener, jint id) {
+static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) {
     class AnimationListenerBridge : public AnimationListener {
     public:
-        AnimationListenerBridge(JNIEnv* env, jobject finishListener, jint id) {
+        AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
             mFinishListener = env->NewGlobalRef(finishListener);
             env->GetJavaVM(&mJvm);
-            mId = id;
         }
 
         virtual ~AnimationListenerBridge() {
@@ -64,7 +63,7 @@
             env->CallStaticVoidMethod(
                     gVectorDrawableAnimatorClassInfo.clazz,
                     gVectorDrawableAnimatorClassInfo.callOnFinished,
-                    mFinishListener, mId);
+                    mFinishListener);
             releaseJavaObject();
         }
 
@@ -77,9 +76,8 @@
 
         JavaVM* mJvm;
         jobject mFinishListener;
-        jint mId;
     };
-    return new AnimationListenerBridge(env, finishListener, id);
+    return new AnimationListenerBridge(env, finishListener);
 }
 
 static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr,
@@ -144,16 +142,15 @@
     holder->setPropertyDataSource(propertyData, length);
     env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
 }
-static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
+static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
     PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
-    AnimationListener* listener = createAnimationListener(env, finishListener, id);
+    // TODO: keep a ref count in finish listener
+    AnimationListener* listener = createAnimationListener(env, finishListener);
     set->start(listener);
 }
 
-static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
-    PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
-    AnimationListener* listener = createAnimationListener(env, finishListener, id);
-    set->reverse(listener);
+static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+    // TODO: implement reverse
 }
 
 static void end(JNIEnv*, jobject, jlong animatorSetPtr) {
@@ -175,8 +172,8 @@
     {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
     {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
     {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
-    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)start},
-    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)reverse},
+    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start},
+    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse},
     {"nEnd", "!(J)V", (void*)end},
     {"nReset", "!(J)V", (void*)reset},
 };
@@ -189,7 +186,7 @@
 
     gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
             env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished",
-            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V");
+            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V");
     return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable",
             gMethods, NELEM(gMethods));
 }
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index c9eac79..0926e9b 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -184,7 +184,7 @@
 
 static void end(JNIEnv* env, jobject clazz, jlong animatorPtr) {
     BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
-    animator->cancel();
+    animator->end();
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index ac77007..cd2c0d6 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "ThreadedRenderer"
 
 #include <algorithm>
+#include <atomic>
 
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
@@ -231,28 +232,13 @@
 
 class NotifyHandler : public MessageHandler {
 public:
-    NotifyHandler(JavaVM* vm) : mVm(vm) {}
-
-    void setObserver(ObserverProxy* observer) {
-        mObserver = observer;
-    }
-
-    void setBuffer(BufferPool::Buffer* buffer) {
-        mBuffer = buffer;
-    }
-
-    void setDropCount(int dropCount) {
-        mDropCount = dropCount;
-    }
+    NotifyHandler(JavaVM* vm, ObserverProxy* observer) : mVm(vm), mObserver(observer) {}
 
     virtual void handleMessage(const Message& message);
 
 private:
-    JavaVM* mVm;
-
-    sp<ObserverProxy> mObserver;
-    BufferPool::Buffer* mBuffer = nullptr;
-    int mDropCount = 0;
+    JavaVM* const mVm;
+    ObserverProxy* const mObserver;
 };
 
 static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
@@ -265,6 +251,9 @@
     return reinterpret_cast<jlongArray>(buffer);
 }
 
+/*
+ * Implements JNI layer for hwui frame metrics reporting.
+ */
 class ObserverProxy : public FrameMetricsObserver {
 public:
     ObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
@@ -284,7 +273,7 @@
         mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
         LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
 
-        mMessageHandler = new NotifyHandler(mVm);
+        mMessageHandler = new NotifyHandler(mVm, this);
         LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
                 "OOM: unable to allocate NotifyHandler");
     }
@@ -298,18 +287,53 @@
         return mObserverWeak;
     }
 
-    virtual void notify(BufferPool::Buffer* buffer, int dropCount) {
-        buffer->incRef();
-        mMessageHandler->setBuffer(buffer);
-        mMessageHandler->setObserver(this);
-        mMessageHandler->setDropCount(dropCount);
-        mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+    bool getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount) {
+        FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
+
+        if (elem.hasData.load()) {
+            env->SetLongArrayRegion(sink, 0, kBufferSize, elem.buffer);
+            *dropCount = elem.dropCount;
+            mNextInQueue = (mNextInQueue + 1) % kRingSize;
+            elem.hasData = false;
+            return true;
+        }
+
+        return false;
+    }
+
+    virtual void notify(const int64_t* stats) {
+        FrameMetricsNotification& elem = mRingBuffer[mNextFree];
+
+        if (!elem.hasData.load()) {
+            memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
+
+            elem.dropCount = mDroppedReports;
+            mDroppedReports = 0;
+
+            incStrong(nullptr);
+            mNextFree = (mNextFree + 1) % kRingSize;
+            elem.hasData = true;
+
+            mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+        } else {
+            mDroppedReports++;
+        }
     }
 
 private:
     static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
+    static constexpr int kRingSize = 3;
 
-    JavaVM* mVm;
+    class FrameMetricsNotification {
+    public:
+        FrameMetricsNotification() : hasData(false) {}
+
+        std::atomic_bool hasData;
+        int64_t buffer[kBufferSize];
+        int dropCount = 0;
+    };
+
+    JavaVM* const mVm;
     jweak mObserverWeak;
     jobject mJavaBufferGlobal;
 
@@ -317,28 +341,28 @@
     sp<NotifyHandler> mMessageHandler;
     Message mMessage;
 
+    int mNextFree = 0;
+    int mNextInQueue = 0;
+    FrameMetricsNotification mRingBuffer[kRingSize];
+
+    int mDroppedReports = 0;
 };
 
 void NotifyHandler::handleMessage(const Message& message) {
     JNIEnv* env = getenv(mVm);
 
-    ObserverProxy* observer = mObserver.get();
-    LOG_ALWAYS_FATAL_IF(observer == nullptr, "received message with no observer configured");
-    LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "received message with no data to report");
-
-    jobject target = env->NewLocalRef(observer->getObserverReference());
+    jobject target = env->NewLocalRef(mObserver->getObserverReference());
 
     if (target != nullptr) {
         jlongArray javaBuffer = get_metrics_buffer(env, target);
-        env->SetLongArrayRegion(javaBuffer,
-                0, mBuffer->getSize(), mBuffer->getBuffer());
-        env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback,
-                mDropCount);
+        int dropCount = 0;
+        while (mObserver->getNextBuffer(env, javaBuffer, &dropCount)) {
+            env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback, dropCount);
+        }
         env->DeleteLocalRef(target);
     }
 
-    mBuffer->release();
-    mObserver.clear();
+    mObserver->decStrong(nullptr);
 }
 
 static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7699da7..9b989b9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -237,12 +237,21 @@
 
     <protected-broadcast android:name="android.net.nsd.STATE_CHANGED" />
 
-    <protected-broadcast android:name="android.nfc.action.LLCP_LINK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" />
+    <protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" />
+    <protected-broadcast android:name="com.android.nfc.action.LLCP_UP" />
+    <protected-broadcast android:name="com.android.nfc.action.LLCP_DOWN" />
+    <protected-broadcast android:name="com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG" />
+    <protected-broadcast android:name="com.android.nfc.handover.action.ALLOW_CONNECT" />
+    <protected-broadcast android:name="com.android.nfc.handover.action.DENY_CONNECT" />
     <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_ON_DETECTED" />
     <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED" />
     <protected-broadcast android:name="com.android.nfc_extras.action.AID_SELECTED" />
-
-    <protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" />
+    <!-- For NFC to BT handover -->
+    <protected-broadcast android:name="android.btopp.intent.action.WHITELIST_DEVICE" />
+    <protected-broadcast android:name="android.btopp.intent.action.STOP_HANDOVER_TRANSFER" />
+    <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND" />
+    <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE" />
 
     <protected-broadcast android:name="android.intent.action.CLEAR_DNS_CACHE" />
     <protected-broadcast android:name="android.intent.action.PROXY_CHANGE" />
diff --git a/core/res/res/layout/app_error_dialog.xml b/core/res/res/layout/app_error_dialog.xml
index 824f97f..b9531f4 100644
--- a/core/res/res/layout/app_error_dialog.xml
+++ b/core/res/res/layout/app_error_dialog.xml
@@ -66,7 +66,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/aerr_mute"
-            android:drawableStart="@drawable/ic_close"
+            android:drawableStart="@drawable/ic_eject_24dp"
             style="@style/aerr_list_item"
     />
 
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index 398f52d..548ee05 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -21,6 +21,7 @@
     android:layout_width="wrap_content"
     android:layout_height="48dp"
     android:layout_gravity="center"
+    android:gravity="start|center_vertical"
     android:layout_marginStart="4dp"
     android:textColor="@color/notification_default_color"
     android:singleLine="true"
diff --git a/core/res/res/layout/notification_material_action_tombstone.xml b/core/res/res/layout/notification_material_action_tombstone.xml
index 976448b..1f59ea0 100644
--- a/core/res/res/layout/notification_material_action_tombstone.xml
+++ b/core/res/res/layout/notification_material_action_tombstone.xml
@@ -18,16 +18,15 @@
 <Button xmlns:android="http://schemas.android.com/apk/res/android"
     style="@android:style/Widget.Material.Light.Button.Borderless.Small"
     android:id="@+id/action0"
-    android:layout_width="0dp"
+    android:layout_width="wrap_content"
     android:layout_height="48dp"
-    android:layout_weight="1"
+    android:layout_marginStart="4dp"
+    android:layout_gravity="center"
     android:gravity="start|center_vertical"
-    android:drawablePadding="8dp"
-    android:paddingStart="8dp"
     android:textColor="#555555"
-    android:textSize="@dimen/notification_text_size"
     android:singleLine="true"
     android:ellipsize="end"
     android:alpha="0.5"
     android:enabled="false"
+    android:background="@drawable/notification_material_action_background"
     />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index be8577a..4480944 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5030,7 +5030,7 @@
         <attr name="firstDayOfWeek" format="integer" />
         <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->
         <attr name="minDate" />
-        <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. -->
+        <!-- The maximal date shown by this calendar view in mm/dd/yyyy format. -->
         <attr name="maxDate" />
         <!-- The text appearance for the month and year in the calendar header. -->
         <attr name="monthTextAppearance" format="reference" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 078aa41..1f0e96d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2608,8 +2608,8 @@
     <string name="aerr_report">Send feedback</string>
     <!-- Button that closes a crashed application -->
     <string name="aerr_close">Close</string>
-    <!-- Button that mutes further crashes of the crashed application-->
-    <string name="aerr_mute">Mute</string>
+    <!-- Button that mutes further crashes of the crashed application. Note that this only appears on engineering builds. -->
+    <string name="aerr_mute">Mute until device restarts</string>
     <!-- Button that waits a bit more for an unresponsive app -->
     <string name="aerr_wait">Wait</string>
     <!-- Button that closes an unresponsive application -->
@@ -2922,9 +2922,9 @@
     <!-- Title of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
     <string name="share_remote_bugreport_notification_title">Share bug report?</string>
     <!-- Title of notification shown to indicate that bug report is still being collected after sharing was accepted. -->
-    <string name="sharing_remote_bugreport_notification_title">Sharing bug report</string>
+    <string name="sharing_remote_bugreport_notification_title">Sharing bug report\u2026</string>
     <!-- Message of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
-    <string name="share_remote_bugreport_notification_message">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared. This may temporarily slow down your device.</string>
+    <string name="share_remote_bugreport_notification_message">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared and your device may temporarily slow down.</string>
     <!-- Message of notification shown to ask for user consent for sharing a bugreport that was requested remotely by the IT administrator. -->
     <string name="share_finished_remote_bugreport_notification_message">Your IT admin requested a bug report to help troubleshoot this device. Apps and data may be shared.</string>
     <!-- Message of notification shown to shown to indicate that bug report is still being collected after sharing was accepted. -->
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index c3afbf6..3869cd2 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -66,7 +66,7 @@
         assertEquals("Excluding files when there was no <exclude/> tag.", 0, excludesSet.size());
         assertEquals("Unexpected number of <include/>s", 1, includeMap.size());
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
         assertEquals("Invalid path parsed for <include/>",
                 new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
@@ -99,7 +99,7 @@
         FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
         bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
         assertEquals("Invalid path parsed for <include/>",
                 new File(mContext.getFilesDir(), "include.txt").getCanonicalPath(),
@@ -128,15 +128,16 @@
         FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
         bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
         assertEquals("Invalid path parsed for <include/>",
                 new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(),
                 fileDomainIncludes.iterator().next());
 
         Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
+        // Three expected here because of "-journal" and "-wal" files
         assertEquals("Didn't find expected database domain include.",
-                2, databaseDomainIncludes.size()); // two expected here because of "-journal" file
+                3, databaseDomainIncludes.size());
         assertTrue("Invalid path parsed for <include/>",
                 databaseDomainIncludes.contains(
                         new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
@@ -147,6 +148,12 @@
                                 mContext.getDatabasePath("foo").getParentFile(),
                                 "include2.txt-journal")
                                 .getCanonicalPath()));
+        assertTrue("Invalid path parsed for <include/>",
+                databaseDomainIncludes.contains(
+                        new File(
+                                mContext.getDatabasePath("foo").getParentFile(),
+                                "include2.txt-wal")
+                                .getCanonicalPath()));
 
         List<String> sharedPrefDomainIncludes = new ArrayList<String>(
                 includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN));
@@ -168,7 +175,7 @@
                 sharedPrefDomainIncludes.get(2));
 
 
-        assertEquals("Unexpected number of <exclude/>s", 6, excludesSet.size());
+        assertEquals("Unexpected number of <exclude/>s", 7, excludesSet.size());
         // Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
         // sort lexicographically.
         List<String> arrayedSet = new ArrayList<String>(excludesSet);
@@ -183,20 +190,24 @@
                         .getCanonicalPath(),
                 arrayedSet.get(1));
         assertEquals("Invalid path parsed for <exclude/>",
-                new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
+                new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-wal")
+                        .getCanonicalPath(),
                 arrayedSet.get(2));
         assertEquals("Invalid path parsed for <exclude/>",
+                new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
+                arrayedSet.get(3));
+        assertEquals("Invalid path parsed for <exclude/>",
                 new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3")
                         .getCanonicalPath(),
-                arrayedSet.get(3));
+                arrayedSet.get(4));
         assertEquals("Invalid path parsed for <exclude/>",
                 new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.xml")
                         .getCanonicalPath(),
-                arrayedSet.get(4));
+                arrayedSet.get(5));
         assertEquals("Invalid path parsed for <exclude/>",
                 new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude4.xml")
                         .getCanonicalPath(),
-                arrayedSet.get(5));
+                arrayedSet.get(6));
     }
 
     public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception {
@@ -247,7 +258,7 @@
 
         assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
     }
     public void testDoubleDotInPath_isIgnored() throws Exception {
@@ -261,7 +272,7 @@
 
         assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
 
-        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
+        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
         assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
     }
 
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
index edeecb2..26f768b 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
@@ -29,6 +29,8 @@
 
 LOCAL_DEX_PREOPT := false
 
+LOCAL_JAVACFLAGS := -nowarn
+
 mainDexList:= \
     $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
index 5e931bc..8d065a4 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/src/com/android/multidexlegacyandexception/Test.java
@@ -15,14 +15,13 @@
  */
 package com.android.multidexlegacyandexception;
 
-import android.test.ActivityInstrumentationTestCase2;
-
 /**
  * Run the tests with: <code>adb shell am instrument -w
  com.android.multidexlegacyandexception/android.test.InstrumentationTestRunner
 </code>
  */
-public class Test extends ActivityInstrumentationTestCase2<MainActivity> {
+@SuppressWarnings("deprecation")
+public class Test extends android.test.ActivityInstrumentationTestCase2<MainActivity> {
     public Test() {
         super(MainActivity.class);
     }
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 77748a8..af8ccf5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -238,6 +238,9 @@
             mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
         }
         mAnimatedVectorState.mVectorDrawable.draw(canvas);
+        if (isStarted()) {
+            invalidateSelf();
+        }
     }
 
     @Override
@@ -608,6 +611,10 @@
         return mAnimatorSet.isRunning();
     }
 
+    private boolean isStarted() {
+        return mAnimatorSet.isStarted();
+    }
+
     /**
      * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
      */
@@ -619,6 +626,12 @@
     @Override
     public void start() {
         ensureAnimatorSet();
+
+        // If any one of the animator has not ended, do nothing.
+        if (isStarted()) {
+            return;
+        }
+
         mAnimatorSet.start();
         invalidateSelf();
     }
@@ -639,7 +652,6 @@
     @Override
     public void stop() {
         mAnimatorSet.end();
-        invalidateSelf();
     }
 
     /**
@@ -762,9 +774,6 @@
      * @hide
      */
     public static class VectorDrawableAnimator {
-        private static final int NONE = 0;
-        private static final int START_ANIMATION = 1;
-        private static final int REVERSE_ANIMATION = 2;
         private AnimatorListener mListener = null;
         private final LongArray mStartDelays = new LongArray();
         private PropertyValuesHolder.PropertyValues mTmpValues =
@@ -773,14 +782,15 @@
         private boolean mContainsSequentialAnimators = false;
         private boolean mStarted = false;
         private boolean mInitialized = false;
+        private boolean mAnimationPending = false;
         private boolean mIsReversible = false;
         // This needs to be set before parsing starts.
         private boolean mShouldIgnoreInvalidAnim;
         // TODO: Consider using NativeAllocationRegistery to track native allocation
         private final VirtualRefBasePtr mSetRefBasePtr;
+        private WeakReference<RenderNode> mTarget = null;
         private WeakReference<RenderNode> mLastSeenTarget = null;
-        private int mLastListenerId = 0;
-        private int mPendingAnimationAction = NONE;
+
 
         VectorDrawableAnimator() {
             mSetPtr = nCreateAnimatorSet();
@@ -800,7 +810,6 @@
             mInitialized = true;
 
             // Check reversible.
-            mIsReversible = true;
             if (mContainsSequentialAnimators) {
                 mIsReversible = false;
             } else {
@@ -812,6 +821,7 @@
                     }
                 }
             }
+            mIsReversible = true;
         }
 
         private void parseAnimatorSet(AnimatorSet set, long startTime) {
@@ -1032,28 +1042,36 @@
          * to the last seen RenderNode target and start right away.
          */
         protected void recordLastSeenTarget(DisplayListCanvas canvas) {
-            mLastSeenTarget = new WeakReference<RenderNode>(
-                    RenderNodeAnimatorSetHelper.getTarget(canvas));
-            if (mPendingAnimationAction != NONE) {
+            if (mAnimationPending) {
+                mLastSeenTarget = new WeakReference<RenderNode>(
+                        RenderNodeAnimatorSetHelper.getTarget(canvas));
                 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
                     Log.d(LOGTAG, "Target is set in the next frame");
                 }
-                if (mPendingAnimationAction == START_ANIMATION) {
-                    start();
-                } else if (mPendingAnimationAction == REVERSE_ANIMATION) {
-                    reverse();
-                }
-                mPendingAnimationAction = NONE;
+                mAnimationPending = false;
+                start();
+            } else {
+                mLastSeenTarget = new WeakReference<RenderNode>(
+                        RenderNodeAnimatorSetHelper.getTarget(canvas));
             }
+
+        }
+
+        private boolean setTarget(RenderNode node) {
+            if (mTarget != null && mTarget.get() != null) {
+                // TODO: Maybe we want to support target change.
+                throw new IllegalStateException("Target already set!");
+            }
+
+            node.addAnimator(this);
+            mTarget = new WeakReference<RenderNode>(node);
+            return true;
         }
 
         private boolean useLastSeenTarget() {
-            if (mLastSeenTarget != null) {
-                final RenderNode target = mLastSeenTarget.get();
-                if (target != null && target.isAttached()) {
-                    target.addAnimator(this);
-                    return true;
-                }
+            if (mLastSeenTarget != null && mLastSeenTarget.get() != null) {
+                setTarget(mLastSeenTarget.get());
+                return true;
             }
             return false;
         }
@@ -1063,8 +1081,12 @@
                 return;
             }
 
+            if (mStarted) {
+                return;
+            }
+
             if (!useLastSeenTarget()) {
-                mPendingAnimationAction = START_ANIMATION;
+                mAnimationPending = true;
                 return;
             }
 
@@ -1072,45 +1094,38 @@
                 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
             }
 
-            mStarted = true;
-            nStart(mSetPtr, this, ++mLastListenerId);
+           nStart(mSetPtr, this);
             if (mListener != null) {
                 mListener.onAnimationStart(null);
             }
+            mStarted = true;
         }
 
         public void end() {
-            if (mInitialized && useLastSeenTarget()) {
-                // If no target has ever been set, no-op
+            if (mInitialized && mStarted) {
                 nEnd(mSetPtr);
+                onAnimationEnd();
             }
         }
 
-        public void reset() {
-            if (mInitialized && useLastSeenTarget()) {
-                // If no target has ever been set, no-op
-                nReset(mSetPtr);
+        void reset() {
+            if (!mInitialized) {
+                return;
             }
+            // TODO: Need to implement reset.
+            Log.w(LOGTAG, "Reset is yet to be implemented");
+            nReset(mSetPtr);
         }
 
         // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
         // animators or when the animator set has a start delay
         void reverse() {
-            if (!mIsReversible || !mInitialized) {
+            if (!mIsReversible) {
                 return;
             }
-            if (!useLastSeenTarget()) {
-                mPendingAnimationAction = REVERSE_ANIMATION;
-                return;
-            }
-            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
-            }
-            mStarted = true;
-            nReverse(mSetPtr, this, ++mLastListenerId);
-            if (mListener != null) {
-                mListener.onAnimationStart(null);
-            }
+            // TODO: Need to support reverse (non-public API)
+            Log.w(LOGTAG, "Reverse is yet to be implemented");
+            nReverse(mSetPtr, this);
         }
 
         public long getAnimatorNativePtr() {
@@ -1140,22 +1155,20 @@
             mListener = null;
         }
 
-        private void onAnimationEnd(int listenerId) {
-            if (listenerId != mLastListenerId) {
-                return;
-            }
-            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "on finished called from native");
-            }
+        private void onAnimationEnd() {
             mStarted = false;
             if (mListener != null) {
                 mListener.onAnimationEnd(null);
             }
+            mTarget = null;
         }
 
         // onFinished: should be called from native
-        private static void callOnFinished(VectorDrawableAnimator set, int id) {
-            set.onAnimationEnd(id);
+        private static void callOnFinished(VectorDrawableAnimator set) {
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "on finished called from native");
+            }
+            set.onAnimationEnd();
         }
     }
 
@@ -1175,8 +1188,8 @@
     private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
             float endValue);
     private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
-    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set, int id);
-    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set, int id);
+    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
+    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
     private static native void nEnd(long animatorSetPtr);
     private static native void nReset(long animatorSetPtr);
 }
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index e6276a4..1321a83 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -233,7 +233,8 @@
                 // not set up).
                 KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
                         spec.isUserAuthenticationRequired(),
-                        spec.getUserAuthenticationValidityDurationSeconds());
+                        spec.getUserAuthenticationValidityDurationSeconds(),
+                        spec.isUserAuthenticationValidWhileOnBody());
             } catch (IllegalStateException | IllegalArgumentException e) {
                 throw new InvalidAlgorithmParameterException(e);
             }
@@ -271,7 +272,8 @@
         args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
         KeymasterUtils.addUserAuthArgs(args,
                 spec.isUserAuthenticationRequired(),
-                spec.getUserAuthenticationValidityDurationSeconds());
+                spec.getUserAuthenticationValidityDurationSeconds(),
+                spec.isUserAuthenticationValidWhileOnBody());
         KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
                 args,
                 mKeymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 3a0ff1c..830402a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -344,7 +344,8 @@
                 // not set up).
                 KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
                         mSpec.isUserAuthenticationRequired(),
-                        mSpec.getUserAuthenticationValidityDurationSeconds());
+                        mSpec.getUserAuthenticationValidityDurationSeconds(),
+                        mSpec.isUserAuthenticationValidWhileOnBody());
             } catch (IllegalArgumentException | IllegalStateException e) {
                 throw new InvalidAlgorithmParameterException(e);
             }
@@ -529,7 +530,8 @@
 
         KeymasterUtils.addUserAuthArgs(args,
                 mSpec.isUserAuthenticationRequired(),
-                mSpec.getUserAuthenticationValidityDurationSeconds());
+                mSpec.getUserAuthenticationValidityDurationSeconds(),
+                mSpec.isUserAuthenticationValidWhileOnBody());
         args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart());
         args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
                 mSpec.getKeyValidityForOriginationEnd());
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 8d606bf..5f5f2c2 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -167,6 +167,8 @@
         boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired)
                 && (keymasterHwEnforcedUserAuthenticators != 0)
                 && (keymasterSwEnforcedUserAuthenticators == 0);
+        boolean userAuthenticationValidWhileOnBody =
+                keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
 
         return new KeyInfo(entryAlias,
                 insideSecureHardware,
@@ -182,7 +184,8 @@
                 blockModes,
                 userAuthenticationRequired,
                 (int) userAuthenticationValidityDurationSeconds,
-                userAuthenticationRequirementEnforcedBySecureHardware);
+                userAuthenticationRequirementEnforcedBySecureHardware,
+                userAuthenticationValidWhileOnBody);
     }
 
     @Override
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index cdcc7a2..d660020 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -498,7 +498,8 @@
                         KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings()));
                 KeymasterUtils.addUserAuthArgs(importArgs,
                         spec.isUserAuthenticationRequired(),
-                        spec.getUserAuthenticationValidityDurationSeconds());
+                        spec.getUserAuthenticationValidityDurationSeconds(),
+                        spec.isUserAuthenticationValidWhileOnBody());
                 importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
                         spec.getKeyValidityStart());
                 importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
@@ -692,7 +693,8 @@
             args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings);
             KeymasterUtils.addUserAuthArgs(args,
                     params.isUserAuthenticationRequired(),
-                    params.getUserAuthenticationValidityDurationSeconds());
+                    params.getUserAuthenticationValidityDurationSeconds(),
+                    params.isUserAuthenticationValidWhileOnBody());
             KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
                     args,
                     keymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index f3fd129..a84e7f34 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -252,6 +252,7 @@
     private final int mUserAuthenticationValidityDurationSeconds;
     private final byte[] mAttestationChallenge;
     private final boolean mUniqueIdIncluded;
+    private final boolean mUserAuthenticationValidWhileOnBody;
 
     /**
      * @hide should be built with Builder
@@ -277,7 +278,8 @@
             boolean userAuthenticationRequired,
             int userAuthenticationValidityDurationSeconds,
             byte[] attestationChallenge,
-            boolean uniqueIdIncluded) {
+            boolean uniqueIdIncluded,
+            boolean userAuthenticationValidWhileOnBody) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -321,6 +323,7 @@
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
         mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
         mUniqueIdIncluded = uniqueIdIncluded;
+        mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
     }
 
     /**
@@ -587,6 +590,23 @@
     }
 
     /**
+     * Returns {@code true} if the key will remain authorized while the device is on the user's
+     * body, even after the validity duration has expired.  This option has no effect on keys that
+     * don't have an authentication validity duration, and has no effect if the device lacks a
+     * secure on-body sensor.
+     *
+     * <p>Authorization applies only to secret key and private key operations. Public key operations
+     * are not restricted.
+     *
+     * @see #isUserAuthenticationRequired()
+     * @see #getUserAuthenticationValidityDurationSeconds()
+     * @see Builder#setUserAuthenticationValidWhileOnBody(boolean)
+     */
+    public boolean isUserAuthenticationValidWhileOnBody() {
+        return mUserAuthenticationValidWhileOnBody;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -612,6 +632,7 @@
         private int mUserAuthenticationValidityDurationSeconds = -1;
         private byte[] mAttestationChallenge = null;
         private boolean mUniqueIdIncluded = false;
+        private boolean mUserAuthenticationValidWhileOnBody;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -1061,6 +1082,34 @@
         }
 
         /**
+         * Sets whether the key is authorized for use after the authentication validity period is
+         * expired (see {@link #setUserAuthenticationValidityDurationSeconds} and {@link
+         * #setUserAuthenticationRequired}) if the device has a secure on-body sensor and if the
+         * device has not been removed from the user's body since the last successful
+         * authentication.
+         *
+         * <p>On devices that do not have a secure on-body sensor, creating a key with this
+         * parameter set to {@code true} will have no effect; the private or secret key will no
+         * longer be authorized for use after the validity period ends, and a fresh authentication
+         * will be required to use it again.
+         *
+         * <p>Note that "secure" on-body sensors are required by Android to have a secure path to
+         * the secure hardware, but the sensors themselves may not be difficult to fool.  It is
+         * recommended that this feature be used to increase slightly the security of keys which
+         * would otherwise have to allow unauthenticated access, or have a very long validity
+         * period. Keys that require high assurance of user authorization should not use this
+         * feature and should set a short validity period.
+         *
+         * @param remainsValid if {@code true}, and if the device supports secure on-body detection,
+         * key will remain valid after authentication validity duration has expired.
+         */
+        @NonNull
+        public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) {
+            mUserAuthenticationValidWhileOnBody = remainsValid;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
         @NonNull
@@ -1086,7 +1135,8 @@
                     mUserAuthenticationRequired,
                     mUserAuthenticationValidityDurationSeconds,
                     mAttestationChallenge,
-                    mUniqueIdIncluded);
+                    mUniqueIdIncluded,
+                    mUserAuthenticationValidWhileOnBody);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index d726880..f77b5ba 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -79,6 +79,7 @@
     private final boolean mUserAuthenticationRequired;
     private final int mUserAuthenticationValidityDurationSeconds;
     private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
+    private final boolean mUserAuthenticationValidWhileOnBody;
 
     /**
      * @hide
@@ -97,7 +98,8 @@
             @KeyProperties.BlockModeEnum String[] blockModes,
             boolean userAuthenticationRequired,
             int userAuthenticationValidityDurationSeconds,
-            boolean userAuthenticationRequirementEnforcedBySecureHardware) {
+            boolean userAuthenticationRequirementEnforcedBySecureHardware,
+            boolean userAuthenticationValidWhileOnBody) {
         mKeystoreAlias = keystoreKeyAlias;
         mInsideSecureHardware = insideSecureHardware;
         mOrigin = origin;
@@ -116,6 +118,7 @@
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
         mUserAuthenticationRequirementEnforcedBySecureHardware =
                 userAuthenticationRequirementEnforcedBySecureHardware;
+        mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
     }
 
     /**
@@ -277,4 +280,14 @@
     public boolean isUserAuthenticationRequirementEnforcedBySecureHardware() {
         return mUserAuthenticationRequirementEnforcedBySecureHardware;
     }
+
+    /**
+     * Returns {@code true} if this key will remain usable after its specified validity duration
+     * for as long as the device remains on the user's body.  This is possible only for keys with
+     * a specified validity duration.  Always returns {@code false} on devices that lack a secure
+     * on-body sensor.
+     */
+    public boolean isUserAuthenticationValidWhileOnBody() {
+        return mUserAuthenticationValidWhileOnBody;
+    }
 }
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index c984439..4700b68 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -214,6 +214,7 @@
     private final boolean mRandomizedEncryptionRequired;
     private final boolean mUserAuthenticationRequired;
     private final int mUserAuthenticationValidityDurationSeconds;
+    private final boolean mUserAuthenticationValidWhileOnBody;
 
     private KeyProtection(
             Date keyValidityStart,
@@ -226,7 +227,8 @@
             @KeyProperties.BlockModeEnum String[] blockModes,
             boolean randomizedEncryptionRequired,
             boolean userAuthenticationRequired,
-            int userAuthenticationValidityDurationSeconds) {
+            int userAuthenticationValidityDurationSeconds,
+            boolean userAuthenticationValidWhileOnBody) {
         mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
         mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
         mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -240,6 +242,7 @@
         mRandomizedEncryptionRequired = randomizedEncryptionRequired;
         mUserAuthenticationRequired = userAuthenticationRequired;
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+        mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
     }
 
     /**
@@ -392,6 +395,23 @@
     }
 
     /**
+     * Returns {@code true} if the key will remain authorized while the device is on the user's
+     * body, even after the validity duration has expired.  This option has no effect on keys that
+     * don't have an authentication validity duration, and has no effect if the device lacks a
+     * secure on-body sensor.
+     *
+     * <p>Authorization applies only to secret key and private key operations. Public key operations
+     * are not restricted.
+     *
+     * @see #isUserAuthenticationRequired()
+     * @see #getUserAuthenticationValidityDurationSeconds()
+     * @see Builder#setUserAuthenticationValidWhileOnBody(boolean)
+     */
+    public boolean isUserAuthenticationValidWhileOnBody() {
+        return mUserAuthenticationValidWhileOnBody;
+    }
+
+    /**
      * Builder of {@link KeyProtection} instances.
      */
     public final static class Builder {
@@ -407,6 +427,7 @@
         private boolean mRandomizedEncryptionRequired = true;
         private boolean mUserAuthenticationRequired;
         private int mUserAuthenticationValidityDurationSeconds = -1;
+        private boolean mUserAuthenticationValidWhileOnBody;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -680,6 +701,34 @@
         }
 
         /**
+         * Sets whether the key is authorized for use after the authentication validity period is
+         * expired (see {@link #setUserAuthenticationValidityDurationSeconds} and {@link
+         * #setUserAuthenticationRequired}) if the device has a secure on-body sensor and if the
+         * device has not been removed from the user's body since the last successful
+         * authentication.
+         *
+         * <p>On devices that do not have a secure on-body sensor, creating a key with this
+         * parameter set to {@code true} will have no effect; the private or secret key will no
+         * longer be authorized for use after the validity period ends, and a fresh authentication
+         * will be required to use it again.
+         *
+         * <p>Note that "secure" on-body sensors are required by Android to have a secure path to
+         * the secure hardware, but the sensors themselves may not be difficult to fool.  It is
+         * recommended that this feature be used to increase slightly the security of keys which
+         * would otherwise have to allow unauthenticated access, or have a very long validity
+         * period. Keys that require high assurance of user authorization should not use this
+         * feature and should set a short validity period.
+         *
+         * @param remainsValid if {@code true}, and if the device supports secure on-body detection,
+         * key will remain valid after authentication validity duration has expired.
+         */
+        @NonNull
+        public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) {
+            mUserAuthenticationValidWhileOnBody = remainsValid;
+            return this;
+        }
+
+        /**
          * Builds an instance of {@link KeyProtection}.
          *
          * @throws IllegalArgumentException if a required field is missing
@@ -697,7 +746,8 @@
                     mBlockModes,
                     mRandomizedEncryptionRequired,
                     mUserAuthenticationRequired,
-                    mUserAuthenticationValidityDurationSeconds);
+                    mUserAuthenticationValidityDurationSeconds,
+                    mUserAuthenticationValidWhileOnBody);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index feafbfa..3a008bc 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -96,7 +96,8 @@
      */
     public static void addUserAuthArgs(KeymasterArguments args,
             boolean userAuthenticationRequired,
-            int userAuthenticationValidityDurationSeconds) {
+            int userAuthenticationValidityDurationSeconds,
+            boolean userAuthenticationValidWhileOnBody) {
         if (!userAuthenticationRequired) {
             args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
             return;
@@ -119,6 +120,10 @@
             args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
                     KeymasterArguments.toUint64(fingerprintOnlySid));
             args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT);
+            if (userAuthenticationValidWhileOnBody) {
+                throw new ProviderException("Key validity extension while device is on-body is not "
+                        + "supported for keys requiring fingerprint authentication");
+            }
         } else {
             // The key is authorized for use for the specified amount of time after the user has
             // authenticated. Whatever unlocks the secure lock screen should authorize this key.
@@ -133,6 +138,9 @@
                     KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT);
             args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
                     userAuthenticationValidityDurationSeconds);
+            if (userAuthenticationValidWhileOnBody) {
+                args.addBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
+            }
         }
     }
 
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 6a565033..6257122 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -232,7 +232,6 @@
 
 LOCAL_SRC_FILES += \
     $(hwui_test_common_src_files) \
-    tests/unit/BufferPoolTests.cpp \
     tests/unit/CanvasStateTests.cpp \
     tests/unit/ClipAreaTests.cpp \
     tests/unit/CrashHandlerInjector.cpp \
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 294edb6..7bd2b24 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -33,7 +33,6 @@
 
 BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue)
         : mTarget(nullptr)
-        , mStagingTarget(nullptr)
         , mFinalValue(finalValue)
         , mDeltaValue(0)
         , mFromValue(0)
@@ -43,8 +42,7 @@
         , mStartTime(0)
         , mDuration(300)
         , mStartDelay(0)
-        , mMayRunAsync(true)
-        , mPlayTime(0) {
+        , mMayRunAsync(true) {
 }
 
 BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
@@ -83,129 +81,26 @@
 }
 
 void BaseRenderNodeAnimator::attach(RenderNode* target) {
-    mStagingTarget = target;
+    mTarget = target;
     onAttached();
 }
 
-void BaseRenderNodeAnimator::start() {
-    mStagingPlayState = PlayState::Running;
-    mStagingRequests.push_back(Request::Start);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::cancel() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::Cancel);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reset() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::Reset);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::reverse() {
-    mStagingPlayState = PlayState::Reversing;
-    mStagingRequests.push_back(Request::Reverse);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::end() {
-    mStagingPlayState = PlayState::Finished;
-    mStagingRequests.push_back(Request::End);
-    onStagingPlayStateChanged();
-}
-
-void BaseRenderNodeAnimator::resolveStagingRequest(Request request) {
-    switch (request) {
-    case Request::Start:
-        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
-                        mPlayTime : 0;
-        mPlayState = PlayState::Running;
-        break;
-    case Request::Reverse:
-        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
-                        mPlayTime : mDuration;
-        mPlayState = PlayState::Reversing;
-        break;
-    case Request::Reset:
-        mPlayTime = 0;
-        mPlayState = PlayState::Finished;
-        break;
-    case Request::Cancel:
-        mPlayState = PlayState::Finished;
-        break;
-    case Request::End:
-        mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration;
-        mPlayState = PlayState::Finished;
-        break;
-    default:
-        LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request));
-    };
-}
-
 void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
-    if (mStagingTarget) {
-        RenderNode* oldTarget = mTarget;
-        mTarget = mStagingTarget;
-        mStagingTarget = nullptr;
-        if (oldTarget && oldTarget != mTarget) {
-            oldTarget->onAnimatorTargetChanged(this);
-        }
-    }
-
     if (!mHasStartValue) {
         doSetStartValue(getValue(mTarget));
     }
-
-    if (!mStagingRequests.empty()) {
-        // Keep track of the play state and play time before they are changed when
-        // staging requests are resolved.
-        nsecs_t currentPlayTime = mPlayTime;
-        PlayState prevFramePlayState = mPlayState;
-
-        // Resolve staging requests one by one.
-        for (Request request : mStagingRequests) {
-            resolveStagingRequest(request);
+    if (mStagingPlayState > mPlayState) {
+        if (mStagingPlayState == PlayState::Restarted) {
+            mStagingPlayState = PlayState::Running;
         }
-        mStagingRequests.clear();
-
-        if (mStagingPlayState == PlayState::Finished) {
-            // Set the staging play time and end the animation
-            updatePlayTime(mPlayTime);
+        mPlayState = mStagingPlayState;
+        // Oh boy, we're starting! Man the battle stations!
+        if (mPlayState == PlayState::Running) {
+            transitionToRunning(context);
+        } else if (mPlayState == PlayState::Finished) {
             callOnFinishedListener(context);
-        } else if (mStagingPlayState == PlayState::Running
-                || mStagingPlayState == PlayState::Reversing) {
-            bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState;
-            if (prevFramePlayState != mStagingPlayState) {
-                transitionToRunning(context);
-            }
-            if (changed) {
-                // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was
-                // requested from UI thread). It is achieved by modifying mStartTime, such that
-                // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the
-                // case of reversing)
-                nsecs_t currentFrameTime = context.frameTimeMs();
-                if (mPlayState == PlayState::Reversing) {
-                    // Reverse is not supported for animations with a start delay, so here we
-                    // assume no start delay.
-                    mStartTime = currentFrameTime  - (mDuration - mPlayTime);
-                } else {
-                    // Animation should play forward
-                    if (mPlayTime == 0) {
-                        // If the request is to start from the beginning, include start delay.
-                        mStartTime = currentFrameTime + mStartDelay;
-                    } else {
-                        // If the request is to seek to a non-zero play time, then we skip start
-                        // delay.
-                        mStartTime = currentFrameTime - mPlayTime;
-                    }
-                }
-            }
         }
     }
-    onPushStaging();
 }
 
 void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) {
@@ -241,37 +136,37 @@
 
     // This should be set before setValue() so animators can query this time when setValue
     // is called.
-    nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime;
-    bool finished = updatePlayTime(currentPlayTime);
-    if (finished && mPlayState != PlayState::Finished) {
-        mPlayState = PlayState::Finished;
-        callOnFinishedListener(context);
-    }
-    return finished;
-}
+    nsecs_t currentFrameTime = context.frameTimeMs();
+    onPlayTimeChanged(currentFrameTime - mStartTime);
 
-bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) {
-    mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime;
-    onPlayTimeChanged(mPlayTime);
     // If BaseRenderNodeAnimator is handling the delay (not typical), then
     // because the staging properties reflect the final value, we always need
     // to call setValue even if the animation isn't yet running or is still
     // being delayed as we need to override the staging value
-    if (playTime < 0) {
+    if (mStartTime > context.frameTimeMs()) {
         setValue(mTarget, mFromValue);
         return false;
     }
 
     float fraction = 1.0f;
-    if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
-        fraction = mPlayTime / (float) mDuration;
+
+    if (mPlayState == PlayState::Running && mDuration > 0) {
+        fraction = (float)(currentFrameTime - mStartTime) / mDuration;
     }
-    fraction = MathUtils::clamp(fraction, 0.0f, 1.0f);
+    if (fraction >= 1.0f) {
+        fraction = 1.0f;
+        mPlayState = PlayState::Finished;
+    }
 
     fraction = mInterpolator->interpolate(fraction);
     setValue(mTarget, mFromValue + (mDeltaValue * fraction));
 
-    return playTime >= mDuration;
+    if (mPlayState == PlayState::Finished) {
+        callOnFinishedListener(context);
+        return true;
+    }
+
+    return false;
 }
 
 void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) {
@@ -320,36 +215,18 @@
 
 void RenderPropertyAnimator::onAttached() {
     if (!mHasStartValue
-            && mStagingTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
-        setStartValue((mStagingTarget->stagingProperties().*mPropertyAccess->getter)());
+            && mTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
+        setStartValue((mTarget->stagingProperties().*mPropertyAccess->getter)());
     }
 }
 
 void RenderPropertyAnimator::onStagingPlayStateChanged() {
     if (mStagingPlayState == PlayState::Running) {
-        if (mStagingTarget) {
-            (mStagingTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
-        } else {
-            // In the case of start delay where stagingTarget has been sync'ed over and null'ed
-            // we delay the properties update to push staging.
-            mShouldUpdateStagingProperties = true;
-        }
+        (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
     } else if (mStagingPlayState == PlayState::Finished) {
         // We're being canceled, so make sure that whatever values the UI thread
         // is observing for us is pushed over
-        mShouldSyncPropertyFields = true;
-    }
-}
-
-void RenderPropertyAnimator::onPushStaging() {
-    if (mShouldUpdateStagingProperties) {
-        (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
-        mShouldUpdateStagingProperties = false;
-    }
-
-    if (mShouldSyncPropertyFields) {
         mTarget->setPropertyFieldsDirty(dirtyMask());
-        mShouldSyncPropertyFields = false;
     }
 }
 
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index fdae0f3..2c9c9c3 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -24,8 +24,6 @@
 
 #include "utils/Macros.h"
 
-#include <vector>
-
 namespace android {
 namespace uirenderer {
 
@@ -61,14 +59,14 @@
         mMayRunAsync = mayRunAsync;
     }
     bool mayRunAsync() { return mMayRunAsync; }
-    ANDROID_API void start();
-    ANDROID_API void reset();
-    ANDROID_API void reverse();
-    // Terminates the animation at its current progress.
-    ANDROID_API void cancel();
-
-    // Terminates the animation and skip to the end of the animation.
-    ANDROID_API void end();
+    ANDROID_API void start() {
+        if (mStagingPlayState == PlayState::NotStarted) {
+            mStagingPlayState = PlayState::Running;
+        } else {
+            mStagingPlayState = PlayState::Restarted;
+        }
+        onStagingPlayStateChanged(); }
+    ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); }
 
     void attach(RenderNode* target);
     virtual void onAttached() {}
@@ -76,42 +74,36 @@
     void pushStaging(AnimationContext& context);
     bool animate(AnimationContext& context);
 
-    bool isRunning() { return mPlayState == PlayState::Running
-            || mPlayState == PlayState::Reversing; }
+    bool isRunning() { return mPlayState == PlayState::Running; }
     bool isFinished() { return mPlayState == PlayState::Finished; }
     float finalValue() { return mFinalValue; }
 
     ANDROID_API virtual uint32_t dirtyMask() = 0;
 
     void forceEndNow(AnimationContext& context);
-    RenderNode* target() { return mTarget; }
-    RenderNode* stagingTarget() { return mStagingTarget; }
 
 protected:
     // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
     // thread and Render Thread animation state, respectively.
     // From the UI thread, mStagingPlayState transition looks like
-    // NotStarted -> Running/Reversing -> Finished
-    //                ^                     |
-    //                |                     |
-    //                ----------------------
+    // NotStarted -> Running -> Finished
+    //                ^            |
+    //                |            |
+    //            Restarted <------
     // Note: For mStagingState, the Finished state (optional) is only set when the animation is
     // terminated by user.
     //
     // On Render Thread, mPlayState transition:
-    // NotStart -> Running/Reversing-> Finished
-    //                ^                 |
-    //                |                 |
-    //                ------------------
-    // Note that if the animation is in Running/Reversing state, calling start or reverse again
-    // would do nothing if the animation has the same play direction as the request; otherwise,
-    // the animation would start from where it is and change direction (i.e. Reversing <-> Running)
+    // NotStart -> Running -> Finished
+    //                ^            |
+    //                |            |
+    //                -------------
 
     enum class PlayState {
         NotStarted,
         Running,
-        Reversing,
         Finished,
+        Restarted,
     };
 
     BaseRenderNodeAnimator(float finalValue);
@@ -119,15 +111,14 @@
 
     virtual float getValue(RenderNode* target) const = 0;
     virtual void setValue(RenderNode* target, float value) = 0;
+    RenderNode* target() { return mTarget; }
 
     void callOnFinishedListener(AnimationContext& context);
 
     virtual void onStagingPlayStateChanged() {}
     virtual void onPlayTimeChanged(nsecs_t playTime) {}
-    virtual void onPushStaging() {}
 
     RenderNode* mTarget;
-    RenderNode* mStagingTarget;
 
     float mFinalValue;
     float mDeltaValue;
@@ -141,28 +132,13 @@
     nsecs_t mDuration;
     nsecs_t mStartDelay;
     bool mMayRunAsync;
-    // Play Time tracks the progress of animation, it should always be [0, mDuration], 0 being
-    // the beginning of the animation, will reach mDuration at the end of an animation.
-    nsecs_t mPlayTime;
 
     sp<AnimationListener> mListener;
 
 private:
-    enum class Request {
-        Start,
-        Reverse,
-        Reset,
-        Cancel,
-        End
-    };
     inline void checkMutable();
     virtual void transitionToRunning(AnimationContext& context);
     void doSetStartValue(float value);
-    bool updatePlayTime(nsecs_t playTime);
-    void resolveStagingRequest(Request request);
-
-    std::vector<Request> mStagingRequests;
-
 };
 
 class RenderPropertyAnimator : public BaseRenderNodeAnimator {
@@ -191,7 +167,6 @@
     virtual void setValue(RenderNode* target, float value) override;
     virtual void onAttached() override;
     virtual void onStagingPlayStateChanged() override;
-    virtual void onPushStaging() override;
 
 private:
     typedef bool (RenderProperties::*SetFloatProperty)(float value);
@@ -201,8 +176,6 @@
     const PropertyAccessors* mPropertyAccess;
 
     static const PropertyAccessors PROPERTY_ACCESSOR_LUT[];
-    bool mShouldSyncPropertyFields = false;
-    bool mShouldUpdateStagingProperties = false;
 };
 
 class CanvasPropertyPrimitiveAnimator : public BaseRenderNodeAnimator {
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index 2198fcc..cd30b18 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -27,8 +27,9 @@
 
 using namespace std;
 
-static void detach(sp<BaseRenderNodeAnimator>& animator) {
+static void unref(BaseRenderNodeAnimator* animator) {
     animator->detach();
+    animator->decStrong(nullptr);
 }
 
 AnimatorManager::AnimatorManager(RenderNode& parent)
@@ -37,28 +38,14 @@
 }
 
 AnimatorManager::~AnimatorManager() {
-    for_each(mNewAnimators.begin(), mNewAnimators.end(), detach);
-    for_each(mAnimators.begin(), mAnimators.end(), detach);
+    for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
+    for_each(mAnimators.begin(), mAnimators.end(), unref);
 }
 
 void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    RenderNode* stagingTarget = animator->stagingTarget();
-    if (stagingTarget == &mParent) {
-        return;
-    }
-    mNewAnimators.emplace_back(animator.get());
-    // If the animator is already attached to other RenderNode, remove it from that RenderNode's
-    // new animator list. This ensures one animator only ends up in one newAnimatorList during one
-    // frame, even when it's added multiple times to multiple targets.
-    if (stagingTarget) {
-        stagingTarget->removeAnimator(animator);
-    }
+    animator->incStrong(nullptr);
     animator->attach(&mParent);
-}
-
-void AnimatorManager::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    mNewAnimators.erase(std::remove(mNewAnimators.begin(), mNewAnimators.end(), animator),
-            mNewAnimators.end());
+    mNewAnimators.push_back(animator.get());
 }
 
 void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
@@ -69,40 +56,38 @@
             &mParent, mParent.getName());
 }
 
+template<typename T>
+static void move_all(T& source, T& dest) {
+    dest.reserve(source.size() + dest.size());
+    for (typename T::iterator it = source.begin(); it != source.end(); it++) {
+        dest.push_back(*it);
+    }
+    source.clear();
+}
+
 void AnimatorManager::pushStaging() {
     if (mNewAnimators.size()) {
         LOG_ALWAYS_FATAL_IF(!mAnimationHandle,
                 "Trying to start new animators on %p (%s) without an animation handle!",
                 &mParent, mParent.getName());
-
-        // Only add new animators that are not already in the mAnimators list
-        for (auto& anim : mNewAnimators) {
-            if (anim->target() != &mParent) {
-                mAnimators.push_back(std::move(anim));
-            }
-        }
-        mNewAnimators.clear();
+        // Since this is a straight move, we don't need to inc/dec the ref count
+        move_all(mNewAnimators, mAnimators);
     }
-    for (auto& animator : mAnimators) {
-        animator->pushStaging(mAnimationHandle->context());
+    for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
+        (*it)->pushStaging(mAnimationHandle->context());
     }
 }
 
-void AnimatorManager::onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
-    LOG_ALWAYS_FATAL_IF(animator->target() == &mParent, "Target has not been changed");
-    mAnimators.erase(std::remove(mAnimators.begin(), mAnimators.end(), animator), mAnimators.end());
-}
-
 class AnimateFunctor {
 public:
     AnimateFunctor(TreeInfo& info, AnimationContext& context)
             : dirtyMask(0), mInfo(info), mContext(context) {}
 
-    bool operator() (sp<BaseRenderNodeAnimator>& animator) {
+    bool operator() (BaseRenderNodeAnimator* animator) {
         dirtyMask |= animator->dirtyMask();
         bool remove = animator->animate(mContext);
         if (remove) {
-            animator->detach();
+            animator->decStrong(nullptr);
         } else {
             if (animator->isRunning()) {
                 mInfo.out.hasAnimations = true;
@@ -144,18 +129,20 @@
 
 uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
     AnimateFunctor functor(info, mAnimationHandle->context());
-    auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+    std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
+    newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
     mAnimators.erase(newEnd, mAnimators.end());
     mAnimationHandle->notifyAnimationsRan();
     mParent.mProperties.updateMatrix();
     return functor.dirtyMask;
 }
 
-static void endStagingAnimator(sp<BaseRenderNodeAnimator>& animator) {
-    animator->cancel();
+static void endStagingAnimator(BaseRenderNodeAnimator* animator) {
+    animator->end();
     if (animator->listener()) {
-        animator->listener()->onAnimationFinished(animator.get());
+        animator->listener()->onAnimationFinished(animator);
     }
+    animator->decStrong(nullptr);
 }
 
 void AnimatorManager::endAllStagingAnimators() {
@@ -170,8 +157,9 @@
 public:
     EndActiveAnimatorsFunctor(AnimationContext& context) : mContext(context) {}
 
-    void operator() (sp<BaseRenderNodeAnimator>& animator) {
+    void operator() (BaseRenderNodeAnimator* animator) {
         animator->forceEndNow(mContext);
+        animator->decStrong(nullptr);
     }
 
 private:
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
index 61f6179..fb75eb8 100644
--- a/libs/hwui/AnimatorManager.h
+++ b/libs/hwui/AnimatorManager.h
@@ -39,13 +39,11 @@
     ~AnimatorManager();
 
     void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
-    void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
 
     void setAnimationHandle(AnimationHandle* handle);
     bool hasAnimationHandle() { return mAnimationHandle; }
 
     void pushStaging();
-    void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator);
 
     // Returns the combined dirty mask of all animators run
     uint32_t animate(TreeInfo& info);
@@ -68,8 +66,9 @@
     AnimationHandle* mAnimationHandle;
 
     // To improve the efficiency of resizing & removing from the vector
-    std::vector< sp<BaseRenderNodeAnimator> > mNewAnimators;
-    std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
+    // use manual ref counting instead of sp<>.
+    std::vector<BaseRenderNodeAnimator*> mNewAnimators;
+    std::vector<BaseRenderNodeAnimator*> mAnimators;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/BufferPool.h b/libs/hwui/BufferPool.h
deleted file mode 100644
index 005b399..0000000
--- a/libs/hwui/BufferPool.h
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#pragma once
-
-#include "utils/RefBase.h"
-#include "utils/Log.h"
-#include "utils/Macros.h"
-
-#include <atomic>
-#include <stdint.h>
-#include <memory>
-#include <mutex>
-
-namespace android {
-namespace uirenderer {
-
-/*
- * Simple thread-safe pool of int64_t arrays of a provided size.
- *
- * Permits allocating a client-provided max number of buffers.
- * If all buffers are in use, refuses to service any more
- * acquire requests until buffers are re-released to the pool.
- */
-class BufferPool : public VirtualLightRefBase {
-public:
-    class Buffer {
-        PREVENT_COPY_AND_ASSIGN(Buffer);
-    public:
-        int64_t* getBuffer() { return mBuffer.get(); }
-        size_t getSize() { return mSize; }
-
-        void release() {
-            LOG_ALWAYS_FATAL_IF(mPool.get() == nullptr, "attempt to release unacquired buffer");
-            mPool->release(this);
-        }
-
-        Buffer* incRef() {
-            mRefs++;
-            return this;
-        }
-
-        int decRef() {
-            int refs = mRefs.fetch_sub(1);
-            LOG_ALWAYS_FATAL_IF(refs == 0, "buffer reference decremented below 0");
-            return refs - 1;
-        }
-
-        bool isUniqueRef() {
-            return mRefs.load() == 1;
-        }
-
-    private:
-        friend class BufferPool;
-
-        Buffer(BufferPool* pool, size_t size) : mRefs(1) {
-            mSize = size;
-            mBuffer.reset(new int64_t[size]);
-            mPool = pool;
-        }
-
-        void setPool(BufferPool* pool) {
-            mPool = pool;
-        }
-
-        std::unique_ptr<Buffer> mNext;
-        std::unique_ptr<int64_t[]> mBuffer;
-        sp<BufferPool> mPool;
-        size_t mSize;
-
-        std::atomic_int mRefs;
-    };
-
-    BufferPool(size_t bufferSize, size_t count)
-            : mBufferSize(bufferSize), mCount(count) {}
-
-    /**
-     * Acquires a buffer from the buffer pool if available.
-     *
-     * Only `mCount` buffers are allowed to be in use at a single
-     * instance.
-     *
-     * If no buffer is available, i.e. `mCount` buffers are in use,
-     * returns nullptr.
-     *
-     * The pointer returned from this method *MUST NOT* be freed, instead
-     * BufferPool::release() must be called upon it when the client
-     * is done with it. Failing to release buffers will eventually make the
-     * BufferPool refuse to service any more BufferPool::acquire() requests.
-     */
-    BufferPool::Buffer* acquire() {
-        std::lock_guard<std::mutex> lock(mLock);
-
-        if (mHead.get() != nullptr) {
-            BufferPool::Buffer* res = mHead.release();
-            mHead = std::move(res->mNext);
-            res->mNext.reset(nullptr);
-            res->setPool(this);
-            res->incRef();
-            return res;
-        }
-
-        if (mAllocatedCount < mCount) {
-            ++mAllocatedCount;
-            return new BufferPool::Buffer(this, mBufferSize);
-        }
-
-        return nullptr;
-    }
-
-    /**
-     * Releases a buffer previously acquired by BufferPool::acquire().
-     *
-     * The released buffer is not valid after calling this method and
-     * attempting to use will result in undefined behavior.
-     */
-    void release(BufferPool::Buffer* buffer) {
-        std::lock_guard<std::mutex> lock(mLock);
-
-        if (buffer->decRef() != 0) {
-            return;
-        }
-
-        buffer->setPool(nullptr);
-
-        BufferPool::Buffer* list = mHead.get();
-        if (list == nullptr) {
-            mHead.reset(buffer);
-            mHead->mNext.reset(nullptr);
-            return;
-        }
-
-        while (list->mNext.get() != nullptr) {
-            list = list->mNext.get();
-        }
-
-        list->mNext.reset(buffer);
-    }
-
-    /*
-     * Used for testing.
-     */
-    size_t getAvailableBufferCount() {
-        size_t remainingToAllocateCount = mCount - mAllocatedCount;
-
-        BufferPool::Buffer* list = mHead.get();
-        if (list == nullptr) return remainingToAllocateCount;
-
-        int count = 1;
-        while (list->mNext.get() != nullptr) {
-            count++;
-            list = list->mNext.get();
-        }
-
-        return count + remainingToAllocateCount;
-    }
-
-private:
-    mutable std::mutex mLock;
-
-    size_t mBufferSize;
-    size_t mCount;
-    size_t mAllocatedCount = 0;
-    std::unique_ptr<BufferPool::Buffer> mHead;
-};
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h
index 2b42a80..4f81c86 100644
--- a/libs/hwui/FrameMetricsObserver.h
+++ b/libs/hwui/FrameMetricsObserver.h
@@ -18,14 +18,12 @@
 
 #include <utils/RefBase.h>
 
-#include "BufferPool.h"
-
 namespace android {
 namespace uirenderer {
 
 class FrameMetricsObserver : public VirtualLightRefBase {
 public:
-    virtual void notify(BufferPool::Buffer* buffer, int dropCount);
+    virtual void notify(const int64_t* buffer);
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h
index 0831d24..c1cd0a92 100644
--- a/libs/hwui/FrameMetricsReporter.h
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -19,7 +19,6 @@
 #include <utils/RefBase.h>
 #include <utils/Log.h>
 
-#include "BufferPool.h"
 #include "FrameInfo.h"
 #include "FrameMetricsObserver.h"
 
@@ -31,10 +30,7 @@
 
 class FrameMetricsReporter {
 public:
-    FrameMetricsReporter() {
-        mBufferPool = new BufferPool(kBufferSize, kBufferCount);
-        LOG_ALWAYS_FATAL_IF(mBufferPool.get() == nullptr, "OOM: unable to allocate buffer pool");
-    }
+    FrameMetricsReporter() {}
 
     void addObserver(FrameMetricsObserver* observer) {
         mObservers.push_back(observer);
@@ -55,36 +51,13 @@
     }
 
     void reportFrameMetrics(const int64_t* stats) {
-        BufferPool::Buffer* statsBuffer = mBufferPool->acquire();
-
-        if (statsBuffer != nullptr) {
-            // copy in frame stats
-            memcpy(statsBuffer->getBuffer(), stats, kBufferSize * sizeof(*stats));
-
-            // notify on requested threads
-            for (size_t i = 0; i < mObservers.size(); i++) {
-                mObservers[i]->notify(statsBuffer, mDroppedReports);
-            }
-
-            // drop our reference
-            statsBuffer->release();
-            mDroppedReports = 0;
-        } else {
-            mDroppedReports++;
+        for (size_t i = 0; i < mObservers.size(); i++) {
+            mObservers[i]->notify(stats);
         }
     }
 
-    int getDroppedReports() { return mDroppedReports; }
-
 private:
-    static const size_t kBufferCount = 3;
-    static const size_t kBufferSize = static_cast<size_t>(FrameInfoIndex::NumIndexes);
-
     std::vector< sp<FrameMetricsObserver> > mObservers;
-
-    sp<BufferPool> mBufferPool;
-
-    int mDroppedReports = 0;
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
index b29f91f..eca1afcc 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.cpp
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -17,8 +17,6 @@
 #include "PropertyValuesAnimatorSet.h"
 #include "RenderNode.h"
 
-#include <algorithm>
-
 namespace android {
 namespace uirenderer {
 
@@ -55,26 +53,16 @@
 }
 
 void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
-    if (playTime == 0 && mDuration > 0) {
-        // Reset all the animators
-        for (auto it = mAnimators.rbegin(); it != mAnimators.rend(); it++) {
-            // Note that this set may containing animators modifying the same property, so when we
-            // reset the animators, we need to make sure the animators that end the first will
-            // have the final say on what the property value should be.
-            (*it)->setFraction(0);
-        }
-    } else if (playTime >= mDuration) {
-        // Skip all the animators to end
-        for (auto& anim : mAnimators) {
-            anim->setFraction(1);
-        }
-    } else {
-        for (auto& anim : mAnimators) {
-            anim->setCurrentPlayTime(playTime);
-        }
+    for (size_t i = 0; i < mAnimators.size(); i++) {
+        mAnimators[i]->setCurrentPlayTime(playTime);
     }
 }
 
+void PropertyValuesAnimatorSet::reset() {
+    // TODO: implement reset through adding a play state because we need to support reset() even
+    // during an animation run.
+}
+
 void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
     init();
     mOneShotListener = listener;
@@ -82,23 +70,20 @@
 }
 
 void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
-    init();
-    mOneShotListener = listener;
-    BaseRenderNodeAnimator::reverse();
+// TODO: implement reverse
 }
 
 void PropertyValuesAnimatorSet::init() {
     if (mInitialized) {
         return;
     }
-
-    // Sort the animators by their total duration. Note that all the animators in the set start at
-    // the same time, so the ones with longer total duration (which includes start delay) will
-    // be the ones that end later.
-    std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) {
-        return a->getTotalDuration() < b->getTotalDuration();
-    });
-    mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration();
+    nsecs_t maxDuration = 0;
+    for (size_t i = 0; i < mAnimators.size(); i++) {
+        if (maxDuration < mAnimators[i]->getTotalDuration()) {
+            maxDuration = mAnimators[i]->getTotalDuration();
+        }
+    }
+    mDuration = maxDuration;
     mInitialized = true;
 }
 
@@ -121,19 +106,18 @@
 void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
     if (playTime >= mStartDelay && playTime < mTotalDuration) {
          nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
-         float fraction = currentIterationPlayTime / (float) mDuration;
-         setFraction(fraction);
+         mLatestFraction = currentIterationPlayTime / (float) mDuration;
     } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
-        // This makes sure we only set the fraction = 1 once. It is needed because there might
-        // be another animator modifying the same property after this animator finishes, we need
-        // to make sure we don't set conflicting values on the same property within one frame.
-        setFraction(1.0f);
+        mLatestFraction = 1.0f;
+    } else {
+        return;
     }
+
+    setFraction(mLatestFraction);
 }
 
 void PropertyAnimator::setFraction(float fraction) {
-    mLatestFraction = fraction;
-    float interpolatedFraction = mInterpolator->interpolate(fraction);
+    float interpolatedFraction = mInterpolator->interpolate(mLatestFraction);
     mPropertyValuesHolder->setFraction(interpolatedFraction);
 }
 
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
index c7ae7c0..4c7ce52 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.h
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -50,6 +50,7 @@
 
     void start(AnimationListener* listener);
     void reverse(AnimationListener* listener);
+    void reset();
 
     void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
             Interpolator* interpolators, int64_t startDelays,
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 9ac76a4..bade216 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -218,10 +218,6 @@
     mAnimatorManager.addAnimator(animator);
 }
 
-void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    mAnimatorManager.removeAnimator(animator);
-}
-
 void RenderNode::damageSelf(TreeInfo& info) {
     if (isRenderable()) {
         if (properties().getClipDamageToBounds()) {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index e037645..f248de54 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -187,12 +187,6 @@
 
     // UI thread only!
     ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
-    void removeAnimator(const sp<BaseRenderNodeAnimator>& animator);
-
-    // This can only happen during pushStaging()
-    void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) {
-        mAnimatorManager.onAnimatorTargetChanged(animator);
-    }
 
     AnimatorManager& animators() { return mAnimatorManager; }
 
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 1f81970..cb61e51 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -159,14 +159,6 @@
         }
     }
 
-    long getDroppedFrameReportCount() {
-        if (mFrameMetricsReporter.get() != nullptr) {
-            return mFrameMetricsReporter->getDroppedReports();
-        }
-
-        return 0;
-    }
-
 private:
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
diff --git a/libs/hwui/tests/unit/BufferPoolTests.cpp b/libs/hwui/tests/unit/BufferPoolTests.cpp
deleted file mode 100644
index 44e6d3a..0000000
--- a/libs/hwui/tests/unit/BufferPoolTests.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#include <gtest/gtest.h>
-
-#include <BufferPool.h>
-#include <utils/StrongPointer.h>
-
-namespace android {
-namespace uirenderer {
-
-TEST(BufferPool, acquireThenRelease) {
-    static const int numRuns = 5;
-
-    // 10 buffers of size 1
-    static const size_t bufferSize = 1;
-    static const size_t bufferCount = 10;
-    sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
-
-    for (int run = 0; run < numRuns; run++) {
-        BufferPool::Buffer* acquiredBuffers[bufferCount];
-        for (size_t i = 0; i < bufferCount; i++) {
-            ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
-            acquiredBuffers[i] = pool->acquire();
-            ASSERT_NE(nullptr, acquiredBuffers[i]);
-            ASSERT_TRUE(acquiredBuffers[i]->isUniqueRef());
-        }
-
-        for (size_t i = 0; i < bufferCount; i++) {
-            ASSERT_EQ(i, pool->getAvailableBufferCount());
-            acquiredBuffers[i]->release();
-            acquiredBuffers[i] = nullptr;
-        }
-
-        ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
-    }
-}
-
-TEST(BufferPool, acquireReleaseInterleaved) {
-    static const int numRuns = 5;
-
-    // 10 buffers of size 1
-    static const size_t bufferSize = 1;
-    static const size_t bufferCount = 10;
-
-    sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
-
-    for (int run = 0; run < numRuns; run++) {
-        BufferPool::Buffer* acquiredBuffers[bufferCount];
-
-        // acquire all
-        for (size_t i = 0; i < bufferCount; i++) {
-            ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
-            acquiredBuffers[i] = pool->acquire();
-            ASSERT_NE(nullptr, acquiredBuffers[i]);
-        }
-
-        // release half
-        for (size_t i = 0; i < bufferCount / 2; i++) {
-            ASSERT_EQ(i, pool->getAvailableBufferCount());
-            acquiredBuffers[i]->release();
-            acquiredBuffers[i] = nullptr;
-        }
-
-        const size_t expectedRemaining = bufferCount / 2;
-        ASSERT_EQ(expectedRemaining, pool->getAvailableBufferCount());
-
-        // acquire half
-        for (size_t i = 0; i < bufferCount / 2; i++) {
-            ASSERT_EQ(expectedRemaining - i, pool->getAvailableBufferCount());
-            acquiredBuffers[i] = pool->acquire();
-        }
-
-        // acquire one more, should fail
-        ASSERT_EQ(nullptr, pool->acquire());
-
-        // release all
-        for (size_t i = 0; i < bufferCount; i++) {
-            ASSERT_EQ(i, pool->getAvailableBufferCount());
-            acquiredBuffers[i]->release();
-            acquiredBuffers[i] = nullptr;
-        }
-
-        ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
-    }
-}
-
-};
-};
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index f09f654..60444e0 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -277,6 +277,30 @@
         public static final int HOTWORD = 1999;
     }
 
+    // TODO make AudioSource static (API change) and move this method inside the AudioSource class
+    /**
+     * @hide
+     * @param source An audio source to test
+     * @return true if the source is only visible to system components
+     */
+    public static boolean isSystemOnlyAudioSource(int source) {
+        switch(source) {
+        case AudioSource.DEFAULT:
+        case AudioSource.MIC:
+        case AudioSource.VOICE_UPLINK:
+        case AudioSource.VOICE_DOWNLINK:
+        case AudioSource.VOICE_CALL:
+        case AudioSource.CAMCORDER:
+        case AudioSource.VOICE_RECOGNITION:
+        case AudioSource.VOICE_COMMUNICATION:
+        //case REMOTE_SUBMIX:  considered "system" as it requires system permissions
+        case AudioSource.UNPROCESSED:
+            return false;
+        default:
+            return true;
+        }
+    }
+
     /**
      * Defines the video source. These constants are used with
      * {@link MediaRecorder#setVideoSource(int)}.
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 707db06..8f022db 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -16,12 +16,17 @@
 
 package android.media.soundtrigger;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.media.AudioFormat;
 import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -29,6 +34,8 @@
 import com.android.internal.app.ISoundTriggerService;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.UUID;
 
 /**
@@ -45,6 +52,12 @@
     private static final boolean DBG = false;
     private static final String TAG = "SoundTriggerDetector";
 
+    private static final int MSG_AVAILABILITY_CHANGED = 1;
+    private static final int MSG_SOUND_TRIGGER_DETECTED = 2;
+    private static final int MSG_DETECTION_ERROR = 3;
+    private static final int MSG_DETECTION_PAUSE = 4;
+    private static final int MSG_DETECTION_RESUME = 5;
+
     private final Object mLock = new Object();
 
     private final ISoundTriggerService mSoundTriggerService;
@@ -53,7 +66,121 @@
     private final Handler mHandler;
     private final RecognitionCallback mRecognitionCallback;
 
-    public abstract class Callback {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true,
+            value = {
+                RECOGNITION_FLAG_NONE,
+                RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
+                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+            })
+    public @interface RecognitionFlags {}
+
+    /**
+     * Empty flag for {@link #startRecognition(int)}.
+     *
+     *  @hide
+     */
+    public static final int RECOGNITION_FLAG_NONE = 0;
+
+    /**
+     * Recognition flag for {@link #startRecognition(int)} that indicates
+     * whether the trigger audio for hotword needs to be captured.
+     */
+    public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1;
+
+    /**
+     * Recognition flag for {@link #startRecognition(int)} that indicates
+     * whether the recognition should keep going on even after the
+     * model triggers.
+     * If this flag is specified, it's possible to get multiple
+     * triggers after a call to {@link #startRecognition(int)}, if the model
+     * triggers multiple times.
+     * When this isn't specified, the default behavior is to stop recognition once the
+     * trigger happenss, till the caller starts recognition again.
+     */
+    public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
+
+    /**
+     * Additional payload for {@link Callback#onDetected}.
+     */
+    public static class EventPayload {
+        private final boolean mTriggerAvailable;
+
+        // Indicates if {@code captureSession} can be used to continue capturing more audio
+        // from the DSP hardware.
+        private final boolean mCaptureAvailable;
+        // The session to use when attempting to capture more audio from the DSP hardware.
+        private final int mCaptureSession;
+        private final AudioFormat mAudioFormat;
+        // Raw data associated with the event.
+        // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
+        private final byte[] mData;
+
+        private EventPayload(boolean triggerAvailable, boolean captureAvailable,
+                AudioFormat audioFormat, int captureSession, byte[] data) {
+            mTriggerAvailable = triggerAvailable;
+            mCaptureAvailable = captureAvailable;
+            mCaptureSession = captureSession;
+            mAudioFormat = audioFormat;
+            mData = data;
+        }
+
+        /**
+         * Gets the format of the audio obtained using {@link #getTriggerAudio()}.
+         * May be null if there's no audio present.
+         */
+        @Nullable
+        public AudioFormat getCaptureAudioFormat() {
+            return mAudioFormat;
+        }
+
+        /**
+         * Gets the raw audio that triggered the keyphrase.
+         * This may be null if the trigger audio isn't available.
+         * If non-null, the format of the audio can be obtained by calling
+         * {@link #getCaptureAudioFormat()}.
+         *
+         * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
+         */
+        @Nullable
+        public byte[] getTriggerAudio() {
+            if (mTriggerAvailable) {
+                return mData;
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the session ID to start a capture from the DSP.
+         * This may be null if streaming capture isn't possible.
+         * If non-null, the format of the audio that can be captured can be
+         * obtained using {@link #getCaptureAudioFormat()}.
+         *
+         * TODO: Candidate for Public API when the API to start capture with a session ID
+         * is made public.
+         *
+         * TODO: Add this to {@link #getCaptureAudioFormat()}:
+         * "Gets the format of the audio obtained using {@link #getTriggerAudio()}
+         * or {@link #getCaptureSession()}. May be null if no audio can be obtained
+         * for either the trigger or a streaming session."
+         *
+         * TODO: Should this return a known invalid value instead?
+         *
+         * @hide
+         */
+        @Nullable
+        public Integer getCaptureSession() {
+            if (mCaptureAvailable) {
+                return mCaptureSession;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    public static abstract class Callback {
         /**
          * Called when the availability of the sound model changes.
          */
@@ -63,7 +190,7 @@
          * Called when the sound model has triggered (such as when it matched a
          * given sound pattern).
          */
-        public abstract void onDetected();
+        public abstract void onDetected(@NonNull EventPayload eventPayload);
 
         /**
          *  Called when the detection fails due to an error.
@@ -95,9 +222,9 @@
         mSoundModelId = soundModelId;
         mCallback = callback;
         if (handler == null) {
-            mHandler = new Handler();
+            mHandler = new MyHandler();
         } else {
-            mHandler = handler;
+            mHandler = new MyHandler(handler.getLooper());
         }
         mRecognitionCallback = new RecognitionCallback();
     }
@@ -107,13 +234,19 @@
      * {@link Callback}.
      * @return Indicates whether the call succeeded or not.
      */
-    public boolean startRecognition() {
+    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
         if (DBG) {
             Slog.d(TAG, "startRecognition()");
         }
+        boolean captureTriggerAudio =
+                (recognitionFlags & RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
+
+        boolean allowMultipleTriggers =
+                (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
         try {
             mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
-                    mRecognitionCallback);
+                    mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
+                        allowMultipleTriggers, null, null));
         } catch (RemoteException e) {
             return false;
         }
@@ -144,17 +277,25 @@
 
     /**
      * Callback that handles events from the lower sound trigger layer.
+     *
+     * Note that these callbacks will be called synchronously from the SoundTriggerService
+     * layer and thus should do minimal work (such as sending a message on a handler to do
+     * the real work).
      * @hide
      */
-    private static class RecognitionCallback extends
-            IRecognitionStatusCallback.Stub {
+    private class RecognitionCallback extends IRecognitionStatusCallback.Stub {
 
         /**
          * @hide
          */
         @Override
         public void onDetected(SoundTrigger.RecognitionEvent event) {
-            Slog.e(TAG, "onDetected()" + event);
+            Slog.d(TAG, "onDetected()" + event);
+            Message.obtain(mHandler,
+                    MSG_SOUND_TRIGGER_DETECTED,
+                    new EventPayload(event.triggerInData, event.captureAvailable,
+                            event.captureFormat, event.captureSession, event.data))
+                    .sendToTarget();
         }
 
         /**
@@ -162,7 +303,8 @@
          */
         @Override
         public void onError(int status) {
-            Slog.e(TAG, "onError()" + status);
+            Slog.d(TAG, "onError()" + status);
+            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
         }
 
         /**
@@ -170,7 +312,8 @@
          */
         @Override
         public void onRecognitionPaused() {
-            Slog.e(TAG, "onRecognitionPaused()");
+            Slog.d(TAG, "onRecognitionPaused()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
         }
 
         /**
@@ -178,7 +321,44 @@
          */
         @Override
         public void onRecognitionResumed() {
-            Slog.e(TAG, "onRecognitionResumed()");
+            Slog.d(TAG, "onRecognitionResumed()");
+            mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+        }
+    }
+
+    private class MyHandler extends Handler {
+
+        MyHandler() {
+            super();
+        }
+
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mCallback == null) {
+                  Slog.w(TAG, "Received message: " + msg.what + " for NULL callback.");
+                  return;
+            }
+            switch (msg.what) {
+                case MSG_SOUND_TRIGGER_DETECTED:
+                    mCallback.onDetected((EventPayload) msg.obj);
+                    break;
+                case MSG_DETECTION_ERROR:
+                    mCallback.onError();
+                    break;
+                case MSG_DETECTION_PAUSE:
+                    mCallback.onRecognitionPaused();
+                    break;
+                case MSG_DETECTION_RESUME:
+                    mCallback.onRecognitionResumed();
+                    break;
+                default:
+                    super.handleMessage(msg);
+
+            }
         }
     }
 }
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 9ac929b..58e7709 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -94,9 +94,8 @@
             android:name=".OpenExternalDirectoryActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
-                <action android:name="android.intent.action.OPEN_EXTERNAL_DIRECTORY" />
+                <action android:name="android.os.storage.action.OPEN_EXTERNAL_DIRECTORY" />
                 <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="file" />
             </intent-filter>
         </activity>
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index d0bb7e0..ec7dde9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -34,7 +34,6 @@
 import android.content.ContentValues;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -74,8 +73,6 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        final Resources res = getResources();
-
         if (mState.action == ACTION_CREATE) {
             final String mimeType = getIntent().getType();
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
@@ -134,9 +131,13 @@
         }
 
         if (state.action == ACTION_PICK_COPY_DESTINATION) {
+            // Indicates that a copy operation (or move) includes a directory.
+            // Why? Directory creation isn't supported by some roots (like Downloads).
+            // This allows us to restrict available roots to just those with support.
             state.directoryCopy = intent.getBooleanExtra(
                     Shared.EXTRA_DIRECTORY_COPY, false);
-            state.transferMode = intent.getIntExtra(FileOperationService.EXTRA_OPERATION,
+            state.copyOperationSubType = intent.getIntExtra(
+                    FileOperationService.EXTRA_OPERATION,
                     FileOperationService.OPERATION_COPY);
         }
     }
@@ -156,6 +157,9 @@
         if (external && mState.action == ACTION_GET_CONTENT) {
             showDrawer = true;
         }
+        if (mState.action == ACTION_PICK_COPY_DESTINATION) {
+            showDrawer = true;
+        }
 
         if (showDrawer) {
             mNavigator.revealRootsDrawer(true);
@@ -307,7 +311,7 @@
             mState.action == ACTION_PICK_COPY_DESTINATION) {
             final PickFragment pick = PickFragment.get(fm);
             if (pick != null) {
-                pick.setPickTarget(mState.action, mState.transferMode, cwd);
+                pick.setPickTarget(mState.action, mState.copyOperationSubType, cwd);
             }
         }
     }
@@ -420,7 +424,7 @@
             // Picking a copy destination is only used internally by us, so we
             // don't need to extend permissions to the caller.
             intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
-            intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.transferMode);
+            intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.copyOperationSubType);
         } else {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index d601550..025faea 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -17,6 +17,8 @@
 package com.android.documentsui;
 
 import static android.os.Environment.isStandardDirectory;
+import static android.os.storage.StorageVolume.EXTRA_DIRECTORY_NAME;
+import static android.os.storage.StorageVolume.EXTRA_STORAGE_VOLUME;
 import static com.android.documentsui.Shared.DEBUG;
 
 import android.app.Activity;
@@ -35,9 +37,11 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
 import android.provider.DocumentsContract;
 import android.text.TextUtils;
@@ -63,16 +67,31 @@
         super.onCreate(savedInstanceState);
 
         final Intent intent = getIntent();
-        if (intent == null || intent.getData() == null) {
-            Log.d(TAG, "missing intent or intent data: " + intent);
+        if (intent == null) {
+            if (DEBUG) Log.d(TAG, "missing intent");
+            setResult(RESULT_CANCELED);
+            finish();
+            return;
+        }
+        final Parcelable storageVolume = intent.getParcelableExtra(EXTRA_STORAGE_VOLUME);
+        if (!(storageVolume instanceof StorageVolume)) {
+            if (DEBUG)
+                Log.d(TAG, "extra " + EXTRA_STORAGE_VOLUME + " is not a StorageVolume: "
+                        + storageVolume);
+            setResult(RESULT_CANCELED);
+            finish();
+            return;
+        }
+        final String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME);
+        if (directoryName == null) {
+            if (DEBUG) Log.d(TAG, "missing extra " + EXTRA_DIRECTORY_NAME + " on " + intent);
             setResult(RESULT_CANCELED);
             finish();
             return;
         }
 
-        final String path = intent.getData().getPath();
         final int userId = UserHandle.myUserId();
-        if (!showFragment(this, userId, path)) {
+        if (!showFragment(this, userId, (StorageVolume) storageVolume, directoryName)) {
             setResult(RESULT_CANCELED);
             finish();
             return;
@@ -80,20 +99,20 @@
     }
 
     /**
-     * Validates the given {@code path} and display the appropriate dialog asking the user to grant
-     * access to it.
+     * Validates the given path (volume + directory) and display the appropriate dialog asking the
+     * user to grant access to it.
      */
-    static boolean showFragment(Activity activity, int userId, String path) {
-        Log.d(TAG, "showFragment() for path " + path + " and user " + userId);
-        if (path == null) {
-            Log.e(TAG, "INTERNAL ERROR: showFragment() with null path");
-            return false;
-        }
+    private static boolean showFragment(Activity activity, int userId, StorageVolume storageVolume,
+            String directoryName) {
+        if (DEBUG)
+            Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
+                    + directoryName + ", and user " + userId);
         File file;
         try {
-            file = new File(new File(path).getCanonicalPath());
+            file = new File(storageVolume.getPathFile(), directoryName).getCanonicalFile();
         } catch (IOException e) {
-            Log.e(TAG, "Could not get canonical file from " + path);
+            Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
+                    + " and directory " + directoryName);
             return false;
         }
         final StorageManager sm =
@@ -104,7 +123,9 @@
 
         // Verify directory is valid.
         if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
-            Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '" + path + "')");
+            if (DEBUG)
+                Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
+                        + file.getAbsolutePath() + "')");
             return false;
         }
 
@@ -123,7 +144,7 @@
             }
         }
         if (volumeLabel == null) {
-            Log.e(TAG, "Could not get volume for " + path);
+            Log.e(TAG, "Could not get volume for " + file);
             return false;
         }
 
@@ -165,13 +186,13 @@
         final File userPath = volume.getPathForUser(userId);
         final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
         final boolean isVisible = volume.isVisibleForWrite(userId);
-        if (DEBUG) {
+        if (DEBUG)
             Log.d(TAG, "Volume: " + volume + " userId: " + userId + " root: " + root
                     + " volumePath: " + volume.getPath().getPath()
                     + " pathForUser: " + path
                     + " internalPathForUser: " + volume.getInternalPath()
                     + " isVisible: " + isVisible);
-        }
+
         return volume.isVisibleForWrite(userId) && root.equals(path);
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index bbf4682..287c904 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -16,6 +16,10 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -27,6 +31,7 @@
 import android.widget.Button;
 
 import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
 
 /**
  * Display pick confirmation bar, usually for selecting a directory.
@@ -35,7 +40,7 @@
     public static final String TAG = "PickFragment";
 
     private int mAction;
-    private int mTransferMode;
+    private @OpType int mOperationType;
     private DocumentInfo mPickTarget;
     private View mContainer;
     private Button mPick;
@@ -92,9 +97,10 @@
     /**
      * @param action Which action defined in State is the picker shown for.
      */
-    public void setPickTarget(int action, int transferMode, DocumentInfo pickTarget) {
+    public void setPickTarget(int action, @OpType int operationType, DocumentInfo pickTarget) {
+        checkArgument(operationType == OPERATION_COPY || operationType == OPERATION_MOVE);
         mAction = action;
-        mTransferMode = transferMode;
+        mOperationType = operationType;
         mPickTarget = pickTarget;
         if (mContainer != null) {
             updateView();
@@ -111,7 +117,8 @@
                 mCancel.setVisibility(View.GONE);
                 break;
             case State.ACTION_PICK_COPY_DESTINATION:
-                mPick.setText(R.string.button_copy);
+                mPick.setText(mOperationType == OPERATION_MOVE
+                        ? R.string.button_move : R.string.button_copy);
                 mCancel.setVisibility(View.VISIBLE);
                 break;
             default:
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index c34cec0..a77a9b3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -33,16 +33,19 @@
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Range;
 
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.model.DocumentInfo;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Provides support for gather a list of quick-viewable files into a quick view intent.
  */
 final class QuickViewIntentBuilder {
+    private static final int MAX_CLIP_ITEMS = 1000;
 
     private final DocumentInfo mDocument;
     private final Model mModel;
@@ -50,9 +53,6 @@
     private final PackageManager mPkgManager;
     private final Resources mResources;
 
-    private ClipData mClipData;
-    private int mDocumentLocation;
-
     public QuickViewIntentBuilder(
             PackageManager pkgManager,
             Resources resources,
@@ -80,12 +80,28 @@
             intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             intent.setPackage(trustedPkg);
             if (hasRegisteredHandler(intent)) {
-                List<String> siblingIds = mModel.getModelIds();
-                for (int i = 0; i < siblingIds.size(); i++) {
-                    onNextItem(i, siblingIds);
+                final ArrayList<Uri> uris = new ArrayList<Uri>();
+                final int documentLocation = collectViewableUris(uris);
+                final Range<Integer> range = computeSiblingsRange(uris, documentLocation);
+
+                ClipData clipData = null;
+                ClipData.Item item;
+                Uri uri;
+                for (int i = range.getLower(); i <= range.getUpper(); i++) {
+                    uri = uris.get(i);
+                    item = new ClipData.Item(uri);
+                    if (DEBUG) Log.d(TAG, "Including file: " + uri);
+                    if (clipData == null) {
+                        clipData = new ClipData(
+                                "URIs", new String[] { ClipDescription.MIMETYPE_TEXT_URILIST },
+                                item);
+                    } else {
+                        clipData.addItem(item);
+                    }
                 }
-                intent.putExtra(Intent.EXTRA_INDEX, mDocumentLocation);
-                intent.setClipData(mClipData);
+
+                intent.putExtra(Intent.EXTRA_INDEX, documentLocation);
+                intent.setClipData(clipData);
 
                 return intent;
             } else {
@@ -96,39 +112,63 @@
         return null;
     }
 
+    private int collectViewableUris(ArrayList<Uri> uris) {
+        final List<String> siblingIds = mModel.getModelIds();
+        uris.ensureCapacity(siblingIds.size());
+
+        int documentLocation = 0;
+        Cursor cursor;
+        String mimeType;
+        String id;
+        String authority;
+        Uri uri;
+
+        // Cursor's are not guaranteed to be immutable. Hence, traverse it only once.
+        for (int i = 0; i < siblingIds.size(); i++) {
+            cursor = mModel.getItem(siblingIds.get(i));
+
+            mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+            if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+                continue;
+            }
+
+            id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+            authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+            uri = DocumentsContract.buildDocumentUri(authority, id);
+
+            if (id.equals(mDocument.documentId)) {
+                if (DEBUG) Log.d(TAG, "Found starting point for QV. " + i);
+                documentLocation = i;
+            }
+
+            uris.add(uri);
+        }
+
+        return documentLocation;
+    }
+
+    private static Range<Integer> computeSiblingsRange(List<Uri> uris, int documentLocation) {
+        // Restrict number of siblings to avoid hitting the IPC limit.
+        // TODO: Remove this restriction once ClipData can hold an arbitrary number of
+        // items.
+        int firstSibling;
+        int lastSibling;
+        if (documentLocation < uris.size() / 2) {
+            firstSibling = Math.max(0, documentLocation - MAX_CLIP_ITEMS / 2);
+            lastSibling = Math.min(uris.size() - 1, firstSibling + MAX_CLIP_ITEMS - 1);
+        } else {
+            lastSibling = Math.min(uris.size() - 1, documentLocation + MAX_CLIP_ITEMS / 2);
+            firstSibling = Math.max(0, lastSibling - MAX_CLIP_ITEMS + 1);
+        }
+
+        if (DEBUG) Log.d(TAG, "Copmuted siblings from index: " + firstSibling
+                + " to: " + lastSibling);
+
+        return new Range(firstSibling, lastSibling);
+    }
+
     private boolean hasRegisteredHandler(Intent intent) {
         // Try to resolve the intent. If a matching app isn't installed, it won't resolve.
         return intent.resolveActivity(mPkgManager) != null;
     }
-
-    private void onNextItem(int index, List<String> siblingIds) {
-        final Cursor cursor = mModel.getItem(siblingIds.get(index));
-
-        if (cursor == null) {
-            return;
-        }
-
-        String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
-        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
-            return;
-        }
-
-        String id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
-        String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
-        Uri uri = DocumentsContract.buildDocumentUri(authority, id);
-        if (DEBUG) Log.d(TAG, "Including file[" + id + "] @ " + uri);
-
-        if (id.equals(mDocument.documentId)) {
-            if (DEBUG) Log.d(TAG, "Found starting point for QV. " + index);
-            mDocumentLocation = index;
-        }
-
-        ClipData.Item item = new ClipData.Item(uri);
-        if (mClipData == null) {
-            mClipData = new ClipData(
-                    "URIs", new String[]{ClipDescription.MIMETYPE_TEXT_URILIST}, item);
-        } else {
-            mClipData.addItem(item);
-        }
-    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 29a72b8..fd96391 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -66,7 +66,7 @@
     private final ContentObserver mObserver;
     private OnCacheUpdateListener mCacheUpdateListener;
 
-    private final RootInfo mRecentsRoot = new RootInfo();
+    private final RootInfo mRecentsRoot;
 
     private final Object mLock = new Object();
     private final CountDownLatch mFirstLoad = new CountDownLatch(1);
@@ -82,6 +82,18 @@
     public RootsCache(Context context) {
         mContext = context;
         mObserver = new RootsChangedObserver();
+
+        // Create a new anonymous "Recents" RootInfo. It's a faker.
+        mRecentsRoot = new RootInfo() {{
+            // Special root for recents
+            authority = null;
+            rootId = null;
+            derivedIcon = R.drawable.ic_root_recent;
+            derivedType = RootInfo.TYPE_RECENTS;
+            flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD;
+            title = mContext.getString(R.string.root_recent);
+            availableBytes = -1;
+        }};
     }
 
     private class RootsChangedObserver extends ContentObserver {
@@ -104,16 +116,6 @@
      * Gather roots from all known storage providers.
      */
     public void updateAsync() {
-        // Special root for recents
-        mRecentsRoot.authority = null;
-        mRecentsRoot.rootId = null;
-        mRecentsRoot.derivedIcon = R.drawable.ic_root_recent;
-        mRecentsRoot.derivedType = RootInfo.TYPE_RECENTS;
-        mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE
-                | Root.FLAG_SUPPORTS_IS_CHILD;
-        mRecentsRoot.title = mContext.getString(R.string.root_recent);
-        mRecentsRoot.availableBytes = -1;
-
         new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
@@ -360,7 +362,7 @@
     }
 
     public boolean isRecentsRoot(RootInfo root) {
-        return mRecentsRoot == root;
+        return mRecentsRoot.equals(root);
     }
 
     public Collection<RootInfo> getRootsBlocking() {
@@ -400,27 +402,22 @@
     static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
         final List<RootInfo> matching = new ArrayList<>();
         for (RootInfo root : roots) {
-            final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
-            final boolean supportsIsChild = (root.flags & Root.FLAG_SUPPORTS_IS_CHILD) != 0;
-            final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
-            final boolean localOnly = (root.flags & Root.FLAG_LOCAL_ONLY) != 0;
-            final boolean empty = (root.flags & Root.FLAG_EMPTY) != 0;
-
             // Exclude read-only devices when creating
-            if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
-            if (state.action == State.ACTION_PICK_COPY_DESTINATION && !supportsCreate) continue;
+            if (state.action == State.ACTION_CREATE && !root.supportsCreate()) continue;
+            if (state.action == State.ACTION_PICK_COPY_DESTINATION
+                    && !root.supportsCreate()) continue;
             // Exclude roots that don't support directory picking
-            if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue;
+            if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) continue;
             // Exclude advanced devices when not requested
-            if (!state.showAdvanced && advanced) continue;
+            if (!state.showAdvanced && root.isAdvanced()) continue;
             // Exclude non-local devices when local only
-            if (state.localOnly && !localOnly) continue;
+            if (state.localOnly && !root.isLocalOnly()) continue;
             // Exclude downloads roots that don't support directory creation
             // TODO: Add flag to check the root supports directory creation or not.
-            if (state.directoryCopy && root.isDownloads()) continue;
+            if (state.directoryCopy && !root.supportsChildren()) continue;
 
             // Only show empty roots when creating, or in browse mode.
-            if (empty && (state.action == State.ACTION_OPEN
+            if (root.isEmpty() && (state.action == State.ACTION_OPEN
                     || state.action == State.ACTION_GET_CONTENT)) {
                 if (DEBUG) Log.i(TAG, "Skipping empty root: " + root);
                 continue;
@@ -436,10 +433,13 @@
 
             // Exclude roots from the calling package.
             if (state.excludedAuthorities.contains(root.authority)) {
-                if (DEBUG) Log.d(TAG, "Excluding root " + root.authority + " from calling package.");
+                if (DEBUG) Log.d(
+                        TAG, "Excluding root " + root.authority + " from calling package.");
                 continue;
             }
 
+            if (DEBUG) Log.d(
+                    TAG, "Including root " + root + " in roots list.");
             matching.add(root);
         }
         return matching;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 53f8297..f908eeb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -30,6 +30,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.text.format.Formatter;
 import android.util.Log;
@@ -86,7 +87,6 @@
     @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        final Context context = inflater.getContext();
 
         final View view = inflater.inflate(R.layout.fragment_roots, container, false);
         mList = (ListView) view.findViewById(R.id.roots_list);
@@ -112,11 +112,13 @@
             @Override
             public void onLoadFinished(
                     Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
-                if (!isAdded()) return;
+                if (!isAdded()) {
+                    return;
+                }
 
-                final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
+                Intent handlerAppIntent = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
 
-                mAdapter = new RootsAdapter(context, result, includeApps);
+                mAdapter = new RootsAdapter(context, result, handlerAppIntent);
                 mList.setAdapter(mAdapter);
 
                 onCurrentRootChanged();
@@ -151,7 +153,9 @@
     }
 
     public void onCurrentRootChanged() {
-        if (mAdapter == null) return;
+        if (mAdapter == null) {
+            return;
+        }
 
         final RootInfo root = ((BaseActivity) getActivity()).getCurrentRoot();
         for (int i = 0; i < mAdapter.getCount(); i++) {
@@ -300,7 +304,13 @@
     }
 
     private static class RootsAdapter extends ArrayAdapter<Item> {
-        public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
+
+        /**
+         * @param handlerAppIntent When not null, apps capable of handling the original
+         *     intent will be included in list of roots (in special section at bottom).
+         */
+        public RootsAdapter(
+                Context context, Collection<RootInfo> roots, @Nullable Intent handlerAppIntent) {
             super(context, 0);
 
             final List<RootItem> libraries = new ArrayList<>();
@@ -322,27 +332,39 @@
             Collections.sort(others, comp);
 
             addAll(libraries);
-            add(new SpacerItem());
+            // Only add the spacer if it is actually separating something.
+            if (!libraries.isEmpty() && !others.isEmpty()) {
+                add(new SpacerItem());
+            }
             addAll(others);
 
-            if (includeApps != null) {
-                final PackageManager pm = context.getPackageManager();
-                final List<ResolveInfo> infos = pm.queryIntentActivities(
-                        includeApps, PackageManager.MATCH_DEFAULT_ONLY);
+            // Include apps that can handle this intent too.
+            if (handlerAppIntent != null) {
+                includeHandlerApps(context, handlerAppIntent);
+            }
+        }
 
-                final List<AppItem> apps = new ArrayList<>();
+        /**
+         * Adds apps capable of handling the original intent will be included
+         * in list of roots (in special section at bottom).
+         */
+        private void includeHandlerApps(Context context, Intent handlerAppIntent) {
+            final PackageManager pm = context.getPackageManager();
+            final List<ResolveInfo> infos = pm.queryIntentActivities(
+                    handlerAppIntent, PackageManager.MATCH_DEFAULT_ONLY);
 
-                // Omit ourselves from the list
-                for (ResolveInfo info : infos) {
-                    if (!context.getPackageName().equals(info.activityInfo.packageName)) {
-                        apps.add(new AppItem(info));
-                    }
+            final List<AppItem> apps = new ArrayList<>();
+
+            // Omit ourselves from the list
+            for (ResolveInfo info : infos) {
+                if (!context.getPackageName().equals(info.activityInfo.packageName)) {
+                    apps.add(new AppItem(info));
                 }
+            }
 
-                if (apps.size() > 0) {
-                    add(new SpacerItem());
-                    addAll(apps);
-                }
+            if (apps.size() > 0) {
+                add(new SpacerItem());
+                addAll(apps);
             }
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
index c81377a..5c4ccdd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java
@@ -50,7 +50,7 @@
         if (isReset()) {
             return;
         }
-        Collection<RootInfo> oldResult = mResult;
+
         mResult = result;
 
         if (isStarted()) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 7dca8a7..81a0635 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -30,6 +30,7 @@
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
 import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -83,10 +84,18 @@
     public boolean forceAdvanced;
     public boolean showAdvanced;
     public boolean restored;
+
+    // Indicates that a copy operation (or move) includes a directory.
+    // Why? Directory creation isn't supported by some roots (like Downloads).
+    // This allows us to restrict available roots to just those with support.
     public boolean directoryCopy;
     public boolean openableOnly;
-    /** Transfer mode for file copy/move operations. */
-    public int transferMode;
+
+    /**
+     * This is basically a sub-type for the copy operation. It can be either COPY or MOVE.
+     * The only legal values are: OPERATION_COPY, OPERATION_MOVE.
+     */
+    public @OpType int copyOperationSubType;
 
     /** Current user navigation stack; empty implies recents. */
     public DocumentStack stack = new DocumentStack();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 4233b36..236fa94 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -101,6 +101,7 @@
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
 import com.android.documentsui.services.FileOperations;
+
 import com.google.common.collect.Lists;
 
 import java.lang.annotation.Retention;
@@ -130,6 +131,11 @@
     public static final int ANIM_LEAVE = 3;
     public static final int ANIM_ENTER = 4;
 
+    @IntDef(flag = true, value = {
+            REQUEST_COPY_DESTINATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequestCode {}
     public static final int REQUEST_COPY_DESTINATION = 1;
 
     static final boolean DEBUG_ENABLE_DND = true;
@@ -193,11 +199,6 @@
 
         mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
 
-        // Make the RecyclerView unfocusable. This is needed in order for the focus search code in
-        // FocusManager to work correctly. Setting android:focusable=false in the layout xml doesn't
-        // work, for some reason.
-        mRecView.setFocusable(false);
-
         // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
         if (DEBUG_ENABLE_DND) {
             setupDragAndDropOnDirectoryView(mRecView);
@@ -377,19 +378,24 @@
     }
 
     @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        // There's only one request code right now. Replace this with a switch statement or
-        // something more scalable when more codes are added.
-        if (requestCode != REQUEST_COPY_DESTINATION) {
-            return;
+    public void onActivityResult(@RequestCode int requestCode, int resultCode, Intent data) {
+        switch(requestCode) {
+            case REQUEST_COPY_DESTINATION:
+                handleCopyResult(resultCode, data);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown request code: " + requestCode);
         }
+    }
+
+    private void handleCopyResult(int resultCode, Intent data) {
         if (resultCode == Activity.RESULT_CANCELED || data == null) {
             // User pressed the back button or otherwise cancelled the destination pick. Don't
             // proceed with the copy.
             return;
         }
 
-        int operationType = data.getIntExtra(
+        @OpType int operationType = data.getIntExtra(
                 FileOperationService.EXTRA_OPERATION,
                 FileOperationService.OPERATION_COPY);
 
@@ -536,7 +542,11 @@
         @Override
         public void onItemStateChanged(String modelId, boolean selected) {
             final Cursor cursor = mModel.getItem(modelId);
-            checkNotNull(cursor, "Cursor cannot be null.");
+            if (cursor == null) {
+                Log.e(TAG, "Model returned null cursor for document: " + modelId
+                        + ". Ignoring state changed event.");
+                return;
+            }
 
             // TODO: Should this be happening in onSelectionChanged? Technically this callback is
             // triggered on "silent" selection updates (i.e. we might be reacting to unfinalized
@@ -804,25 +814,43 @@
                 getActivity(),
                 DocumentsActivity.class);
 
+        // Set an appropriate title on the drawer when it is shown in the picker.
+        // Coupled with the fact that we auto-open the drawer for copy/move operations
+        // it should basically be the thing people see first.
+        int drawerTitleId = mode == FileOperationService.OPERATION_MOVE
+                ? R.string.menu_move : R.string.menu_copy;
+        intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId));
+
         new GetDocumentsTask() {
             @Override
             void onDocumentsReady(List<DocumentInfo> docs) {
+                // TODO: Can this move to Fragment bundle state?
                 getDisplayState().selectedDocumentsForCopy = docs;
 
-                boolean directoryCopy = false;
-                for (DocumentInfo info : docs) {
-                    if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
-                        directoryCopy = true;
-                        break;
-                    }
-                }
-                intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
+                // Determine if there is a directory in the set of documents
+                // to be copied? Why? Directory creation isn't supported by some roots
+                // (like Downloads). This informs DocumentsActivity (the "picker")
+                // to restrict available roots to just those with support.
+                intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs));
                 intent.putExtra(FileOperationService.EXTRA_OPERATION, mode);
+
+                // This just identifies the type of request...we'll check it
+                // when we reveive a response.
                 startActivityForResult(intent, REQUEST_COPY_DESTINATION);
             }
+
         }.execute(selected);
     }
 
+    private static boolean hasDirectory(List<DocumentInfo> docs) {
+        for (DocumentInfo info : docs) {
+            if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void renameDocuments(Selection selected) {
         // Batch renaming not supported
         // Rename option is only available in menu when 1 document selected
@@ -957,7 +985,8 @@
     }
 
     private void copyDocuments(final List<DocumentInfo> docs, final DocumentInfo destination) {
-        if (!canCopy(docs, destination)) {
+        BaseActivity activity = (BaseActivity) getActivity();
+        if (!canCopy(docs, activity.getCurrentRoot(), destination)) {
             Snackbars.makeSnackbar(
                     getActivity(),
                     R.string.clipboard_files_cannot_paste,
@@ -1037,13 +1066,13 @@
      *
      * @return true if the list of files can be copied to destination.
      */
-    boolean canCopy(List<DocumentInfo> files, DocumentInfo dest) {
-        BaseActivity activity = (BaseActivity) getActivity();
+    private boolean canCopy(List<DocumentInfo> files, RootInfo root, DocumentInfo dest) {
+        if (dest == null || !dest.isDirectory() || !dest.isCreateSupported()) {
+            return false;
+        }
 
-        final RootInfo root = activity.getCurrentRoot();
-
-        // Can't copy folders to Downloads.
-        if (root.isDownloads()) {
+        // Can't copy folders to roots that don't support children.
+        if (!root.supportsChildren()) {
             for (DocumentInfo docs : files) {
                 if (docs.isDirectory()) {
                     return false;
@@ -1051,7 +1080,7 @@
             }
         }
 
-        return dest != null && dest.isDirectory() && dest.isCreateSupported();
+        return true;
     }
 
     public void selectAllFiles() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
index 93ec842..e90a447 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FocusManager.java
@@ -158,7 +158,14 @@
         }
 
         if (searchDir != -1) {
+            // Focus search behaves badly if the parent RecyclerView is focused. However, focusable
+            // shouldn't be unset on RecyclerView, otherwise focus isn't properly restored after
+            // events that cause a UI rebuild (like rotating the device). Compromise: turn focusable
+            // off while performing the focus search.
+            // TODO: Revisit this when RV focus issues are resolved.
+            mView.setFocusable(false);
             View targetView = view.focusSearch(searchDir);
+            mView.setFocusable(true);
             // TargetView can be null, for example, if the user pressed <down> at the bottom
             // of the list.
             if (targetView != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 4b5499a..1c696ad 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -39,6 +39,7 @@
 import java.io.IOException;
 import java.net.ProtocolException;
 import java.text.Collator;
+import java.util.Objects;
 
 /**
  * Representation of a {@link Document}.
@@ -263,16 +264,23 @@
         return derivedUri.hashCode() + mimeType.hashCode();
     }
 
-    public boolean equals(Object other) {
-        if (this == other) {
-            return true;
-        } else if (!(other instanceof DocumentInfo)) {
+    public boolean equals(Object o) {
+        if (o == null) {
             return false;
         }
 
-        DocumentInfo that = (DocumentInfo) other;
-        // Uri + mime type should be totally unique.
-        return derivedUri.equals(that.derivedUri) && mimeType.equals(that.mimeType);
+        if (this == o) {
+            return true;
+        }
+
+        if (o instanceof DocumentInfo) {
+            DocumentInfo other = (DocumentInfo) o;
+            // Uri + mime type should be totally unique.
+            return Objects.equals(derivedUri, other.derivedUri)
+                    && Objects.equals(mimeType, other.mimeType);
+        }
+
+        return false;
     }
 
     public static String getCursorString(Cursor cursor, String columnName) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index ffa8f59..8cbbb6c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -249,6 +249,26 @@
         return (flags & Root.FLAG_HAS_SETTINGS) != 0;
     }
 
+    public boolean supportsChildren() {
+        return (flags & Root.FLAG_SUPPORTS_IS_CHILD) != 0;
+    }
+
+    public boolean supportsCreate() {
+        return (flags & Root.FLAG_SUPPORTS_CREATE) != 0;
+    }
+
+    public boolean isAdvanced() {
+        return (flags & Root.FLAG_ADVANCED) != 0;
+    }
+
+    public boolean isLocalOnly() {
+        return (flags & Root.FLAG_LOCAL_ONLY) != 0;
+    }
+
+    public boolean isEmpty() {
+        return (flags & Root.FLAG_EMPTY) != 0;
+    }
+
     public Drawable loadIcon(Context context) {
         if (derivedIcon != 0) {
             return context.getDrawable(derivedIcon);
@@ -276,12 +296,21 @@
 
     @Override
     public boolean equals(Object o) {
-        if (o instanceof RootInfo) {
-            final RootInfo root = (RootInfo) o;
-            return Objects.equals(authority, root.authority) && Objects.equals(rootId, root.rootId);
-        } else {
+        if (o == null) {
             return false;
         }
+
+        if (this == o) {
+            return true;
+        }
+
+        if (o instanceof RootInfo) {
+            RootInfo other = (RootInfo) o;
+            return Objects.equals(authority, other.authority)
+                    && Objects.equals(rootId, other.rootId);
+        }
+
+        return false;
     }
 
     @Override
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
index 3a025c2..05a3f11 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
@@ -71,11 +71,6 @@
     // such case, this needs to be replaced with pairs of parent and child.
     public static final String EXTRA_SRC_PARENT = "com.android.documentsui.SRC_PARENT";
 
-    public static final int OPERATION_UNKNOWN = -1;
-    public static final int OPERATION_COPY = 1;
-    public static final int OPERATION_MOVE = 2;
-    public static final int OPERATION_DELETE = 3;
-
     @IntDef(flag = true, value = {
             OPERATION_UNKNOWN,
             OPERATION_COPY,
@@ -84,6 +79,10 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OpType {}
+    public static final int OPERATION_UNKNOWN = -1;
+    public static final int OPERATION_COPY = 1;
+    public static final int OPERATION_MOVE = 2;
+    public static final int OPERATION_DELETE = 3;
 
     // TODO: Move it to a shared file when more operations are implemented.
     public static final int FAILURE_COPY = 1;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 609dc0c..95515db 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -52,7 +52,7 @@
                 "Videos",
                 "Audio",
                 "Downloads",
-                "Home",
+                "Documents",
                 ROOT_0_ID,
                 ROOT_1_ID);
     }
@@ -64,11 +64,11 @@
         bot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
     }
 
-    public void testLoadsHomeByDefault() throws Exception {
+    public void testLoadsHomeDirectoryByDefault() throws Exception {
         initTestFiles();
 
         device.waitForIdle();
-        bot.assertWindowTitle("Home");
+        bot.assertWindowTitle("Documents");
     }
 
     public void testRootClickSetsWindowTitle() throws Exception {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java
index b74b985..f057850 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StateTest.java
@@ -23,18 +23,44 @@
 
 @SmallTest
 public class StateTest extends AndroidTestCase {
-    public void testPushDocument() {
-        final State state = new State();
-        final DocumentInfo infoFirst = new DocumentInfo();
-        infoFirst.displayName = "firstDirectory";
-        final DocumentInfo infoSecond = new DocumentInfo();
-        infoSecond.displayName = "secondDirectory";
-        assertFalse(state.hasLocationChanged());
-        state.pushDocument(infoFirst);
-        state.pushDocument(infoSecond);
-        assertTrue(state.hasLocationChanged());
-        assertEquals("secondDirectory", state.stack.getFirst().displayName);
-        state.popDocument();
-        assertEquals("firstDirectory", state.stack.getFirst().displayName);
+
+    private static final DocumentInfo DIR_1;
+    private static final DocumentInfo DIR_2;
+
+    private State mState;
+
+    static {
+        DIR_1 = new DocumentInfo();
+        DIR_1.displayName = "firstDirectory";
+        DIR_2 = new DocumentInfo();
+        DIR_2.displayName = "secondDirectory";
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        mState = new State();
+    }
+
+    public void testInitialStateEmpty() {
+        assertFalse(mState.hasLocationChanged());
+    }
+
+    public void testPushDocument_ChangesLocation() {
+        mState.pushDocument(DIR_1);
+        mState.pushDocument(DIR_2);
+        assertTrue(mState.hasLocationChanged());
+    }
+
+    public void testPushDocument_ModifiesStack() {
+        mState.pushDocument(DIR_1);
+        mState.pushDocument(DIR_2);
+        assertEquals(DIR_2, mState.stack.getFirst());
+    }
+
+    public void testPopDocument_ModifiesStack() {
+        mState.pushDocument(DIR_1);
+        mState.pushDocument(DIR_2);
+        mState.popDocument();
+        assertEquals(DIR_1, mState.stack.getFirst());
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
index a6aba7b..2481dc3 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
@@ -22,30 +22,36 @@
 @SmallTest
 public class DocumentInfoTest extends AndroidTestCase {
 
+    private static final DocumentInfo TEST_DOC
+            = createDocInfo("authority.a", "doc.1", "text/plain");
+
     public void testEquals() throws Exception {
-        DocumentInfo doc = createDocInfo("authority.a", "doc.1", "text/plain");
-        assertEquals(doc, doc);
+        assertEquals(TEST_DOC, TEST_DOC);
+        assertEquals(TEST_DOC, createDocInfo("authority.a", "doc.1", "text/plain"));
+    }
+
+    public void testEquals_HandlesNulls() throws Exception {
+        assertFalse(TEST_DOC.equals(null));
+    }
+
+    public void testEquals_HandlesNullFields() throws Exception {
+        assertFalse(TEST_DOC.equals(new DocumentInfo()));
+        assertFalse(new DocumentInfo().equals(TEST_DOC));
     }
 
     public void testNotEquals_differentAuthority() throws Exception {
-        DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
-        DocumentInfo docB = createDocInfo("authority.b", "doc.1", "text/plain");
-        assertFalse(docA.equals(docB));
+        assertFalse(TEST_DOC.equals(createDocInfo("authority.b", "doc.1", "text/plain")));
     }
 
     public void testNotEquals_differentDocId() throws Exception {
-        DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
-        DocumentInfo docB = createDocInfo("authority.a", "doc.2", "text/plain");
-        assertFalse(docA.equals(docB));
+        assertFalse(TEST_DOC.equals(createDocInfo("authority.a", "doc.2", "text/plain")));
     }
 
     public void testNotEquals_differentMimetype() throws Exception {
-        DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
-        DocumentInfo docB = createDocInfo("authority.a", "doc.1", "image/png");
-        assertFalse(docA.equals(docB));
+        assertFalse(TEST_DOC.equals(createDocInfo("authority.a", "doc.1", "image/png")));
     }
 
-    private DocumentInfo createDocInfo(String authority, String docId, String mimeType) {
+    private static DocumentInfo createDocInfo(String authority, String docId, String mimeType) {
         DocumentInfo doc = new DocumentInfo();
         doc.authority = authority;
         doc.documentId = docId;
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 96506512..7012eb2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -626,12 +626,12 @@
     <!-- UI debug setting: force all activites to be resizable for multiwindow [CHAR LIMIT=50] -->
     <string name="force_resizable_activities">Force activities to be resizable</string>
     <!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] -->
-    <string name="force_resizable_activities_summary">Makes all activities resizable for multi-window, regardless of manifest values.</string>
+    <string name="force_resizable_activities_summary">Make all activities resizable for multi-window, regardless of manifest values.</string>
 
     <!-- UI debug setting: enable freeform window support [CHAR LIMIT=50] -->
     <string name="enable_freeform_support">Enable freeform windows</string>
     <!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
-    <string name="enable_freeform_support_summary">Enables support for experimental freeform windows.</string>
+    <string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
 
     <!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
     <string name="local_backup_password_title">Desktop backup password</string>
diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
index 6f98509..6f153c1 100644
--- a/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
+++ b/packages/SystemUI/res/layout-sw600dp/navigation_layout_rot90.xml
@@ -41,26 +41,6 @@
 
     </FrameLayout>
 
-    <FrameLayout
-        android:id="@+id/lights_out"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <LinearLayout
-            android:id="@+id/ends_group_lightsout"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="horizontal" />
-
-        <LinearLayout
-            android:id="@+id/center_group_lightsout"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            android:orientation="horizontal" />
-
-    </FrameLayout>
-
     <com.android.systemui.statusbar.policy.DeadZone
         android:id="@+id/deadzone"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_layout.xml b/packages/SystemUI/res/layout/navigation_layout.xml
index 142d13a..2bf4d9c 100644
--- a/packages/SystemUI/res/layout/navigation_layout.xml
+++ b/packages/SystemUI/res/layout/navigation_layout.xml
@@ -42,26 +42,6 @@
 
     </FrameLayout>
 
-    <FrameLayout
-        android:id="@+id/lights_out"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <LinearLayout
-            android:id="@+id/ends_group_lightsout"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="horizontal" />
-
-        <LinearLayout
-            android:id="@+id/center_group_lightsout"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            android:orientation="horizontal" />
-
-    </FrameLayout>
-
     <com.android.systemui.statusbar.policy.DeadZone
         android:id="@+id/deadzone"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/navigation_layout_rot90.xml b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
index 3b7b369..7601efc 100644
--- a/packages/SystemUI/res/layout/navigation_layout_rot90.xml
+++ b/packages/SystemUI/res/layout/navigation_layout_rot90.xml
@@ -42,26 +42,6 @@
 
     </FrameLayout>
 
-    <FrameLayout
-        android:id="@+id/lights_out"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <com.android.systemui.statusbar.phone.ReverseLinearLayout
-            android:id="@+id/ends_group_lightsout"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical" />
-
-        <com.android.systemui.statusbar.phone.ReverseLinearLayout
-            android:id="@+id/center_group_lightsout"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            android:orientation="vertical" />
-
-    </FrameLayout>
-
     <com.android.systemui.statusbar.policy.DeadZone
         android:id="@+id/deadzone"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml
index bed8f1b..858f487 100644
--- a/packages/SystemUI/res/layout/qs_detail.xml
+++ b/packages/SystemUI/res/layout/qs_detail.xml
@@ -50,30 +50,6 @@
             android:layout_height="0dp"
             android:layout_weight="1" />
 
-    <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingEnd="8dp"
-            android:gravity="end">
+    <include layout="@layout/qs_detail_buttons" />
 
-        <TextView
-                android:id="@android:id/button2"
-                style="@style/QSBorderlessButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="8dp"
-                android:minWidth="132dp"
-                android:textAppearance="@style/TextAppearance.QS.DetailButton"
-                android:focusable="true" />
-
-        <TextView
-                android:id="@android:id/button1"
-                style="@style/QSBorderlessButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:minWidth="88dp"
-                android:textAppearance="@style/TextAppearance.QS.DetailButton"
-                android:focusable="true"/>
-
-    </LinearLayout>
 </com.android.systemui.qs.QSDetail>
diff --git a/packages/SystemUI/res/layout/qs_detail_buttons.xml b/packages/SystemUI/res/layout/qs_detail_buttons.xml
new file mode 100644
index 0000000..03ed62b
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_buttons.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingEnd="8dp"
+    android:gravity="end">
+
+    <TextView
+        android:id="@android:id/button2"
+        style="@style/QSBorderlessButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:minWidth="132dp"
+        android:textAppearance="@style/TextAppearance.QS.DetailButton"
+        android:focusable="true" />
+
+    <TextView
+        android:id="@android:id/button1"
+        style="@style/QSBorderlessButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minWidth="88dp"
+        android:textAppearance="@style/TextAppearance.QS.DetailButton"
+        android:focusable="true"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 84df0d6..8df2c280 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -32,21 +32,32 @@
     >
 
     <LinearLayout
-        android:id="@+id/expanded_group"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
+        android:layout_height="48dp"
         android:gravity="center"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:orientation="horizontal"
         android:layout_alignParentEnd="true"
-        android:layout_marginTop="30dp"
-        android:layout_marginEnd="16dp">
+        android:layout_marginTop="28dp"
+        android:layout_marginEnd="12dp">
+
+        <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_alignParentEnd="true"
+            android:background="@drawable/ripple_drawable" >
+            <ImageView android:id="@+id/multi_user_avatar"
+                android:layout_width="@dimen/multi_user_avatar_expanded_size"
+                android:layout_height="@dimen/multi_user_avatar_expanded_size"
+                android:layout_gravity="center"
+                android:scaleType="centerInside"/>
+        </com.android.systemui.statusbar.phone.MultiUserSwitch>
 
         <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
             android:id="@+id/settings_button_container"
             android:layout_width="48dp"
-            android:layout_height="@dimen/status_bar_header_height"
+            android:layout_height="48dp"
             android:clipChildren="false"
             android:clipToPadding="false">
 
@@ -68,24 +79,12 @@
 
         </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
 
-        <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
+        <com.android.systemui.statusbar.phone.ExpandableIndicator
+            android:id="@+id/expand_indicator"
             android:layout_width="48dp"
             android:layout_height="48dp"
-            android:layout_alignParentEnd="true"
-            android:background="@drawable/ripple_drawable" >
-            <ImageView android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_expanded_size"
-                android:layout_height="@dimen/multi_user_avatar_expanded_size"
-                android:layout_gravity="center"
-                android:scaleType="centerInside"/>
-        </com.android.systemui.statusbar.phone.MultiUserSwitch>
+            android:padding="12dp" />
 
-        <ImageView
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:padding="12dp"
-            android:src="@drawable/ic_expand_less"
-            android:tint="@android:color/white" />
     </LinearLayout>
 
     <TextView
@@ -104,44 +103,62 @@
         android:gravity="center_vertical" />
 
     <LinearLayout
-        android:id="@+id/date_time_group"
+        android:id="@+id/date_time_alarm_group"
         android:layout_width="wrap_content"
-        android:layout_height="25dp"
+        android:layout_height="wrap_content"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
-        android:orientation="horizontal">
-
-        <include layout="@layout/split_clock_view"
+        android:layout_marginTop="4dp"
+        android:layout_marginStart="16dp"
+        android:gravity="start"
+        android:orientation="vertical">
+        <LinearLayout
+            android:id="@+id/date_time_group"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="4dp"
-            android:id="@+id/clock" />
+            android:layout_height="19dp"
+            android:orientation="horizontal">
 
-        <com.android.systemui.statusbar.policy.DateView
-            android:id="@+id/date"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginStart="6dp"
-            android:layout_marginTop="4dp"
-            android:drawableStart="@drawable/header_dot"
-            android:drawablePadding="6dp"
-            android:singleLine="true"
-            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
-            android:textSize="@dimen/qs_time_collapsed_size"
-            android:gravity="top"
-            systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+            <include layout="@layout/split_clock_view"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/clock" />
+
+            <com.android.systemui.statusbar.policy.DateView
+                android:id="@+id/date"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="6dp"
+                android:drawableStart="@drawable/header_dot"
+                android:drawablePadding="6dp"
+                android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+                android:textSize="@dimen/qs_time_collapsed_size"
+                android:gravity="top"
+                systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+
+            <com.android.systemui.statusbar.AlphaOptimizedButton
+                android:id="@+id/alarm_status_collapsed"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:drawablePadding="6dp"
+                android:drawableStart="@drawable/ic_access_alarms_small"
+                android:textColor="#64ffffff"
+                android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
+                android:paddingStart="6dp"
+                android:gravity="top"
+                android:background="?android:attr/selectableItemBackground"
+                android:visibility="gone" />
+        </LinearLayout>
 
         <com.android.systemui.statusbar.AlphaOptimizedButton
             android:id="@+id/alarm_status"
             android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginTop="4dp"
-            android:drawablePadding="6dp"
+            android:layout_height="20dp"
+            android:paddingTop="3dp"
+            android:drawablePadding="8dp"
             android:drawableStart="@drawable/ic_access_alarms_small"
             android:textColor="#64ffffff"
             android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
-            android:paddingStart="6dp"
             android:gravity="top"
             android:background="?android:attr/selectableItemBackground"
             android:visibility="gone" />
@@ -152,7 +169,7 @@
         android:background="#0000"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="25dp"
+        android:layout_marginTop="28dp"
         android:layout_marginStart="12dp"
         android:layout_marginEnd="12dp"
         android:layout_alignParentEnd="true"
diff --git a/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
new file mode 100644
index 0000000..efe63d7
--- /dev/null
+++ b/packages/SystemUI/res/layout/tuner_zen_mode_panel.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 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.
+-->
+<!-- extends LinearLayout -->
+<com.android.systemui.tuner.TunerZenModePanel
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tuner_zen_mode_panel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:visibility="gone"
+    android:orientation="vertical" >
+
+    <View
+        android:id="@+id/zen_embedded_divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:layout_marginBottom="12dp"
+        android:layout_marginTop="8dp"
+        android:background="@color/qs_tile_divider" />
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:layout_marginStart="16dp"
+        android:id="@+id/tuner_zen_switch"
+        layout="@layout/qs_detail_header" />
+
+    <include layout="@layout/zen_mode_panel" />
+
+    <include
+        android:id="@+id/tuner_zen_buttons"
+        layout="@layout/qs_detail_buttons" />
+
+</com.android.systemui.tuner.TunerZenModePanel>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 34796cd..e4effd4 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -46,7 +46,7 @@
         <include layout="@layout/volume_zen_footer" />
 
         <!-- Only shown from Tuner setting -->
-        <include layout="@layout/zen_mode_panel" />
+        <include layout="@layout/tuner_zen_mode_panel" />
     </LinearLayout>
 
 </RelativeLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 11c13e1..b8044ba 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -162,7 +162,11 @@
     <dimen name="qs_tile_margin">16dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
     <dimen name="qs_quick_tile_padding">12dp</dimen>
-    <dimen name="qs_date_anim_translation">44.5dp</dimen>
+    <dimen name="qs_date_anim_translation">36dp</dimen>
+    <dimen name="qs_date_alarm_anim_translation">26dp</dimen>
+    <dimen name="qs_date_collapsed_text_size">14sp</dimen>
+    <dimen name="qs_date_text_size">16sp</dimen>
+    <dimen name="qs_header_gear_translation">120dp</dimen>
     <dimen name="qs_page_indicator_size">12dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
@@ -598,9 +602,6 @@
     <dimen name="fab_elevation">12dp</dimen>
     <dimen name="fab_press_translation_z">9dp</dimen>
 
-    <!-- TODO: Remove this -->
-    <dimen name="qs_header_neg_padding">-8dp</dimen>
-
     <!-- How high we lift the divider when touching -->
     <dimen name="docked_stack_divider_lift_elevation">4dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ec04861..6135dc6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -810,12 +810,6 @@
     <!-- Interruption level: Alarms only.  Optimized for narrow two-line display. [CHAR LIMIT=40] -->
     <string name="interruption_level_alarms_twoline">Alarms\nonly</string>
 
-    <!-- Interruption level: All interruptions. [CHAR LIMIT=40] -->
-    <string name="interruption_level_all">All</string>
-
-    <!-- Interruption level: All interruptions.  Optimized for narrow two-line display. [CHAR LIMIT=40] -->
-    <string name="interruption_level_all_twoline">All\n</string>
-
     <!-- Indication on the keyguard that is shown when the device is charging. [CHAR LIMIT=40]-->
     <string name="keyguard_indication_charging_time">Charging (<xliff:g id="charging_time_left" example="4 hours and 2 minutes">%s</xliff:g> until full)</string>
 
@@ -1182,7 +1176,7 @@
     <string name="overview_disable_fast_toggle_via_button_desc">Disable launch timeout while paging</string>
 
     <!-- Toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=60]-->
-    <string name="overview_nav_bar_gesture">Enable split-screen swipe-up accelerator</string>
+    <string name="overview_nav_bar_gesture">Enable split-screen swipe-up gesture</string>
     <!-- Description for the toggle to enable the gesture to enter split-screen by swiping up from the Overview button. [CHAR LIMIT=NONE]-->
     <string name="overview_nav_bar_gesture_desc">Enable gesture to enter split-screen by swiping up from the Overview button</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 90d56f7..6029c23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -758,7 +758,7 @@
                 mPendingLock = false;
             }
         }
-        doKeyguardLaterLockedForChildProfiles();
+        doKeyguardForChildProfilesLocked();
         KeyguardUpdateMonitor.getInstance(mContext).dispatchFinishedGoingToSleep(why);
     }
 
@@ -781,8 +781,7 @@
 
         long timeout;
 
-        if ((mLockPatternUtils.isSeparateProfileChallengeEnabled(userId))
-                || policyTimeout <= 0) {
+        if (policyTimeout <= 0) {
             timeout = lockAfterTimeout;
         } else {
             // From DisplaySettings
@@ -815,23 +814,31 @@
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
         if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
                          + mDelayedShowingSequence);
-        doKeyguardLaterLockedForChildProfiles();
+        doKeyguardLaterForChildProfilesLocked();
     }
 
-    private void doKeyguardLaterLockedForChildProfiles() {
+    private void doKeyguardLaterForChildProfilesLocked() {
         UserManager um = UserManager.get(mContext);
         List<UserInfo> profiles = um.getEnabledProfiles(UserHandle.myUserId());
-        if (profiles.size() > 1) {
-            for (UserInfo info : profiles) {
-                if (mLockPatternUtils.isSeparateProfileChallengeEnabled(info.id)) {
-                    long userTimeout = getLockTimeout(info.id);
-                    long userWhen = SystemClock.elapsedRealtime() + userTimeout;
-                    Intent lockIntent = new Intent(DELAYED_LOCK_PROFILE_ACTION);
-                    lockIntent.putExtra(Intent.EXTRA_USER_ID, info.id);
-                    PendingIntent lockSender = PendingIntent.getBroadcast(
-                            mContext, 0, lockIntent, PendingIntent.FLAG_CANCEL_CURRENT);
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, userWhen, lockSender);
-                }
+        for (UserInfo info : profiles) {
+            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(info.id)) {
+                long userTimeout = getLockTimeout(info.id);
+                long userWhen = SystemClock.elapsedRealtime() + userTimeout;
+                Intent lockIntent = new Intent(DELAYED_LOCK_PROFILE_ACTION);
+                lockIntent.putExtra(Intent.EXTRA_USER_ID, info.id);
+                PendingIntent lockSender = PendingIntent.getBroadcast(
+                        mContext, 0, lockIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, userWhen, lockSender);
+            }
+        }
+    }
+
+    private void doKeyguardForChildProfilesLocked() {
+        UserManager um = UserManager.get(mContext);
+        List<UserInfo> profiles = um.getEnabledProfiles(UserHandle.myUserId());
+        for (UserInfo info : profiles) {
+            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(info.id)) {
+                lockProfile(info.id);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index f208470..d0f7e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -17,12 +17,10 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.Space;
 import com.android.systemui.R;
@@ -103,7 +101,7 @@
 
     private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
 
-        private final ImageView mDownArrow;
+        private final Space mEndSpacer;
 
         public HeaderTileLayout(Context context) {
             super(context);
@@ -112,16 +110,10 @@
             setGravity(Gravity.CENTER_VERTICAL);
             setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
 
-            int padding =
-                    mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_padding);
-            mDownArrow = new ImageView(context);
-            mDownArrow.setImageResource(R.drawable.ic_expand_more);
-            mDownArrow.setImageTintList(ColorStateList.valueOf(context.getResources().getColor(
-                    android.R.color.white, null)));
-            mDownArrow.setLayoutParams(generateLayoutParams());
-            mDownArrow.setPadding(padding, padding, padding, padding);
+            mEndSpacer = new Space(context);
+            mEndSpacer.setLayoutParams(generateLayoutParams());
             updateDownArrowMargin();
-            addView(mDownArrow);
+            addView(mEndSpacer);
             setOrientation(LinearLayout.HORIZONTAL);
         }
 
@@ -132,10 +124,10 @@
         }
 
         private void updateDownArrowMargin() {
-            LayoutParams params = (LayoutParams) mDownArrow.getLayoutParams();
+            LayoutParams params = (LayoutParams) mEndSpacer.getLayoutParams();
             params.setMarginStart(mContext.getResources().getDimensionPixelSize(
                     R.dimen.qs_expand_margin));
-            mDownArrow.setLayoutParams(params);
+            mEndSpacer.setLayoutParams(params);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
new file mode 100644
index 0000000..5e6b52b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2016 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.systemui.qs;
+
+import android.animation.Keyframe;
+import android.util.MathUtils;
+import android.util.Property;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class, that handles similar properties as animators (delay, interpolators)
+ * but can have a float input as to the amount they should be in effect.  This allows
+ * easier animation that tracks input.
+ *
+ * All "delays" and "times" are as fractions from 0-1.
+ */
+public class TouchAnimator {
+
+    private final Object[] mTargets;
+    private final Property[] mProperties;
+    private final KeyframeSet[] mKeyframeSets;
+    private final float mStartDelay;
+    private final float mEndDelay;
+    private final float mSpan;
+    private final Interpolator mInterpolator;
+    private final Listener mListener;
+    private float mLastT;
+
+    private TouchAnimator(Object[] targets, Property[] properties, KeyframeSet[] keyframeSets,
+            float startDelay, float endDelay, Interpolator interpolator, Listener listener) {
+        mTargets = targets;
+        mProperties = properties;
+        mKeyframeSets = keyframeSets;
+        mStartDelay = startDelay;
+        mEndDelay = endDelay;
+        mSpan = (1 - mEndDelay - mStartDelay);
+        mInterpolator = interpolator;
+        mListener = listener;
+    }
+
+    public void setPosition(float fraction) {
+        float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
+        if (mInterpolator != null) {
+            t = mInterpolator.getInterpolation(t);
+        }
+        if (mListener != null) {
+            if (mLastT == 0 || mLastT == 1) {
+                if (t != 0) {
+                    mListener.onAnimationStarted();
+                }
+            } else if (t == 1) {
+                mListener.onAnimationAtEnd();
+            } else if (t == 0) {
+                mListener.onAnimationAtStart();
+            }
+            mLastT = t;
+        }
+        for (int i = 0; i < mTargets.length; i++) {
+            Object value = mKeyframeSets[i].getValue(t);
+            mProperties[i].set(mTargets[i], value);
+        }
+    }
+
+    public static class ListenerAdapter implements Listener {
+        @Override
+        public void onAnimationAtStart() { }
+
+        @Override
+        public void onAnimationAtEnd() { }
+
+        @Override
+        public void onAnimationStarted() { }
+    }
+
+    public interface Listener {
+        /**
+         * Called when the animator moves into a position of "0". Start and end delays are
+         * taken into account, so this position may cover a range of fractional inputs.
+         */
+        void onAnimationAtStart();
+
+        /**
+         * Called when the animator moves into a position of "0". Start and end delays are
+         * taken into account, so this position may cover a range of fractional inputs.
+         */
+        void onAnimationAtEnd();
+
+        /**
+         * Called when the animator moves out of the start or end position and is in a transient
+         * state.
+         */
+        void onAnimationStarted();
+    }
+
+    public static class Builder {
+        private List<Object> mTargets = new ArrayList<>();
+        private List<Property> mProperties = new ArrayList<>();
+        private List<KeyframeSet> mValues = new ArrayList<>();
+
+        private float mStartDelay;
+        private float mEndDelay;
+        private Interpolator mInterpolator;
+        private Listener mListener;
+
+        public Builder addFloat(Object target, String property, float... values) {
+            add(target, property, KeyframeSet.ofFloat(values));
+            return this;
+        }
+
+        public Builder addInt(Object target, String property, int... values) {
+            add(target, property, KeyframeSet.ofInt(values));
+            return this;
+        }
+
+        private void add(Object target, String property, KeyframeSet keyframeSet) {
+            mTargets.add(target);
+            // TODO: Optimize the properties here, to use those in View when possible.
+            mProperties.add(Property.of(target.getClass(), float.class, property));
+            mValues.add(keyframeSet);
+        }
+
+        public Builder setStartDelay(float startDelay) {
+            mStartDelay = startDelay;
+            return this;
+        }
+
+        public Builder setEndDelay(float endDelay) {
+            mEndDelay = endDelay;
+            return this;
+        }
+
+        public Builder setInterpolator(Interpolator intepolator) {
+            mInterpolator = intepolator;
+            return this;
+        }
+
+        public Builder setListener(Listener listener) {
+            mListener = listener;
+            return this;
+        }
+
+        public TouchAnimator build() {
+            return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
+                    mProperties.toArray(new Property[mProperties.size()]),
+                    mValues.toArray(new KeyframeSet[mValues.size()]),
+                    mStartDelay, mEndDelay, mInterpolator, mListener);
+        }
+    }
+
+    private static abstract class KeyframeSet {
+
+        private final Keyframe[] mKeyframes;
+
+        public KeyframeSet(Keyframe[] keyframes) {
+            mKeyframes = keyframes;
+        }
+
+        Object getValue(float fraction) {
+            int i;
+            for (i = 1; i < mKeyframes.length && fraction > mKeyframes[i].getFraction(); i++) ;
+            Keyframe first = mKeyframes[i - 1];
+            Keyframe second = mKeyframes[i];
+            float amount = (fraction - first.getFraction())
+                    / (second.getFraction() - first.getFraction());
+            return interpolate(first, second, amount);
+        }
+
+        protected abstract Object interpolate(Keyframe first, Keyframe second, float amount);
+
+        public static KeyframeSet ofInt(int... values) {
+            int numKeyframes = values.length;
+            Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
+            if (numKeyframes == 1) {
+                keyframes[0] = Keyframe.ofInt(0f);
+                keyframes[1] = Keyframe.ofInt(1f, values[0]);
+            } else {
+                keyframes[0] = Keyframe.ofInt(0f, values[0]);
+                for (int i = 1; i < numKeyframes; ++i) {
+                    keyframes[i] = Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+                }
+            }
+            return new IntKeyframeSet(keyframes);
+        }
+
+        public static KeyframeSet ofFloat(float... values) {
+            int numKeyframes = values.length;
+            Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes, 2)];
+            if (numKeyframes == 1) {
+                keyframes[0] = Keyframe.ofFloat(0f);
+                keyframes[1] = Keyframe.ofFloat(1f, values[0]);
+            } else {
+                keyframes[0] = Keyframe.ofFloat(0f, values[0]);
+                for (int i = 1; i < numKeyframes; ++i) {
+                    keyframes[i] = Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+                }
+            }
+            return new FloatKeyframeSet(keyframes);
+        }
+    }
+
+    public static class FloatKeyframeSet extends KeyframeSet {
+        public FloatKeyframeSet(Keyframe[] keyframes) {
+            super(keyframes);
+        }
+
+        @Override
+        protected Object interpolate(Keyframe first, Keyframe second, float amount) {
+            float firstFloat = (float) first.getValue();
+            float secondFloat = (float) second.getValue();
+            return firstFloat + (secondFloat - firstFloat) * amount;
+        }
+    }
+
+    public static class IntKeyframeSet extends KeyframeSet {
+        public IntKeyframeSet(Keyframe[] keyframes) {
+            super(keyframes);
+        }
+
+        @Override
+        protected Object interpolate(Keyframe first, Keyframe second, float amount) {
+            int firstFloat = (int) first.getValue();
+            int secondFloat = (int) second.getValue();
+            return (int) (firstFloat + (secondFloat - firstFloat) * amount);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index a58fa86..8a93c5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -23,6 +23,7 @@
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.support.v4.util.SimpleArrayMap;
+import android.util.SparseBooleanArray;
 import android.view.View;
 import android.widget.LinearLayout;
 
@@ -71,6 +72,7 @@
 
     private int mCurrentFacetIndex;
     private String mCurrentPackageName;
+    private SparseBooleanArray mFacetHasMultipleAppsCache = new SparseBooleanArray();
 
     public CarNavigationBarController(Context context,
                                       CarNavigationBarView navBar,
@@ -96,6 +98,21 @@
         }
     }
 
+    public void onPackageChange(String packageName) {
+        if (mFacetPackageMap.containsKey(packageName)) {
+            int index = mFacetPackageMap.get(packageName);
+            mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
+            // No need to check categories because we've already refreshed the cache.
+            return;
+        }
+
+        String category = getPackageCategory(packageName);
+        if (mFacetCategoryMap.containsKey(category)) {
+            int index = mFacetCategoryMap.get(packageName);
+            mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
+        }
+    }
+
     private void bind() {
         // Read up arrays_car.xml and populate the navigation bar here.
         Resources r = mContext.getResources();
@@ -138,6 +155,7 @@
                 initFacetFilterMaps(i,
                         facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER),
                         facetCategories.getString(i).split(FACET_FILTER_DEMILITER));
+                        mFacetHasMultipleAppsCache.put(i, facetHasMultiplePackages(i));
             } catch (URISyntaxException e) {
                 throw new RuntimeException("Malformed intent uri", e);
             }
@@ -229,7 +247,7 @@
 
         if (mNavButtons.get(index) != null) {
             mNavButtons.get(index).setSelected(true /* selected */,
-                    facetHasMultiplePackages(index)  /* showMoreIcon */);
+                    mFacetHasMultipleAppsCache.get(index)  /* showMoreIcon */);
         }
         mCurrentFacetIndex = index;
     }
@@ -268,7 +286,7 @@
 
     private void startActivity(Intent intent) {
         if (mActivityStarter != null && intent != null) {
-            mActivityStarter.startActivity(intent, true);
+            mActivityStarter.startActivity(intent, false);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 08cd053..c32ef0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -18,7 +18,10 @@
 
 import android.app.ActivityManager;
 import android.app.ITaskStackListener;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.PixelFormat;
 import android.os.Handler;
 import android.os.Looper;
@@ -52,6 +55,7 @@
         mTaskStackListener = new TaskStackListenerImpl(mHandler);
         mSystemServicesProxy = new SystemServicesProxy(mContext);
         mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+        registerPackageChangeReceivers();
     }
 
     @Override
@@ -81,6 +85,26 @@
         mController = new CarNavigationBarController(context, mCarNavigationBar,
                 this /* ActivityStarter*/);
         mNavigationBarView = mCarNavigationBar;
+
+    }
+
+    private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getData() == null || mController == null) {
+                return;
+            }
+            String packageName = intent.getData().getSchemeSpecificPart();
+            mController.onPackageChange(packageName);
+        }
+    };
+
+    private void registerPackageChangeReceivers() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(mPackageChangeReceiver, filter);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
new file mode 100644
index 0000000..8c7c71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ExpandableIndicator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import com.android.systemui.R;
+
+public class ExpandableIndicator extends ImageView {
+
+    private boolean mExpanded;
+
+    public ExpandableIndicator(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
+                : R.drawable.ic_volume_expand_animation;
+        setImageResource(res);
+    }
+
+    public void setExpanded(boolean expanded) {
+        if (expanded == mExpanded) return;
+        mExpanded = expanded;
+        final int res = mExpanded ? R.drawable.ic_volume_expand_animation
+                : R.drawable.ic_volume_collapse_animation;
+        // workaround to reset drawable
+        final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext()
+                .getDrawable(res).getConstantState().newDrawable();
+        setImageDrawable(avd);
+        avd.start();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index d625fc2..260c969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -163,68 +163,29 @@
         String[] start = sets[0].split(BUTTON_SEPARATOR);
         String[] center = sets[1].split(BUTTON_SEPARATOR);
         String[] end = sets[2].split(BUTTON_SEPARATOR);
-        inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group),
-                (ViewGroup) mRot0.findViewById(R.id.ends_group_lightsout), false);
-        inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group),
-                (ViewGroup) mRot90.findViewById(R.id.ends_group_lightsout), true);
+        inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
+        inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
 
-        inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group),
-                (ViewGroup) mRot0.findViewById(R.id.center_group_lightsout), false);
-        inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group),
-                (ViewGroup) mRot90.findViewById(R.id.center_group_lightsout), true);
+        inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group), false);
+        inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group), true);
 
         addGravitySpacer((LinearLayout) mRot0.findViewById(R.id.ends_group));
         addGravitySpacer((LinearLayout) mRot90.findViewById(R.id.ends_group));
 
-        inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group),
-                (ViewGroup) mRot0.findViewById(R.id.ends_group_lightsout), false);
-        inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group),
-                (ViewGroup) mRot90.findViewById(R.id.ends_group_lightsout), true);
+        inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
+        inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
     }
 
     private void addGravitySpacer(LinearLayout layout) {
         layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1));
     }
 
-    private void inflateButtons(String[] buttons, ViewGroup parent, ViewGroup lightsOutParent,
-            boolean landscape) {
+    private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape) {
         for (int i = 0; i < buttons.length; i++) {
-            copyToLightsout(inflateButton(buttons[i], parent, landscape), lightsOutParent);
+            inflateButton(buttons[i], parent, landscape);
         }
     }
 
-    private void copyToLightsout(View view, ViewGroup lightsOutParent) {
-        if (view == null) return;
-
-        if (view instanceof FrameLayout) {
-            // The only ViewGroup we support in here is a FrameLayout, so copy those manually.
-            FrameLayout original = (FrameLayout) view;
-            FrameLayout layout = new FrameLayout(view.getContext());
-            for (int i = 0; i < original.getChildCount(); i++) {
-                copyToLightsout(original.getChildAt(i), layout);
-            }
-            lightsOutParent.addView(layout, copy(view.getLayoutParams()));
-        } else if (view instanceof Space) {
-            lightsOutParent.addView(new Space(view.getContext()), copy(view.getLayoutParams()));
-        } else {
-            lightsOutParent.addView(generateLightsOutView(view), copy(view.getLayoutParams()));
-        }
-    }
-
-    private View generateLightsOutView(View view) {
-        ImageView imageView = new ImageView(view.getContext());
-        // Copy everything we can about the original view.
-        imageView.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
-                view.getPaddingBottom());
-        imageView.setContentDescription(view.getContentDescription());
-        imageView.setId(view.getId());
-        // Only home gets a big dot, everything else will be little.
-        imageView.setImageResource(view.getId() == R.id.home
-                ? R.drawable.ic_sysbar_lights_out_dot_large
-                : R.drawable.ic_sysbar_lights_out_dot_small);
-        return imageView;
-    }
-
     private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) {
         if (layoutParams instanceof LinearLayout.LayoutParams) {
             return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height,
@@ -348,9 +309,7 @@
             }
         }
         clearAllChildren((ViewGroup) mRot0.findViewById(R.id.nav_buttons));
-        clearAllChildren((ViewGroup) mRot0.findViewById(R.id.lights_out));
         clearAllChildren((ViewGroup) mRot90.findViewById(R.id.nav_buttons));
-        clearAllChildren((ViewGroup) mRot90.findViewById(R.id.lights_out));
     }
 
     private void clearAllChildren(ViewGroup group) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 134c579..1fe0115 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -64,42 +64,20 @@
         mLightsOut = lightsOut;
 
         final View navButtons = mView.getCurrentView().findViewById(R.id.nav_buttons);
-        final View lowLights = mView.getCurrentView().findViewById(R.id.lights_out);
 
         // ok, everyone, stop it right there
         navButtons.animate().cancel();
-        lowLights.animate().cancel();
 
-        final float navButtonsAlpha = lightsOut ? 0f : 1f;
-        final float lowLightsAlpha = lightsOut ? 1f : 0f;
+        final float navButtonsAlpha = lightsOut ? 0.5f : 1f;
 
         if (!animate) {
             navButtons.setAlpha(navButtonsAlpha);
-            lowLights.setAlpha(lowLightsAlpha);
-            lowLights.setVisibility(lightsOut ? View.VISIBLE : View.GONE);
         } else {
             final int duration = lightsOut ? LIGHTS_OUT_DURATION : LIGHTS_IN_DURATION;
             navButtons.animate()
                 .alpha(navButtonsAlpha)
                 .setDuration(duration)
                 .start();
-
-            lowLights.setOnTouchListener(mLightsOutListener);
-            if (lowLights.getVisibility() == View.GONE) {
-                lowLights.setAlpha(0f);
-                lowLights.setVisibility(View.VISIBLE);
-            }
-            lowLights.animate()
-                .alpha(lowLightsAlpha)
-                .setDuration(duration)
-                .setInterpolator(new AccelerateInterpolator(2.0f))
-                .setListener(lightsOut ? null : new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator _a) {
-                        lowLights.setVisibility(View.GONE);
-                    }
-                })
-                .start();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 97d7dd5..6698076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -79,7 +79,7 @@
     private Drawable mBackAltCarModeIcon, mBackAltLandCarModeIcon;
     private Drawable mHomeDefaultIcon, mHomeCarModeIcon;
     private Drawable mRecentIcon;
-    private Drawable mRecentLandIcon;
+    private Drawable mDockedIcon;
 
     private NavigationBarGestureHelper mGestureHelper;
     private DeadZone mDeadZone;
@@ -97,6 +97,7 @@
     private boolean mLayoutTransitionsEnabled = true;
     private boolean mWakeAndUnlocking;
     private boolean mCarMode = false;
+    private boolean mDockedStackExists;
 
     private final SparseArray<ButtonDispatcher> mButtonDisatchers = new SparseArray<>();
 
@@ -280,7 +281,7 @@
         mHomeDefaultIcon = ctx.getDrawable(R.drawable.ic_sysbar_home);
 
         mRecentIcon = ctx.getDrawable(R.drawable.ic_sysbar_recent);
-        mRecentLandIcon = mRecentIcon;
+        mDockedIcon = ctx.getDrawable(R.drawable.ic_sysbar_docked);
         getCarModeIcons(ctx);
     }
 
@@ -335,8 +336,7 @@
 
         getBackButton().setImageDrawable(backIcon);
 
-        getRecentsButton().setImageDrawable(
-                mVertical ? mRecentLandIcon : mRecentIcon);
+        updateRecentsIcon();
 
         if (mCarMode) {
             getHomeButton().setImageDrawable(mHomeCarModeIcon);
@@ -507,7 +507,8 @@
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            updateRecentsIcon(exists);
+                            mDockedStackExists = exists;
+                            updateRecentsIcon();
                         }
                     });
                 }
@@ -517,10 +518,8 @@
         }
     }
 
-    private void updateRecentsIcon(boolean dockedStackExists) {
-        getRecentsButton().setImageResource(dockedStackExists
-                ? R.drawable.ic_sysbar_docked
-                : R.drawable.ic_sysbar_recent);
+    private void updateRecentsIcon() {
+        getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
     }
 
     public boolean isVertical() {
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 3aa576f..401d405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -318,7 +318,7 @@
     StatusBarIconController mIconController;
 
     // expanded notifications
-    NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
+    protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
     View mExpandedContents;
     TextView mNotificationPanelDebugText;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 11d99ff..bd5bac2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -20,55 +20,76 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QuickQSPanel;
+import com.android.systemui.qs.TouchAnimator;
+import com.android.systemui.qs.TouchAnimator.Listener;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.tuner.TunerService;
 
 public class QuickStatusBarHeader extends BaseStatusBarHeader implements
-        NextAlarmController.NextAlarmChangeCallback, View.OnClickListener {
+        NextAlarmChangeCallback, OnClickListener, Listener {
 
     private static final String TAG = "QuickStatusBarHeader";
+
+    private static final float EXPAND_INDICATOR_THRESHOLD = .8f;
+
     private ActivityStarter mActivityStarter;
     private NextAlarmController mNextAlarmController;
     private SettingsButton mSettingsButton;
     private View mSettingsContainer;
+
     private TextView mAlarmStatus;
+    private TextView mAlarmStatusCollapsed;
 
     private QSPanel mQsPanel;
 
     private boolean mExpanded;
     private boolean mAlarmShowing;
 
-    private ViewGroup mExpandedGroup;
     private ViewGroup mDateTimeGroup;
-    private View mEmergencyOnly;
-    private TextView mQsDetailHeaderTitle;
+    private ViewGroup mDateTimeAlarmGroup;
+    private TextView mEmergencyOnly;
+
+    private ExpandableIndicator mExpandIndicator;
+
     private boolean mListening;
     private AlarmManager.AlarmClockInfo mNextAlarm;
 
     private QuickQSPanel mHeaderQsPanel;
     private boolean mShowEmergencyCallsOnly;
-    private float mDateTimeTranslation;
     private MultiUserSwitch mMultiUserSwitch;
     private ImageView mMultiUserAvatar;
-    private View mQsDetailHeaderBack;
 
-    private final int[] mTmpInt2 = new int[2];
+    private float mDateTimeTranslation;
+    private float mDateTimeAlarmTranslation;
+    private float mDateScaleFactor;
+    private float mGearTranslation;
+
+    private TouchAnimator mAnimator;
+    private TouchAnimator mSecondHalfAnimator;
+    private TouchAnimator mFirstHalfAnimator;
+    private TouchAnimator mDateSizeAnimator;
+    private TouchAnimator mAlarmTranslation;
+    private float mExpansionAmount;
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -78,12 +99,15 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mEmergencyOnly = findViewById(R.id.header_emergency_calls_only);
-        mDateTimeTranslation = mContext.getResources().getDimension(
-                R.dimen.qs_date_anim_translation);
+        mEmergencyOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
+
+        mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
+        mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
         mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
-        mDateTimeGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
-        mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
+        mDateTimeGroup.setPivotX(0);
+        mDateTimeGroup.setPivotY(0);
+
+        mExpandIndicator = (ExpandableIndicator) findViewById(R.id.expand_indicator);
 
         mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
 
@@ -91,6 +115,7 @@
         mSettingsContainer = findViewById(R.id.settings_button_container);
         mSettingsButton.setOnClickListener(this);
 
+        mAlarmStatusCollapsed = (TextView) findViewById(R.id.alarm_status_collapsed);
         mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
         mAlarmStatus.setOnClickListener(this);
 
@@ -110,6 +135,54 @@
                         getHeight()));
             }
         });
+        updateResources();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateResources();
+    }
+
+    private void updateResources() {
+        FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
+        FontSizeUtils.updateFontSize(mEmergencyOnly, R.dimen.qs_emergency_calls_only_text_size);
+
+        mGearTranslation = mContext.getResources().getDimension(R.dimen.qs_header_gear_translation);
+
+        mDateTimeTranslation = mContext.getResources().getDimension(
+                R.dimen.qs_date_anim_translation);
+        mDateTimeAlarmTranslation = mContext.getResources().getDimension(
+                R.dimen.qs_date_alarm_anim_translation);
+        float dateCollapsedSize = mContext.getResources().getDimension(
+                R.dimen.qs_date_collapsed_text_size);
+        float dateExpandedSize = mContext.getResources().getDimension(
+                R.dimen.qs_date_text_size);
+        mDateScaleFactor = dateExpandedSize / dateCollapsedSize;
+        updateDateTimePosition();
+
+        mAnimator = new TouchAnimator.Builder()
+                .addFloat(mSettingsContainer, "translationY", -mGearTranslation, 0)
+                .addFloat(mMultiUserSwitch, "translationY", -mGearTranslation, 0)
+                .addFloat(mSettingsButton, "rotation", -90, 0)
+                .setListener(this)
+                .build();
+        mSecondHalfAnimator = new TouchAnimator.Builder()
+                .addFloat(mSettingsButton, "rotation", -90, 0)
+                .addFloat(mAlarmStatus, "alpha", 0, 1)
+                .addFloat(mEmergencyOnly, "alpha", 0, 1)
+                .setStartDelay(.5f)
+                .build();
+        mFirstHalfAnimator = new TouchAnimator.Builder()
+                .addFloat(mAlarmStatusCollapsed, "alpha", 1, 0)
+                .addFloat(mHeaderQsPanel, "alpha", 1, 0)
+                .setEndDelay(.5f)
+                .build();
+        mDateSizeAnimator = new TouchAnimator.Builder()
+                .addFloat(mDateTimeGroup, "scaleX", 1, mDateScaleFactor)
+                .addFloat(mDateTimeGroup, "scaleY", 1, mDateScaleFactor)
+                .setStartDelay(.36f)
+                .build();
     }
 
     @Override
@@ -134,19 +207,52 @@
         if (nextAlarm != null) {
             mAlarmStatus.setText(KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm));
         }
-        mAlarmShowing = nextAlarm != null;
-        updateEverything();
+        if (mAlarmShowing != (nextAlarm != null)) {
+            mAlarmShowing = nextAlarm != null;
+            updateEverything();
+        }
     }
 
     @Override
     public void setExpansion(float headerExpansionFraction) {
-        mExpandedGroup.setAlpha(headerExpansionFraction);
-        mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
-        mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
-        mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
+        mExpansionAmount = headerExpansionFraction;
+        mAnimator.setPosition(headerExpansionFraction);
+        mSecondHalfAnimator.setPosition(headerExpansionFraction);
+        mFirstHalfAnimator.setPosition(headerExpansionFraction);
+        mDateSizeAnimator.setPosition(headerExpansionFraction);
+        mAlarmTranslation.setPosition(headerExpansionFraction);
 
-        mDateTimeGroup.setTranslationY(headerExpansionFraction * mDateTimeTranslation);
-        mEmergencyOnly.setAlpha(headerExpansionFraction);
+        updateAlarmVisibilities();
+
+        mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
+    }
+
+    @Override
+    public void onAnimationAtStart() {
+    }
+
+    @Override
+    public void onAnimationAtEnd() {
+        mHeaderQsPanel.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void onAnimationStarted() {
+        mHeaderQsPanel.setVisibility(View.VISIBLE);
+    }
+
+    private void updateAlarmVisibilities() {
+        mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
+        mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    private void updateDateTimePosition() {
+        // This one has its own because we have to rebuild it every time the alarm state changes.
+        mAlarmTranslation = new TouchAnimator.Builder()
+                .addFloat(mDateTimeAlarmGroup, "translationY", 0, mAlarmShowing
+                        ? mDateTimeAlarmTranslation : mDateTimeTranslation)
+                .build();
+        mAlarmTranslation.setPosition(mExpansionAmount);
     }
 
     public void setListening(boolean listening) {
@@ -160,11 +266,12 @@
 
     @Override
     public void updateEverything() {
+        updateDateTimePosition();
         updateVisibilities();
     }
 
     private void updateVisibilities() {
-        mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
+        updateAlarmVisibilities();
         mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly
                 ? View.VISIBLE : View.INVISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index 5ded885..ad42459 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -117,13 +117,9 @@
         if (isRotated) {
             mPreview.findViewById(R.id.rot0).setVisibility(View.GONE);
             final View rot90 = mPreview.findViewById(R.id.rot90);
-            rot90.findViewById(R.id.ends_group_lightsout).setVisibility(View.GONE);
-            rot90.findViewById(R.id.center_group_lightsout).setVisibility(View.GONE);
         } else {
             mPreview.findViewById(R.id.rot90).setVisibility(View.GONE);
             final View rot0 = mPreview.findViewById(R.id.rot0);
-            rot0.findViewById(R.id.ends_group_lightsout).setVisibility(View.GONE);
-            rot0.findViewById(R.id.center_group_lightsout).setVisibility(View.GONE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
new file mode 100644
index 0000000..cc0ffb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerZenModePanel.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 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.systemui.tuner;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.volume.ZenModePanel;
+import com.android.systemui.volume.ZenModePanel.Callback;
+
+public class TunerZenModePanel extends LinearLayout implements OnClickListener {
+    private static final String TAG = "TunerZenModePanel";
+
+    private Callback mCallback;
+    private ZenModePanel mZenModePanel;
+    private View mHeaderSwitch;
+    private int mZenMode;
+    private ZenModeController mController;
+    private View mButtons;
+    private View mMoreSettings;
+    private View mDone;
+    private OnClickListener mDoneListener;
+    private boolean mEditing;
+
+    public TunerZenModePanel(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void init(ZenModeController zenModeController) {
+        mController = zenModeController;
+        mHeaderSwitch = findViewById(R.id.tuner_zen_switch);
+        mHeaderSwitch.setVisibility(View.VISIBLE);
+        mHeaderSwitch.setOnClickListener(this);
+        mHeaderSwitch.findViewById(com.android.internal.R.id.up).setVisibility(View.GONE);
+        ((TextView) mHeaderSwitch.findViewById(android.R.id.title)).setText(
+                R.string.quick_settings_dnd_label);
+        mZenModePanel = (ZenModePanel) findViewById(R.id.zen_mode_panel);
+        mZenModePanel.init(zenModeController);
+        mButtons = findViewById(R.id.tuner_zen_buttons);
+        mMoreSettings = mButtons.findViewById(android.R.id.button2);
+        mMoreSettings.setOnClickListener(this);
+        ((TextView) mMoreSettings).setText(R.string.quick_settings_more_settings);
+        mDone = mButtons.findViewById(android.R.id.button1);
+        mDone.setOnClickListener(this);
+        ((TextView) mDone).setText(R.string.quick_settings_done);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mEditing = false;
+    }
+
+    public void setCallback(Callback zenPanelCallback) {
+        mCallback = zenPanelCallback;
+        mZenModePanel.setCallback(zenPanelCallback);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mHeaderSwitch) {
+            mEditing = true;
+            if (mZenMode == Global.ZEN_MODE_OFF) {
+                mZenMode = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
+                        Global.ZEN_MODE_ALARMS);
+                mController.setZen(mZenMode, null, TAG);
+                postUpdatePanel();
+            } else {
+                mZenMode = Global.ZEN_MODE_OFF;
+                mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
+                postUpdatePanel();
+            }
+        } else if (v == mMoreSettings) {
+            Intent intent = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            getContext().startActivity(intent);
+        } else if (v == mDone) {
+            mEditing = false;
+            setVisibility(View.GONE);
+            mDoneListener.onClick(v);
+        }
+    }
+
+    public boolean isEditing() {
+        return mEditing;
+    }
+
+    public void setZenState(int zenMode) {
+        mZenMode = zenMode;
+        postUpdatePanel();
+    }
+
+    private void postUpdatePanel() {
+        // The complicated structure from reusing the same ZenPanel has resulted in some
+        // unstableness/flickering from callbacks coming in quickly. To solve this just
+        // post the UI updates a little bit.
+        removeCallbacks(mUpdate);
+        postDelayed(mUpdate, 40);
+    }
+
+    public void setDoneListener(OnClickListener onClickListener) {
+        mDoneListener = onClickListener;
+    }
+
+    private void updatePanel() {
+        boolean zenOn = mZenMode != Global.ZEN_MODE_OFF;
+        ((Checkable) mHeaderSwitch.findViewById(android.R.id.toggle)).setChecked(zenOn);
+        mZenModePanel.setVisibility(zenOn ? View.VISIBLE : View.GONE);
+        mButtons.setVisibility(zenOn ? View.VISIBLE : View.GONE);
+    }
+
+    private final Runnable mUpdate = new Runnable() {
+        @Override
+        public void run() {
+            updatePanel();
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index b407935..56a604d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -37,7 +37,8 @@
     private View mGuideOverlayView;
     private final Runnable mHideGuideOverlayRunnable = new Runnable() {
         public void run() {
-            mGuideOverlayView.setVisibility(View.INVISIBLE);
+            // TODO: Uncomment this after the b/27224884 is fixed.
+            //mGuideOverlayView.setVisibility(View.INVISIBLE);
         }
     };
 
@@ -50,8 +51,9 @@
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
+    protected void onResume() {
+        super.onResume();
+        mGuideOverlayView.setVisibility(View.VISIBLE);
         mHandler.removeCallbacks(mHideGuideOverlayRunnable);
         mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 6aae9bd..1810c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -67,6 +67,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerZenModePanel;
 import com.android.systemui.volume.VolumeDialogController.State;
 import com.android.systemui.volume.VolumeDialogController.StreamState;
 
@@ -133,7 +134,7 @@
     private int mLastActiveStream;
 
     private boolean mShowFullZen;
-    private final ZenModePanel mZenPanel;
+    private final TunerZenModePanel mZenPanel;
 
     public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
             ZenModeController zenModeController, Callback callback) {
@@ -225,8 +226,7 @@
         mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
         mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
         mZenFooter.init(zenModeController);
-        mZenPanel = (ZenModePanel) mDialog.findViewById(R.id.zen_mode_panel);
-        mZenPanel.addNoneButton();
+        mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
         mZenPanel.init(zenModeController);
         mZenPanel.setCallback(mZenPanelCallback);
 
@@ -671,7 +671,7 @@
         final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
         final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
                 && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
-                && !mShowFullZen;
+                && !mZenPanel.isEditing();
         if (wasVisible != visible && !visible) {
             prepareForCollapse();
         }
@@ -679,12 +679,21 @@
         mZenFooter.update();
 
         final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
-        final boolean fullVisible = mShowFullZen && (mState.zenMode != Global.ZEN_MODE_OFF
-                || mExpanded);
+        final boolean fullVisible = mShowFullZen && !visible;
         if (fullWasVisible != fullVisible && !fullVisible) {
             prepareForCollapse();
         }
         Util.setVisOrGone(mZenPanel, fullVisible);
+        if (fullVisible) {
+            mZenPanel.setZenState(mState.zenMode);
+            mZenPanel.setDoneListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    prepareForCollapse();
+                    mHandler.sendEmptyMessage(H.UPDATE_FOOTER);
+                }
+            });
+        }
     }
 
     private void updateVolumeRowH(VolumeRow row) {
@@ -978,6 +987,7 @@
         private static final int RESCHEDULE_TIMEOUT = 6;
         private static final int STATE_CHANGED = 7;
         private static final int UPDATE_BOTTOM_MARGIN = 8;
+        private static final int UPDATE_FOOTER = 9;
 
         public H() {
             super(Looper.getMainLooper());
@@ -994,6 +1004,7 @@
                 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
                 case STATE_CHANGED: onStateChangedH(mState); break;
                 case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
+                case UPDATE_FOOTER: updateFooterH(); break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 5ca24f7..6976c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -190,12 +190,6 @@
         mZenAlarmWarning = (TextView) findViewById(R.id.zen_alarm_warning);
     }
 
-    public void addNoneButton() {
-        mZenButtons.addButton(R.string.interruption_level_all_twoline,
-                R.string.interruption_level_all,
-                Global.ZEN_MODE_OFF);
-    }
-
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 3335315..3e7466f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -275,6 +275,9 @@
 
     private void processBatchedEvents(long frameNanos) {
         MotionEventHolder current = mEventQueue;
+        if (current == null) {
+            return;
+        }
         while (current.next != null) {
             current = current.next;
         }
@@ -403,6 +406,9 @@
     }
 
     private void disableFeatures() {
+        // Give the features a chance to process any batched events so we'll keep a consistent
+        // event stream
+        processBatchedEvents(Long.MAX_VALUE);
         if (mMotionEventInjector != null) {
             mAms.setMotionEventInjector(null);
             mMotionEventInjector.onDestroy();
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 9e6cd00..3ecff40 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -467,17 +467,18 @@
       if (mCurrentState == STATE_GESTURE_DETECTING) {
           endGestureDetection();
       } else if (mCurrentState == STATE_TOUCH_EXPLORING) {
-          final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
-          final int pointerIdBits = (1 << pointerId);
+          // If the finger is still moving, pass the event on.
+          if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
+              final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+              final int pointerIdBits = (1 << pointerId);
 
-          // Cache the event until we discern exploration from gesturing.
-          mSendHoverEnterAndMoveDelayed.addEvent(event);
-
-          // We have just decided that the user is touch,
-          // exploring so start sending events.
-          mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
-          mSendHoverExitDelayed.cancel();
-          sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+              // We have just decided that the user is touch,
+              // exploring so start sending events.
+              mSendHoverEnterAndMoveDelayed.addEvent(event);
+              mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
+              mSendHoverExitDelayed.cancel();
+              sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+          }
       }
     }
 
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index eaee1d3..a4fc2ec 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -191,7 +191,8 @@
     // 1 : initial release
     // 2 : no format change per se; version bump to facilitate PBKDF2 version skew detection
     // 3 : introduced "_meta" metadata file; no other format change per se
-    static final int BACKUP_FILE_VERSION = 3;
+    // 4 : added support for new device-encrypted storage locations
+    static final int BACKUP_FILE_VERSION = 4;
     static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
     static final int BACKUP_PW_FILE_VERSION = 2;
     static final String BACKUP_METADATA_FILENAME = "_meta";
@@ -347,13 +348,13 @@
         }
 
         @Override
-        public void onBootPhase(int phase) {
-            if (phase == PHASE_SYSTEM_SERVICES_READY) {
-                sInstance.initialize(UserHandle.USER_SYSTEM);
-            } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+        public void onUnlockUser(int userId) {
+            if (userId == UserHandle.USER_SYSTEM) {
+                sInstance.initialize(userId);
+
                 ContentResolver r = sInstance.mContext.getContentResolver();
-                boolean areEnabled = Settings.Secure.getInt(r,
-                        Settings.Secure.BACKUP_ENABLED, 0) != 0;
+                boolean areEnabled = Settings.Secure.getIntForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, 0, userId) != 0;
                 try {
                     sInstance.setBackupEnabled(areEnabled);
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 499b706..6d0d9e9 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1672,6 +1672,7 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String args[]) {
+      mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
       if (mBluetoothBinder == null) {
         writer.println("Bluetooth Service not connected");
       } else {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index f522288..507ac22 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -940,35 +940,18 @@
 
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
-        if (mCurMethodId != null
-                && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
+        if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
             return;
         }
-
-        InputMethodInfo defIm = null;
-        for (InputMethodInfo imi : mMethodList) {
-            if (defIm == null && mSystemReady) {
-                final Locale systemLocale = context.getResources().getConfiguration().locale;
-                if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context,
-                        true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */,
-                        InputMethodUtils.SUBTYPE_MODE_ANY)) {
-                    defIm = imi;
-                    Slog.i(TAG, "Selected default: " + imi.getId());
-                }
-            }
+        final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
+                context, mSystemReady, mSettings.getEnabledInputMethodListLocked());
+        if (suitableImes.isEmpty()) {
+            Slog.i(TAG, "No default found");
+            return;
         }
-        if (defIm == null && mMethodList.size() > 0) {
-            defIm = InputMethodUtils.getMostApplicableDefaultIME(
-                    mSettings.getEnabledInputMethodListLocked());
-            if (defIm != null) {
-                Slog.i(TAG, "Default found, using " + defIm.getId());
-            } else {
-                Slog.i(TAG, "No default found");
-            }
-        }
-        if (defIm != null) {
-            setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
-        }
+        final InputMethodInfo defIm = suitableImes.get(0);
+        Slog.i(TAG, "Default found, using " + defIm.getId());
+        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
     }
 
     private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged,
@@ -3004,6 +2987,25 @@
             }
         }
 
+        // TODO: The following code should find better place to live.
+        if (!resetDefaultEnabledIme) {
+            boolean enabledImeFound = false;
+            final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
+            final int N = enabledImes.size();
+            for (int i = 0; i < N; ++i) {
+                final InputMethodInfo imi = enabledImes.get(i);
+                if (mMethodList.contains(imi)) {
+                    enabledImeFound = true;
+                    break;
+                }
+            }
+            if (!enabledImeFound) {
+                Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
+                resetDefaultEnabledIme = true;
+                resetSelectedInputMethodAndSubtypeLocked("");
+            }
+        }
+
         if (resetDefaultEnabledIme) {
             final ArrayList<InputMethodInfo> defaultEnabledIme =
                     InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, mMethodList);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index ca1e371..163b9be 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -660,7 +660,12 @@
         long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
-            return readUserDataInternal(accounts, account, key);
+            synchronized (accounts.cacheLock) {
+                if (!accountExistsCacheLocked(accounts, account)) {
+                    return null;
+                }
+                return readUserDataInternalLocked(accounts, account, key);
+            }
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -1716,44 +1721,58 @@
         long identityToken = clearCallingIdentity();
         try {
             UserAccounts accounts = getUserAccounts(userId);
-            setUserdataInternal(accounts, account, key, value);
+            synchronized (accounts.cacheLock) {
+                if (!accountExistsCacheLocked(accounts, account)) {
+                    return;
+                }
+                setUserdataInternalLocked(accounts, account, key, value);
+            }
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    private void setUserdataInternal(UserAccounts accounts, Account account, String key,
+    private boolean accountExistsCacheLocked(UserAccounts accounts, Account account) {
+        if (accounts.accountCache.containsKey(account.type)) {
+            for (Account acc : getUserAccountsForCaller().accountCache.get(account.type)) {
+                if (acc.name.equals(account.name)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void setUserdataInternalLocked(UserAccounts accounts, Account account, String key,
             String value) {
         if (account == null || key == null) {
             return;
         }
-        synchronized (accounts.cacheLock) {
-            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
-            db.beginTransaction();
-            try {
-                long accountId = getAccountIdLocked(db, account);
-                if (accountId < 0) {
+        final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            long accountId = getAccountIdLocked(db, account);
+            if (accountId < 0) {
+                return;
+            }
+            long extrasId = getExtrasIdLocked(db, accountId, key);
+            if (extrasId < 0) {
+                extrasId = insertExtraLocked(db, accountId, key, value);
+                if (extrasId < 0) {
                     return;
                 }
-                long extrasId = getExtrasIdLocked(db, accountId, key);
-                if (extrasId < 0 ) {
-                    extrasId = insertExtraLocked(db, accountId, key, value);
-                    if (extrasId < 0) {
-                        return;
-                    }
-                } else {
-                    ContentValues values = new ContentValues();
-                    values.put(EXTRAS_VALUE, value);
-                    if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
-                        return;
-                    }
-
+            } else {
+                ContentValues values = new ContentValues();
+                values.put(EXTRAS_VALUE, value);
+                if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
+                    return;
                 }
-                writeUserDataIntoCacheLocked(accounts, db, account, key, value);
-                db.setTransactionSuccessful();
-            } finally {
-                db.endTransaction();
+
             }
+            writeUserDataIntoCacheLocked(accounts, db, account, key, value);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
         }
     }
 
@@ -4788,17 +4807,16 @@
         }
     }
 
-    protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
-        synchronized (accounts.cacheLock) {
-            HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
-            if (userDataForAccount == null) {
-                // need to populate the cache for this account
-                final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
-                userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
-                accounts.userDataCache.put(account, userDataForAccount);
-            }
-            return userDataForAccount.get(key);
+    protected String readUserDataInternalLocked(
+            UserAccounts accounts, Account account, String key) {
+        HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+        if (userDataForAccount == null) {
+            // need to populate the cache for this account
+            final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+            userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+            accounts.userDataCache.put(account, userDataForAccount);
         }
+        return userDataForAccount.get(key);
     }
 
     protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ccaa1d2..c7f7378 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6430,6 +6430,9 @@
                 if (mLockScreenShown == LOCK_SCREEN_SHOWN) {
                     mLockScreenShown = LOCK_SCREEN_HIDDEN;
                     updateSleepIfNeededLocked();
+
+                    // Some stack visibility might change (e.g. docked stack)
+                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 6716a47..574b9db 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1902,7 +1902,7 @@
             return false;
         }
 
-        cancelInitializingActivities();
+        mStackSupervisor.cancelInitializingActivities();
 
         // Find the first activity that is not finishing.
         final ActivityRecord next = topRunningActivityLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 662c51e..c143474 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -908,6 +908,15 @@
         }
     }
 
+    void cancelInitializingActivities() {
+        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                stacks.get(stackNdx).cancelInitializingActivities();
+            }
+        }
+    }
+
     void reportActivityVisibleLocked(ActivityRecord r) {
         sendWaitingVisibleReportLocked(r);
     }
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 5806f3f..a6325a4 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -20,6 +20,7 @@
 import android.media.AudioRecordConfiguration;
 import android.media.AudioSystem;
 import android.media.IRecordingConfigDispatcher;
+import android.media.MediaRecorder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -48,6 +49,9 @@
      * Implementation of android.media.AudioSystem.AudioRecordingCallback
      */
     public void onRecordingConfigurationChanged(int event, int session, int source) {
+        if (MediaRecorder.isSystemOnlyAudioSource(source)) {
+            return;
+        }
         if (updateSnapshot(event, session, source)) {
             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
             synchronized(mClients) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 484b0e9..2ca5534 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -23,11 +23,13 @@
 
 import android.app.Notification;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
+import android.os.Build;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -146,6 +148,23 @@
             importance = IMPORTANCE_DEFAULT;
         }
 
+        try {
+            final ApplicationInfo applicationInfo =
+                    mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(),
+                            0, sbn.getUser().getIdentifier());
+            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.N) {
+                if (isNoisy) {
+                    if (importance >= IMPORTANCE_HIGH) {
+                        importance = IMPORTANCE_MAX;
+                    } else {
+                        importance = IMPORTANCE_HIGH;
+                    }
+                }
+            }
+        } catch (NameNotFoundException e) {
+            // oh well.
+        }
+
         if (n.fullScreenIntent != null) {
             importance = IMPORTANCE_MAX;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5db7e63..bf0073a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -65,6 +65,7 @@
 import static android.content.pm.PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.MOVE_FAILED_DEVICE_ADMIN;
 import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
 import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
@@ -3339,11 +3340,6 @@
                     + " with flags 0x" + Integer.toHexString(flags), new Throwable());
         }
 
-        // Safe mode means we shouldn't match any third-party components
-        if (mSafeMode) {
-            flags |= PackageManager.MATCH_SYSTEM_ONLY;
-        }
-
         return updateFlags(flags, userId);
     }
 
@@ -3351,6 +3347,11 @@
      * Update given flags when being used to request {@link ResolveInfo}.
      */
     int updateFlagsForResolve(int flags, int userId, Object cookie) {
+        // Safe mode means we shouldn't match any third-party components
+        if (mSafeMode) {
+            flags |= PackageManager.MATCH_SYSTEM_ONLY;
+        }
+
         return updateFlagsForComponent(flags, userId, cookie);
     }
 
@@ -14104,6 +14105,11 @@
         });
     }
 
+    @Override
+    public boolean isPackageDeviceAdminOnAnyUser(String packageName) {
+        return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
+    }
+
     private boolean isPackageDeviceAdmin(String packageName, int userId) {
         IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
@@ -18170,6 +18176,10 @@
                 throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
                         "Package already moved to " + volumeUuid);
             }
+            if (pkg.applicationInfo.isInternal() && isPackageDeviceAdminOnAnyUser(packageName)) {
+                throw new PackageManagerException(MOVE_FAILED_DEVICE_ADMIN,
+                        "Device admin cannot be moved");
+            }
 
             if (ps.frozen) {
                 throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING,
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 76d6b28..117c663 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1749,16 +1749,6 @@
         }
     }
 
-    private boolean isPackageInstalled(String pkg, int userId) {
-        final ApplicationInfo info = mPm.getApplicationInfo(pkg,
-                PackageManager.GET_UNINSTALLED_PACKAGES,
-                userId);
-        if (info == null || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Removes the app restrictions file for a specific package and user id, if it exists.
      */
@@ -2210,20 +2200,18 @@
             }
         }
 
-        if (isPackageInstalled(packageName, userId)) {
-            // Notify package of changes via an intent - only sent to explicitly registered receivers.
-            Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
-            changeIntent.setPackage(packageName);
-            changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            mContext.sendBroadcastAsUser(changeIntent, new UserHandle(userId));
-        }
+        // Notify package of changes via an intent - only sent to explicitly registered receivers.
+        Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+        changeIntent.setPackage(packageName);
+        changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
     }
 
     private int getUidForPackage(String packageName) {
         long ident = Binder.clearCallingIdentity();
         try {
             return mContext.getPackageManager().getApplicationInfo(packageName,
-                    PackageManager.GET_UNINSTALLED_PACKAGES).uid;
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES).uid;
         } catch (NameNotFoundException nnfe) {
             return -1;
         } finally {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 6c2e4d4..e88b72f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2409,7 +2409,6 @@
             case TYPE_WALLPAPER:
             case TYPE_DREAM:
             case TYPE_KEYGUARD_SCRIM:
-            case TYPE_DOCK_DIVIDER:
                 return false;
             default:
                 // Hide only windows below the keyguard host window.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0c429e5..144d7ac 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -19,6 +19,9 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
@@ -607,4 +610,39 @@
         final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
         return (stack != null && stack.isVisibleLocked()) ? stack : null;
     }
+
+    /**
+     * Find the visible, touch-deliverable window under the given point
+     */
+    WindowState getTouchableWinAtPointLocked(float xf, float yf) {
+        WindowState touchedWin = null;
+        final int x = (int) xf;
+        final int y = (int) yf;
+
+        for (int i = mWindows.size() - 1; i >= 0; i--) {
+            WindowState window = mWindows.get(i);
+            final int flags = window.mAttrs.flags;
+            if (!window.isVisibleLw()) {
+                continue;
+            }
+            if ((flags & FLAG_NOT_TOUCHABLE) != 0) {
+                continue;
+            }
+
+            window.getVisibleBounds(mTmpRect);
+            if (!mTmpRect.contains(x, y)) {
+                continue;
+            }
+
+            window.getTouchableRegion(mTmpRegion);
+
+            final int touchFlags = flags & (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL);
+            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
+                touchedWin = window;
+                break;
+            }
+        }
+
+        return touchedWin;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 412a455..75c06ff 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -27,6 +27,8 @@
 
 import com.android.server.wm.DimLayer.DimLayerUser;
 
+import java.util.ArrayList;
+
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -77,7 +79,17 @@
     }
 
     void setResizing(boolean resizing) {
-        mResizing = resizing;
+        if (mResizing != resizing) {
+            mResizing = resizing;
+            resetDragResizingChangeReported();
+        }
+    }
+
+    private void resetDragResizingChangeReported() {
+        final WindowList windowList = mDisplayContent.getWindowList();
+        for (int i = windowList.size() - 1; i >= 0; i--) {
+            windowList.get(i).resetDragResizingChangeReported();
+        }
     }
 
     void setWindow(WindowState window) {
@@ -90,7 +102,9 @@
             return;
         }
         TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
-        final boolean visible = stack != null && stack.isVisibleLocked();
+
+        // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
+        final boolean visible = stack != null;
         if (mLastVisibility == visible && !force) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 9a3aaa5..cf27b97 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -27,8 +27,6 @@
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.IBinder;
 import android.os.Message;
@@ -71,6 +69,13 @@
 class DragState {
     private static final long ANIMATION_DURATION_MS = 500;
 
+    private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
+            View.DRAG_FLAG_GLOBAL_URI_WRITE;
+
+    private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
+            View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
+            View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
+
     final WindowManagerService mService;
     IBinder mToken;
     SurfaceControl mSurfaceControl;
@@ -95,10 +100,7 @@
     WindowState mTargetWindow;
     ArrayList<WindowState> mNotifiedWindows;
     boolean mDragInProgress;
-    Display mDisplay;
-
-    private final Region mTmpRegion = new Region();
-    private final Rect mTmpRect = new Rect();
+    DisplayContent mDisplayContent;
 
     private Animation mAnimation;
     final Transformation mTransformation = new Transformation();
@@ -131,11 +133,12 @@
      * @param display The Display that the window being dragged is on.
      */
     void register(Display display) {
-        mDisplay = display;
         if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
         if (mClientChannel != null) {
             Slog.e(TAG_WM, "Duplicate register of drag input channel");
         } else {
+            mDisplayContent = mService.getDisplayContentLocked(display.getDisplayId());
+
             InputChannel[] channels = InputChannel.openInputChannelPair("drag");
             mServerChannel = channels[0];
             mClientChannel = channels[1];
@@ -149,7 +152,7 @@
                     WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
 
             mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
-                    mDisplay.getDisplayId());
+                    display.getDisplayId());
             mDragWindowHandle.name = "drag";
             mDragWindowHandle.inputChannel = mServerChannel;
             mDragWindowHandle.layer = getDragLayerLw();
@@ -174,7 +177,7 @@
             mDragWindowHandle.frameLeft = 0;
             mDragWindowHandle.frameTop = 0;
             Point p = new Point();
-            mDisplay.getRealSize(p);
+            display.getRealSize(p);
             mDragWindowHandle.frameRight = p.x;
             mDragWindowHandle.frameBottom = p.y;
 
@@ -244,12 +247,10 @@
             Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
         }
 
-        final WindowList windows = mService.getWindowListLocked(mDisplay);
-        if (windows != null) {
-            final int N = windows.size();
-            for (int i = 0; i < N; i++) {
-                sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
-            }
+        final WindowList windows = mDisplayContent.getWindowList();
+        final int N = windows.size();
+        for (int i = 0; i < N; i++) {
+            sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
         }
     }
 
@@ -379,7 +380,7 @@
     private void cleanUpDragLw() {
         broadcastDragEndedLw();
         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            mService.restorePointerIconLocked(mDisplay, mCurrentX, mCurrentY);
+            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
         }
 
         // stop intercepting input
@@ -418,7 +419,7 @@
 
     void notifyLocationLw(float x, float y) {
         // Tell the affected window
-        WindowState touchedWin = mService.getTouchableWinAtPointLocked(mDisplay, x, y);
+        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
         if (touchedWin == null) {
             if (DEBUG_DRAG) Slog.d(TAG_WM, "No touched win at x=" + x + " y=" + y);
             return;
@@ -463,17 +464,18 @@
         mTargetWindow = touchedWin;
     }
 
-    // Tell the drop target about the data.  Returns 'true' if we can immediately
+    // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
     // dispatch the global drag-ended message, 'false' if we need to wait for a
     // result from the recipient.
-    boolean notifyDropLw(WindowState touchedWin, IDropPermissions dropPermissions,
-            float x, float y) {
+    boolean notifyDropLw(float x, float y) {
         if (mAnimation != null) {
             return false;
         }
         mCurrentX = x;
         mCurrentY = y;
 
+        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+
         if (!isWindowNotified(touchedWin)) {
             // "drop" outside a valid window -- no recipient to apply a
             // timeout to, and we can send the drag-ended message immediately.
@@ -484,7 +486,21 @@
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "sending DROP to " + touchedWin);
         }
-        if (mSourceUserId != UserHandle.getUserId(touchedWin.getOwningUid())){
+
+        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+
+        DropPermissionsHandler dropPermissions = null;
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
+                (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+            dropPermissions = new DropPermissionsHandler(
+                    mData,
+                    mUid,
+                    touchedWin.getOwningPackage(),
+                    mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+                    mSourceUserId,
+                    targetUserId);
+        }
+        if (mSourceUserId != targetUserId){
             mData.fixUris(mSourceUserId);
         }
         final int myPid = Process.myPid();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4167ac4..fe55e80 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -536,7 +536,20 @@
     }
 
     void setDragResizing(boolean dragResizing) {
-        mDragResizing = dragResizing;
+        if (mDragResizing != dragResizing) {
+            mDragResizing = dragResizing;
+            resetDragResizingChangeReported();
+        }
+    }
+
+    void resetDragResizingChangeReported() {
+        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
+            final ArrayList<WindowState> windows = mAppTokens.get(activityNdx).allAppWindows;
+            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+                final WindowState win = windows.get(winNdx);
+                win.resetDragResizingChangeReported();
+            }
+        }
     }
 
     boolean isDragResizing() {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7244676..8409058 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -898,7 +898,8 @@
     }
 
     boolean isVisibleLocked() {
-        final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded();
+        final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded()
+                && !mService.mAnimator.mKeyguardGoingAway;
         if (keyguardOn && !StackId.isAllowedOverLockscreen(mStackId)) {
             // The keyguard is showing and the stack shouldn't show on top of the keyguard.
             return false;
@@ -919,6 +920,16 @@
         return mDragResizing;
     }
 
+    private void setDragResizingLocked(boolean resizing) {
+        if (mDragResizing == resizing) {
+            return;
+        }
+        mDragResizing = resizing;
+        for (int i = mTasks.size() - 1; i >= 0 ; i--) {
+            mTasks.get(i).resetDragResizingChangeReported();
+        }
+    }
+
     @Override  // AnimatesBounds
     public boolean setSize(Rect bounds) {
         synchronized (mService.mWindowMap) {
@@ -936,14 +947,14 @@
     @Override  // AnimatesBounds
     public void onAnimationStart() {
         synchronized (mService.mWindowMap) {
-            mDragResizing = true;
+            setDragResizingLocked(true);
         }
     }
 
     @Override  // AnimatesBounds
     public void onAnimationEnd() {
         synchronized (mService.mWindowMap) {
-            mDragResizing = false;
+            setDragResizingLocked(false);
             mService.requestTraversal();
         }
         if (mStackId == PINNED_STACK_ID) {
@@ -968,4 +979,4 @@
     public void getFullScreenBounds(Rect bounds) {
         getDisplayContent().getContentRect(bounds);
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 8d2fb9b..f8a4d33 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEYGUARD;
@@ -231,7 +232,10 @@
         // Only hide windows if the keyguard is active and not animating away.
         boolean keyguardOn = mPolicy.isKeyguardShowingOrOccluded()
                 && mForceHiding != KEYGUARD_ANIMATING_OUT;
-        return keyguardOn && !allowWhenLocked && (win.getDisplayId() == Display.DEFAULT_DISPLAY);
+        boolean hideDockDivider = win.mAttrs.type == TYPE_DOCK_DIVIDER
+                && win.getDisplayContent().getDockedStackLocked() == null;
+        return keyguardOn && !allowWhenLocked && (win.getDisplayId() == Display.DEFAULT_DISPLAY)
+                || hideDockDivider;
     }
 
     private void updateWindowsLocked(final int displayId) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c8f5dda..7169375 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -664,13 +664,6 @@
 
     private WindowContentFrameStats mTempWindowRenderStats;
 
-    private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
-            View.DRAG_FLAG_GLOBAL_URI_WRITE;
-
-    private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
-            View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
-            View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
-
     final class DragInputEventReceiver extends InputEventReceiver {
         // Set, if stylus button was down at the start of the drag.
         private boolean mStylusButtonDownAtStart;
@@ -716,7 +709,7 @@
                             if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
                                     + newX + "," + newY);
                             synchronized (mWindowMap) {
-                                endDrag = completeDropLw(newX, newY);
+                                endDrag = mDragState.notifyDropLw(newX, newY);
                             }
                         } else {
                             synchronized (mWindowMap) {
@@ -730,7 +723,7 @@
                         if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
                                 + newX + "," + newY);
                         synchronized (mWindowMap) {
-                            endDrag = completeDropLw(newX, newY);
+                            endDrag = mDragState.notifyDropLw(newX, newY);
                         }
                     } break;
 
@@ -760,25 +753,6 @@
         }
     }
 
-    private boolean completeDropLw(float x, float y) {
-        WindowState dropTargetWin = getTouchableWinAtPointLocked(mDragState.mDisplay, x, y);
-
-        DropPermissionsHandler dropPermissions = null;
-        if (dropTargetWin != null &&
-                (mDragState.mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
-                (mDragState.mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
-            dropPermissions = new DropPermissionsHandler(
-                    mDragState.mData,
-                    mDragState.mUid,
-                    dropTargetWin.getOwningPackage(),
-                    mDragState.mFlags & DRAG_FLAGS_URI_PERMISSIONS,
-                    mDragState.mSourceUserId,
-                    UserHandle.getUserId(dropTargetWin.getOwningUid()));
-        }
-
-        return mDragState.notifyDropLw(dropTargetWin, dropPermissions, x, y);
-    }
-
     /**
      * Whether the UI is currently running in touch mode (not showing
      * navigational focus because the user is directly pressing the screen).
@@ -8839,7 +8813,8 @@
                 Slog.v(TAG_WM, "Win " + w + " config changed: "
                         + mCurConfiguration);
             }
-            final boolean dragResizingChanged = w.isDragResizeChanged();
+            final boolean dragResizingChanged = w.isDragResizeChanged()
+                    && !w.isDragResizingChangeReported();
             if (localLOGV) Slog.v(TAG_WM, "Resizing " + w
                     + ": configChanged=" + configChanged
                     + " dragResizingChanged=" + dragResizingChanged
@@ -10466,43 +10441,6 @@
         }
     }
 
-    /**
-     * Find the visible, touch-deliverable window under the given point
-     */
-    WindowState getTouchableWinAtPointLocked(Display display, float xf, float yf) {
-        WindowState touchedWin = null;
-        final int x = (int) xf;
-        final int y = (int) yf;
-
-        final WindowList windows = getWindowListLocked(display);
-        if (windows == null) {
-            return null;
-        }
-        final int N = windows.size();
-        for (int i = N - 1; i >= 0; i--) {
-            WindowState child = windows.get(i);
-            final int flags = child.mAttrs.flags;
-            if (!child.isVisibleLw()) {
-                continue;
-            }
-            if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
-                continue;
-            }
-
-            child.getTouchableRegion(mTmpRegion);
-
-            final int touchFlags = flags &
-                    (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
-            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
-                touchedWin = child;
-                break;
-            }
-        }
-
-        return touchedWin;
-    }
-
     private MousePositionTracker mMousePositionTracker = new MousePositionTracker();
 
     private static class MousePositionTracker implements PointerEventListener {
@@ -10555,8 +10493,8 @@
             if (displayContent == null) {
                 return;
             }
-            Display display = displayContent.getDisplay();
-            WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, mouseX, mouseY);
+            WindowState windowUnderPointer =
+                    displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
             if (windowUnderPointer != callingWin) {
                 return;
             }
@@ -10570,11 +10508,12 @@
         }
     }
 
-    void restorePointerIconLocked(Display display, float latestX, float latestY) {
+    void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
         // Mouse position tracker has not been getting updates while dragging, update it now.
         mMousePositionTracker.updatePosition(latestX, latestY);
 
-        WindowState windowUnderPointer = getTouchableWinAtPointLocked(display, latestX, latestY);
+        WindowState windowUnderPointer =
+                displayContent.getTouchableWinAtPointLocked(latestX, latestY);
         if (windowUnderPointer != null) {
             try {
                 windowUnderPointer.mClient.updatePointerIcon(
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 37c8a7e..bea333b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -161,6 +161,7 @@
     boolean mAttachedHidden;    // is our parent window hidden?
     boolean mWallpaperVisible;  // for wallpaper, what was last vis report?
     boolean mDragResizing;
+    boolean mDragResizingChangeReported;
     int mResizeMode;
 
     RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
@@ -2103,6 +2104,7 @@
         mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
                 reportDraw, newConfig, getBackdropFrame(frame),
                 isDragResizeChanged() /* forceRelayout */);
+        mDragResizingChangeReported = true;
     }
 
     public void registerFocusObserver(IWindowFocusObserver observer) {
@@ -2137,6 +2139,20 @@
         return mDragResizing != computeDragResizing();
     }
 
+    /**
+     * @return Whether we reported a drag resize change to the application or not already.
+     */
+    boolean isDragResizingChangeReported() {
+        return mDragResizingChangeReported;
+    }
+
+    /**
+     * Resets the state whether we reported a drag resize change to the app.
+     */
+    void resetDragResizingChangeReported() {
+        mDragResizingChangeReported = false;
+    }
+
     int getResizeMode() {
         return mResizeMode;
     }
@@ -2161,7 +2177,11 @@
     }
 
     void setDragResizing() {
-        mDragResizing = computeDragResizing();
+        final boolean resizing = computeDragResizing();
+        if (resizing == mDragResizing) {
+            return;
+        }
+        mDragResizing = resizing;
         mResizeMode = mDragResizing && mDisplayContent.mDividerControllerLocked.isResizing()
                 ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
                 : DRAG_RESIZE_MODE_FREEFORM;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index fa727d4..6ec0ba1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2702,6 +2702,10 @@
         if (info == null) {
             throw new IllegalArgumentException("Bad admin: " + adminReceiver);
         }
+        if (!info.getActivityInfo().applicationInfo.isInternal()) {
+            throw new IllegalArgumentException("Only apps in internal storage can be active admin: "
+                    + adminReceiver);
+        }
         synchronized (this) {
             long ident = mInjector.binderClearCallingIdentity();
             try {
@@ -5392,6 +5396,9 @@
         }
         synchronized (this) {
             enforceCanSetDeviceOwnerLocked(userId);
+            if (getActiveAdminUncheckedLocked(admin, userId) == null) {
+                throw new IllegalArgumentException("Not active admin: " + admin);
+            }
 
             // Shutting down backup manager service permanently.
             long ident = mInjector.binderClearCallingIdentity();
@@ -5567,6 +5574,11 @@
         }
         synchronized (this) {
             enforceCanSetProfileOwnerLocked(userHandle);
+
+            if (getActiveAdminUncheckedLocked(who, userHandle) == null) {
+                throw new IllegalArgumentException("Not active admin: " + who);
+            }
+
             mOwners.setProfileOwner(who, ownerName, userHandle);
             mOwners.writeProfileOwner(userHandle);
             return true;
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 38551b6..5229b40 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.BaseDhcpStateMachine;
 import android.net.DhcpResults;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
@@ -83,7 +82,7 @@
  *
  * @hide
  */
-public class DhcpClient extends BaseDhcpStateMachine {
+public class DhcpClient extends StateMachine {
 
     private static final String TAG = "DhcpClient";
     private static final boolean DBG = true;
@@ -240,16 +239,10 @@
         mOneshotTimeoutAlarm = makeWakeupMessage("ONESHOT_TIMEOUT", CMD_ONESHOT_TIMEOUT);
     }
 
-    @Override
     public void registerForPreDhcpNotification() {
         mRegisteredForPreDhcpNotification = true;
     }
 
-    public static BaseDhcpStateMachine makeDhcpStateMachine(
-            Context context, StateMachine controller, String intf) {
-        return makeDhcpClient(context, controller, intf);
-    }
-
     public static DhcpClient makeDhcpClient(
             Context context, StateMachine controller, String intf) {
         DhcpClient client = new DhcpClient(context, controller, intf);
@@ -446,12 +439,12 @@
      *
      * @hide
      */
-    @Override
     public void doQuit() {
         Log.d(TAG, "doQuit");
         quit();
     }
 
+    @Override
     protected void onQuitting() {
         Log.d(TAG, "onQuitting");
         mController.sendMessage(CMD_ON_QUIT);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index b23ad50..6d168b0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1668,7 +1668,12 @@
         // that the test user is not affiliated anymore.
         dpm.clearProfileOwner(admin2);
         final ComponentName admin = new ComponentName("test", "test");
-        markPackageAsInstalled(admin.getPackageName(), null, DpmMockContext.CALLER_USER_HANDLE);
+
+        setUpPackageManagerForFakeAdmin(admin, DpmMockContext.CALLER_UID,
+                /* enabledSetting =*/ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                /* appTargetSdk = */ null, admin2);
+
+        dpm.setActiveAdmin(admin, /* refreshing =*/ true, DpmMockContext.CALLER_USER_HANDLE);
         assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
         assertFalse(dpm.isAffiliatedUser());
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 53ca45d..ca43644 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -19,6 +19,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -96,12 +97,26 @@
 
     protected void setUpPackageManagerForAdmin(ComponentName admin, int packageUid,
             Integer enabledSetting, Integer appTargetSdk) throws Exception {
+        setUpPackageManagerForFakeAdmin(admin, packageUid, enabledSetting, appTargetSdk,
+                admin);
+    }
+
+    /**
+     * Set up a component in the mock package manager to be an active admin.
+     *
+     * @param admin ComponentName that's visible to the test code, which doesn't have to exist.
+     * @param copyFromAdmin package information for {@code admin} will be built based on this
+     *    component's information.
+     */
+    protected void setUpPackageManagerForFakeAdmin(ComponentName admin, int packageUid,
+            Integer enabledSetting, Integer appTargetSdk, ComponentName copyFromAdmin)
+            throws Exception {
 
         // Set up getApplicationInfo().
 
         final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
                 mRealTestContext.getPackageManager().getApplicationInfo(
-                        admin.getPackageName(),
+                        copyFromAdmin.getPackageName(),
                         PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
 
         ai.enabledSetting = enabledSetting == null
@@ -111,6 +126,8 @@
             ai.targetSdkVersion = appTargetSdk;
         }
         ai.uid = packageUid;
+        ai.packageName = admin.getPackageName();
+        ai.name = admin.getClassName();
 
         doReturn(ai).when(mMockContext.ipackageManager).getApplicationInfo(
                 eq(admin.getPackageName()),
@@ -120,7 +137,7 @@
         // Set up queryBroadcastReceivers().
 
         final Intent resolveIntent = new Intent();
-        resolveIntent.setComponent(admin);
+        resolveIntent.setComponent(copyFromAdmin);
         final List<ResolveInfo> realResolveInfo =
                 mRealTestContext.getPackageManager().queryBroadcastReceivers(
                         resolveIntent,
@@ -132,7 +149,10 @@
         realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
 
         // We need to rewrite the UID in the activity info.
-        realResolveInfo.get(0).activityInfo.applicationInfo = ai;
+        final ActivityInfo aci = realResolveInfo.get(0).activityInfo;
+        aci.applicationInfo = ai;
+        aci.packageName = admin.getPackageName();
+        aci.name = admin.getClassName();
 
         doReturn(realResolveInfo).when(mMockContext.packageManager).queryBroadcastReceiversAsUser(
                 MockUtils.checkIntentComponent(admin),
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 46ad8a1..8e891bf 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -74,6 +74,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.SystemService;
 
@@ -125,6 +126,7 @@
     Handler mHandler;
     AppOpsManager mAppOps;
     UserManager mUserManager;
+    PackageManager mPackageManager;
     AppWidgetManager mAppWidgetManager;
     IDeviceIdleController mDeviceIdleController;
     private DisplayManager mDisplayManager;
@@ -157,7 +159,7 @@
     public void onStart() {
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
-
+        mPackageManager = getContext().getPackageManager();
         mHandler = new H(BackgroundThread.get().getLooper());
 
         File systemDataDir = new File(Environment.getDataDirectory(), "system");
@@ -296,9 +298,8 @@
     private void initializeDefaultsForSystemApps(int userId) {
         Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
-        List<PackageInfo> packages = getContext().getPackageManager().getInstalledPackagesAsUser(
-                PackageManager.MATCH_DISABLED_COMPONENTS
-                | PackageManager.MATCH_UNINSTALLED_PACKAGES,
+        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                PackageManager.MATCH_DISABLED_COMPONENTS,
                 userId);
         final int packageCount = packages.size();
         for (int i = 0; i < packageCount; i++) {
@@ -398,47 +399,59 @@
         }
     }
 
-    /** Check all running users' or specified user's apps to see if they enter an idle state. */
-    void checkIdleStates(int checkUserId) {
+    /**
+     * Check all running users' or specified user's apps to see if they enter an idle state.
+     * @return Returns whether checking should continue periodically.
+     */
+    boolean checkIdleStates(int checkUserId) {
         if (!mAppIdleEnabled) {
-            return;
+            return false;
         }
 
-        final int[] userIds;
+        final int[] runningUserIds;
         try {
-            if (checkUserId == UserHandle.USER_ALL) {
-                userIds = ActivityManagerNative.getDefault().getRunningUserIds();
-            } else {
-                userIds = new int[] { checkUserId };
+            runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds();
+            if (checkUserId != UserHandle.USER_ALL
+                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
+                return false;
             }
         } catch (RemoteException re) {
-            return;
+            return false;
         }
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
-        for (int i = 0; i < userIds.length; i++) {
-            final int userId = userIds[i];
-            List<PackageInfo> packages =
-                    getContext().getPackageManager().getInstalledPackagesAsUser(
-                            PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                            userId);
-            synchronized (mLock) {
-                final int packageCount = packages.size();
-                for (int p = 0; p < packageCount; p++) {
-                    final PackageInfo pi = packages.get(p);
-                    final String packageName = pi.packageName;
-                    final boolean isIdle = isAppIdleFiltered(packageName,
-                            UserHandle.getAppId(pi.applicationInfo.uid),
-                            userId, elapsedRealtime);
-                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                            userId, isIdle ? 1 : 0, packageName));
-                    if (isIdle) {
+        for (int i = 0; i < runningUserIds.length; i++) {
+            final int userId = runningUserIds[i];
+            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
+                continue;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Checking idle state for user " + userId);
+            }
+            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                    PackageManager.MATCH_DISABLED_COMPONENTS,
+                    userId);
+            final int packageCount = packages.size();
+            for (int p = 0; p < packageCount; p++) {
+                final PackageInfo pi = packages.get(p);
+                final String packageName = pi.packageName;
+                final boolean isIdle = isAppIdleFiltered(packageName,
+                        UserHandle.getAppId(pi.applicationInfo.uid),
+                        userId, elapsedRealtime);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                        userId, isIdle ? 1 : 0, packageName));
+                if (isIdle) {
+                    synchronized (mLock) {
                         mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
                     }
                 }
             }
         }
+        if (DEBUG) {
+            Slog.d(TAG, "checkIdleStates took "
+                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
+        return true;
     }
 
     /** Check if it's been a while since last parole and let idle apps do some work */
@@ -459,7 +472,7 @@
 
     private void notifyBatteryStats(String packageName, int userId, boolean idle) {
         try {
-            final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
+            final int uid = mPackageManager.getPackageUidAsUser(packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
             if (idle) {
                 mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
@@ -468,7 +481,7 @@
                 mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
                         packageName, uid);
             }
-        } catch (RemoteException re) {
+        } catch (NameNotFoundException | RemoteException e) {
         }
     }
 
@@ -592,7 +605,7 @@
             // Only force the sync adapters to active if the provider is not in the same package and
             // the sync adapter is a system package.
             try {
-                PackageInfo pi = AppGlobals.getPackageManager().getPackageInfo(
+                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
                         packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
                 if (pi == null || pi.applicationInfo == null) {
                     continue;
@@ -600,7 +613,7 @@
                 if (!packageName.equals(providerPkgName)) {
                     forceIdleState(packageName, userId, false);
                 }
-            } catch (RemoteException re) {
+            } catch (NameNotFoundException e) {
                 // Shouldn't happen
             }
         }
@@ -725,7 +738,7 @@
 
     int getAppId(String packageName) {
         try {
-            ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(packageName,
+            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES
                             | PackageManager.MATCH_DISABLED_COMPONENTS);
             return ai.uid;
@@ -772,12 +785,8 @@
             }
         } catch (RemoteException re) {
         }
-        // TODO: Optimize this check
-        if (isActiveDeviceAdmin(packageName, userId)) {
-            return false;
-        }
 
-        if (isCarrierApp(packageName)) {
+        if (isActiveDeviceAdmin(packageName, userId)) {
             return false;
         }
 
@@ -790,7 +799,17 @@
             return false;
         }
 
-        return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
+        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
+            return false;
+        }
+
+        // Check this last, as it is the most expensive check
+        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
+        if (isCarrierApp(packageName)) {
+            return false;
+        }
+
+        return true;
     }
 
     int[] getIdleUidsForUser(int userId) {
@@ -803,7 +822,7 @@
         List<ApplicationInfo> apps;
         try {
             ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
-                    .getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+                    .getInstalledApplications(/* flags= */ 0, userId);
             if (slice == null) {
                 return new int[0];
             }
@@ -833,7 +852,9 @@
                 uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
             }
         }
-
+        if (DEBUG) {
+            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
         int numIdle = 0;
         for (int i = uidStates.size() - 1; i >= 0; i--) {
             int value = uidStates.valueAt(i);
@@ -865,15 +886,7 @@
     private boolean isActiveDeviceAdmin(String packageName, int userId) {
         DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
         if (dpm == null) return false;
-        List<ComponentName> components = dpm.getActiveAdminsAsUser(userId);
-        if (components == null) return false;
-        final int size = components.size();
-        for (int i = 0; i < size; i++) {
-            if (components.get(i).getPackageName().equals(packageName)) {
-                return true;
-            }
-        }
-        return false;
+        return dpm.packageHasActiveAdmins(packageName, userId);
     }
 
     private boolean isCarrierApp(String packageName) {
@@ -1011,10 +1024,11 @@
                     break;
 
                 case MSG_CHECK_IDLE_STATES:
-                    checkIdleStates(msg.arg1);
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                            MSG_CHECK_IDLE_STATES, msg.arg1, 0),
-                            mCheckIdleIntervalMillis);
+                    if (checkIdleStates(msg.arg1)) {
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
+                                mCheckIdleIntervalMillis);
+                    }
                     break;
 
                 case MSG_ONE_TIME_CHECK_IDLE_STATES:
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
index 18a5d59..f7cd6a3 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -54,6 +54,7 @@
     private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
             + GenericSoundModelContract.TABLE + "("
             + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+            + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
             + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
 
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 354075e..cde47bd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,12 +16,16 @@
 
 package com.android.server.soundtrigger;
 
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
@@ -29,6 +33,7 @@
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
+import android.hardware.soundtrigger.SoundTrigger.SoundModel;
 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
 import android.hardware.soundtrigger.SoundTriggerModule;
 import android.os.PowerManager;
@@ -40,9 +45,16 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.UUID;
 
 /**
- * Helper for {@link SoundTrigger} APIs.
+ * Helper for {@link SoundTrigger} APIs. Supports two types of models:
+ * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
+ * a single voice model running on the DSP at any given time.
+ *
+ * (ii) Generic sound-trigger models: Supports multiple of these.
+ *
  * Currently this just acts as an abstraction over all SoundTrigger API calls.
  *
  * @hide
@@ -62,7 +74,7 @@
     private static final int INVALID_VALUE = Integer.MIN_VALUE;
 
     /** The {@link ModuleProperties} for the system, or null if none exists. */
-    final ModuleProperties moduleProperties;
+    final ModuleProperties mModuleProperties;
 
     /** The properties for the DSP module */
     private SoundTriggerModule mModule;
@@ -72,21 +84,36 @@
     private final PhoneStateListener mPhoneStateListener;
     private final PowerManager mPowerManager;
 
-    // TODO: Since many layers currently only deal with one recognition
+    // TODO: Since the voice layer currently only handles one recognition
     // we simplify things by assuming one listener here too.
-    private IRecognitionStatusCallback mActiveListener;
+    private IRecognitionStatusCallback mKeyphraseListener;
+
+    // The SoundTriggerManager layer handles multiple generic recognition models. We store the
+    // ModelData here in a hashmap.
+    private final HashMap<UUID, ModelData> mGenericModelDataMap;
+
+    // Note: KeyphraseId is not really used.
     private int mKeyphraseId = INVALID_VALUE;
-    private int mCurrentSoundModelHandle = INVALID_VALUE;
+
+    // Current voice sound model handle. We only allow one voice model to run at any given time.
+    private int mCurrentKeyphraseModelHandle = INVALID_VALUE;
     private KeyphraseSoundModel mCurrentSoundModel = null;
     // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
     private RecognitionConfig mRecognitionConfig = null;
+
+    // Whether we are requesting recognition to start.
     private boolean mRequested = false;
     private boolean mCallActive = false;
     private boolean mIsPowerSaveMode = false;
     // Indicates if the native sound trigger service is disabled or not.
     // This is an indirect indication of the microphone being open in some other application.
     private boolean mServiceDisabled = false;
-    private boolean mStarted = false;
+
+    // Whether we have ANY recognition (keyphrase or generic) running.
+    private boolean mRecognitionRunning = false;
+
+    // Keeps track of whether the keyphrase recognition is running.
+    private boolean mKeyphraseStarted = false;
     private boolean mRecognitionAborted = false;
     private PowerSaveModeListener mPowerSaveModeListener;
 
@@ -96,14 +123,87 @@
         mContext = context;
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mGenericModelDataMap = new HashMap<UUID, ModelData>();
         mPhoneStateListener = new MyCallStateListener();
         if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
             Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
-            moduleProperties = null;
+            mModuleProperties = null;
             mModule = null;
         } else {
             // TODO: Figure out how to determine which module corresponds to the DSP hardware.
-            moduleProperties = modules.get(0);
+            mModuleProperties = modules.get(0);
+        }
+    }
+
+    /**
+     * Starts recognition for the given generic sound model ID.
+     *
+     * @param soundModel The sound model to use for recognition.
+     * @param listener The listener for the recognition events related to the given keyphrase.
+     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+     */
+    int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
+            IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
+        if (soundModel == null || callback == null || recognitionConfig == null) {
+            Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
+            return STATUS_ERROR;
+        }
+
+        synchronized (mLock) {
+
+            if (mModuleProperties == null) {
+                Slog.w(TAG, "Attempting startRecognition without the capability");
+                return STATUS_ERROR;
+            }
+
+            if (mModule == null) {
+                mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
+                if (mModule == null) {
+                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
+                    return STATUS_ERROR;
+                }
+            }
+
+            // Initialize power save, call active state monitoring logic.
+            if (!mRecognitionRunning) {
+                initializeTelephonyAndPowerStateListeners();
+            }
+
+            // Fetch a ModelData instance from the hash map. Creates a new one if none
+            // exists.
+            ModelData modelData = getOrCreateGenericModelData(modelId);
+
+            IRecognitionStatusCallback oldCallback = modelData.getCallback();
+            if (oldCallback != null) {
+                Slog.w(TAG, "Canceling previous recognition for model id: " + modelId);
+                try {
+                    oldCallback.onError(STATUS_ERROR);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onDetectionStopped", e);
+                }
+                modelData.clearCallback();
+            }
+
+            // Load the model if its not loaded.
+            if (!modelData.isModelLoaded()) {
+                // Load the model
+                int[] handle = new int[] { INVALID_VALUE };
+                int status = mModule.loadSoundModel(soundModel, handle);
+                if (status != SoundTrigger.STATUS_OK) {
+                    Slog.w(TAG, "loadSoundModel call failed with " + status);
+                    return status;
+                }
+                if (handle[0] == INVALID_VALUE) {
+                    Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+                    return STATUS_ERROR;
+                }
+                modelData.setHandle(handle[0]);
+            }
+            modelData.setCallback(callback);
+            modelData.setRecognitionConfig(recognitionConfig);
+
+            // Don't notify for synchronous calls.
+            return startGenericRecognitionLocked(modelData, false);
         }
     }
 
@@ -116,7 +216,7 @@
      * @param listener The listener for the recognition events related to the given keyphrase.
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int startRecognition(int keyphraseId,
+    int startKeyphraseRecognition(int keyphraseId,
             KeyphraseSoundModel soundModel,
             IRecognitionStatusCallback listener,
             RecognitionConfig recognitionConfig) {
@@ -129,36 +229,24 @@
                 Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
                         + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
                         + ", recognitionConfig=" + recognitionConfig);
-                Slog.d(TAG, "moduleProperties=" + moduleProperties);
+                Slog.d(TAG, "moduleProperties=" + mModuleProperties);
                 Slog.d(TAG, "current listener="
-                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
-                Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
+                        + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
+                Slog.d(TAG, "current SoundModel handle=" + mCurrentKeyphraseModelHandle);
                 Slog.d(TAG, "current SoundModel UUID="
                         + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid));
             }
 
-            if (!mStarted) {
-                // Get the current call state synchronously for the first recognition.
-                mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
-                // Register for call state changes when the first call to start recognition occurs.
-                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
-                // Register for power saver mode changes when the first call to start recognition
-                // occurs.
-                if (mPowerSaveModeListener == null) {
-                    mPowerSaveModeListener = new PowerSaveModeListener();
-                    mContext.registerReceiver(mPowerSaveModeListener,
-                            new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
-                }
-                mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
+            if (!mRecognitionRunning) {
+                initializeTelephonyAndPowerStateListeners();
             }
 
-            if (moduleProperties == null) {
+            if (mModuleProperties == null) {
                 Slog.w(TAG, "Attempting startRecognition without the capability");
                 return STATUS_ERROR;
             }
             if (mModule == null) {
-                mModule = SoundTrigger.attachModule(moduleProperties.id, this, null);
+                mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
                 if (mModule == null) {
                     Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
                     return STATUS_ERROR;
@@ -168,32 +256,32 @@
             // Unload the previous model if the current one isn't invalid
             // and, it's not the same as the new one.
             // This helps use cache and reuse the model and just start/stop it when necessary.
-            if (mCurrentSoundModelHandle != INVALID_VALUE
+            if (mCurrentKeyphraseModelHandle != INVALID_VALUE
                     && !soundModel.equals(mCurrentSoundModel)) {
                 Slog.w(TAG, "Unloading previous sound model");
-                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+                int status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
                 if (status != SoundTrigger.STATUS_OK) {
                     Slog.w(TAG, "unloadSoundModel call failed with " + status);
                 }
-                internalClearSoundModelLocked();
-                mStarted = false;
+                internalClearKeyphraseSoundModelLocked();
+                mKeyphraseStarted = false;
             }
 
             // If the previous recognition was by a different listener,
             // Notify them that it was stopped.
-            if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) {
+            if (mKeyphraseListener != null && mKeyphraseListener.asBinder() != listener.asBinder()) {
                 Slog.w(TAG, "Canceling previous recognition");
                 try {
-                    mActiveListener.onError(STATUS_ERROR);
+                    mKeyphraseListener.onError(STATUS_ERROR);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "RemoteException in onDetectionStopped", e);
                 }
-                mActiveListener = null;
+                mKeyphraseListener = null;
             }
 
             // Load the sound model if the current one is null.
-            int soundModelHandle = mCurrentSoundModelHandle;
-            if (mCurrentSoundModelHandle == INVALID_VALUE
+            int soundModelHandle = mCurrentKeyphraseModelHandle;
+            if (mCurrentKeyphraseModelHandle == INVALID_VALUE
                     || mCurrentSoundModel == null) {
                 int[] handle = new int[] { INVALID_VALUE };
                 int status = mModule.loadSoundModel(soundModel, handle);
@@ -213,18 +301,81 @@
             // Start the recognition.
             mRequested = true;
             mKeyphraseId = keyphraseId;
-            mCurrentSoundModelHandle = soundModelHandle;
+            mCurrentKeyphraseModelHandle = soundModelHandle;
             mCurrentSoundModel = soundModel;
             mRecognitionConfig = recognitionConfig;
             // Register the new listener. This replaces the old one.
             // There can only be a maximum of one active listener at any given time.
-            mActiveListener = listener;
+            mKeyphraseListener = listener;
 
             return updateRecognitionLocked(false /* don't notify for synchronous calls */);
         }
     }
 
     /**
+     * Stops recognition for the given generic sound model.
+     *
+     * @param modelId The identifier of the generic sound model for which
+     *        the recognition is to be stopped.
+     * @param listener The listener for the recognition events related to the given sound model.
+     *
+     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+     */
+    int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback listener) {
+        if (listener == null) {
+            return STATUS_ERROR;
+        }
+
+        synchronized (mLock) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            if (modelData == null) {
+                Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
+                return STATUS_ERROR;
+            }
+
+            IRecognitionStatusCallback currentCallback = modelData.getCallback();
+            if (DBG) {
+                Slog.d(TAG, "stopRecognition for modelId=" + modelId
+                        + ", listener=" + listener.asBinder());
+                Slog.d(TAG, "current callback ="
+                        + (currentCallback == null ? "null" : currentCallback.asBinder()));
+            }
+
+            if (mModuleProperties == null || mModule == null) {
+                Slog.w(TAG, "Attempting stopRecognition without the capability");
+                return STATUS_ERROR;
+            }
+
+            if (currentCallback == null || !modelData.modelStarted()) {
+                // startRecognition hasn't been called or it failed.
+                Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+                return STATUS_ERROR;
+            }
+            if (currentCallback.asBinder() != listener.asBinder()) {
+                // We don't allow a different listener to stop the recognition than the one
+                // that started it.
+                Slog.w(TAG, "Attempting stopRecognition for another recognition");
+                return STATUS_ERROR;
+            }
+
+            int status = stopGenericRecognitionLocked(modelData, false /* don't notify for synchronous calls */);
+            if (status != SoundTrigger.STATUS_OK) {
+                return status;
+            }
+
+            // We leave the sound model loaded but not started, this helps us when we start
+            // back.
+            // Also clear the internal state once the recognition has been stopped.
+            modelData.clearState();
+            modelData.clearCallback();
+            if (!computeRecognitionRunning()) {
+                internalClearGlobalStateLocked();
+            }
+            return status;
+        }
+    }
+
+    /**
      * Stops recognition for the given {@link Keyphrase} if a recognition is
      * currently active.
      *
@@ -234,7 +385,7 @@
      *
      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
      */
-    int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+    int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
         if (listener == null) {
             return STATUS_ERROR;
         }
@@ -244,20 +395,20 @@
                 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
                         + ", listener=" + listener.asBinder());
                 Slog.d(TAG, "current listener="
-                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
+                        + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
             }
 
-            if (moduleProperties == null || mModule == null) {
+            if (mModuleProperties == null || mModule == null) {
                 Slog.w(TAG, "Attempting stopRecognition without the capability");
                 return STATUS_ERROR;
             }
 
-            if (mActiveListener == null) {
+            if (mKeyphraseListener == null) {
                 // startRecognition hasn't been called or it failed.
                 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
                 return STATUS_ERROR;
             }
-            if (mActiveListener.asBinder() != listener.asBinder()) {
+            if (mKeyphraseListener.asBinder() != listener.asBinder()) {
                 // We don't allow a different listener to stop the recognition than the one
                 // that started it.
                 Slog.w(TAG, "Attempting stopRecognition for another recognition");
@@ -274,7 +425,8 @@
             // We leave the sound model loaded but not started, this helps us when we start
             // back.
             // Also clear the internal state once the recognition has been stopped.
-            internalClearStateLocked();
+            internalClearKeyphraseStateLocked();
+            internalClearGlobalStateLocked();
             return status;
         }
     }
@@ -284,38 +436,56 @@
      */
     void stopAllRecognitions() {
         synchronized (mLock) {
-            if (moduleProperties == null || mModule == null) {
+            if (mModuleProperties == null || mModule == null) {
                 return;
             }
 
-            if (mCurrentSoundModelHandle == INVALID_VALUE) {
-                return;
+            // Stop Keyphrase recognition if one exists.
+            if (mCurrentKeyphraseModelHandle != INVALID_VALUE) {
+
+                mRequested = false;
+                int status = updateRecognitionLocked(
+                        false /* don't notify for synchronous calls */);
+                internalClearKeyphraseStateLocked();
             }
 
-            mRequested = false;
-            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
-            internalClearStateLocked();
+            // Stop all generic recognition models.
+            for (ModelData model : mGenericModelDataMap.values()) {
+                if (model.modelStarted()) {
+                    int status = stopGenericRecognitionLocked(model,
+                            false /* do not notify for synchronous calls */);
+                    if (status != STATUS_OK) {
+                        // What else can we do if there is an error here.
+                        Slog.w(TAG, "Error stopping generic model: " + model.getHandle());
+                    }
+                    model.clearState();
+                    model.clearCallback();
+                }
+            }
+            internalClearGlobalStateLocked();
         }
     }
 
     public ModuleProperties getModuleProperties() {
-        return moduleProperties;
+        return mModuleProperties;
     }
 
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
-        if (event == null || !(event instanceof KeyphraseRecognitionEvent)) {
-            Slog.w(TAG, "Invalid recognition event!");
+        if (event == null) {
+            Slog.w(TAG, "Null recognition event!");
+            return;
+        }
+
+        if (!(event instanceof KeyphraseRecognitionEvent) &&
+                !(event instanceof GenericRecognitionEvent)) {
+            Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase) !");
             return;
         }
 
         if (DBG) Slog.d(TAG, "onRecognition: " + event);
         synchronized (mLock) {
-            if (mActiveListener == null) {
-                Slog.w(TAG, "received onRecognition event without any listener for it");
-                return;
-            }
             switch (event.status) {
                 // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
                 case SoundTrigger.RECOGNITION_STATUS_ABORT:
@@ -325,12 +495,60 @@
                     onRecognitionFailureLocked();
                     break;
                 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
-                    onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+
+                    if (isKeyphraseRecognitionEvent(event)) {
+                        onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+                    } else {
+                        onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+                    }
+
                     break;
             }
         }
     }
 
+    private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
+        return mCurrentKeyphraseModelHandle == event.soundModelHandle;
+    }
+
+    private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+        if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
+            return;
+        }
+        ModelData model = getModelDataFor(event.soundModelHandle);
+        if (model == null) {
+            Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
+                    event.soundModelHandle);
+            return;
+        }
+
+        IRecognitionStatusCallback callback = model.getCallback();
+        if (callback == null) {
+            Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
+                    event.soundModelHandle);
+            return;
+        }
+
+        try {
+            callback.onDetected((GenericRecognitionEvent) event);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in onDetected", e);
+        }
+
+        model.setStopped();
+        RecognitionConfig config = model.getRecognitionConfig();
+        if (config == null) {
+            Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
+                    event.soundModelHandle);
+            return;
+        }
+
+        // TODO: Remove this block if the lower layer supports multiple triggers.
+        if (config.allowMultipleTriggers) {
+            startGenericRecognitionLocked(model, true /* notify */);
+        }
+    }
+
     @Override
     public void onSoundModelUpdate(SoundModelEvent event) {
         if (event == null) {
@@ -399,18 +617,25 @@
     private void onRecognitionFailureLocked() {
         Slog.w(TAG, "Recognition failure");
         try {
-            if (mActiveListener != null) {
-                mActiveListener.onError(STATUS_ERROR);
+            if (mKeyphraseListener != null) {
+                mKeyphraseListener.onError(STATUS_ERROR);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onError", e);
         } finally {
-            internalClearStateLocked();
+            internalClearKeyphraseStateLocked();
+            internalClearGlobalStateLocked();
         }
     }
 
-    private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+    private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
         Slog.i(TAG, "Recognition success");
+
+        if (mKeyphraseListener == null) {
+            Slog.w(TAG, "received onRecognition event without any listener for it");
+            return;
+        }
+
         KeyphraseRecognitionExtra[] keyphraseExtras =
                 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
         if (keyphraseExtras == null || keyphraseExtras.length == 0) {
@@ -424,14 +649,14 @@
         }
 
         try {
-            if (mActiveListener != null) {
-                mActiveListener.onDetected((KeyphraseRecognitionEvent) event);
+            if (mKeyphraseListener != null) {
+                mKeyphraseListener.onDetected((KeyphraseRecognitionEvent) event);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onDetected", e);
         }
 
-        mStarted = false;
+        mKeyphraseStarted = false;
         mRequested = mRecognitionConfig.allowMultipleTriggers;
         // TODO: Remove this block if the lower layer supports multiple triggers.
         if (mRequested) {
@@ -441,14 +666,16 @@
 
     private void onServiceDiedLocked() {
         try {
-            if (mActiveListener != null) {
-                mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
+            if (mKeyphraseListener != null) {
+                mKeyphraseListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in onError", e);
         } finally {
-            internalClearSoundModelLocked();
-            internalClearStateLocked();
+            internalClearKeyphraseSoundModelLocked();
+            internalClearKeyphraseStateLocked();
+            internalClearGenericModelStateLocked();
+            internalClearGlobalStateLocked();
             if (mModule != null) {
                 mModule.detach();
                 mModule = null;
@@ -457,14 +684,14 @@
     }
 
     private int updateRecognitionLocked(boolean notify) {
-        if (mModule == null || moduleProperties == null
-                || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) {
+        if (mModule == null || mModuleProperties == null
+                || mCurrentKeyphraseModelHandle == INVALID_VALUE || mKeyphraseListener == null) {
             // Nothing to do here.
             return STATUS_OK;
         }
 
         boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
-        if (start == mStarted) {
+        if (start == mKeyphraseStarted) {
             // No-op.
             return STATUS_OK;
         }
@@ -472,23 +699,24 @@
         // See if the recognition needs to be started.
         if (start) {
             // Start recognition.
-            int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig);
+            int status = mModule.startRecognition(mCurrentKeyphraseModelHandle,
+                    mRecognitionConfig);
             if (status != SoundTrigger.STATUS_OK) {
                 Slog.w(TAG, "startRecognition failed with " + status);
                 // Notify of error if needed.
                 if (notify) {
                     try {
-                        mActiveListener.onError(status);
+                        mKeyphraseListener.onError(status);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onError", e);
                     }
                 }
             } else {
-                mStarted = true;
+                mKeyphraseStarted = true;
                 // Notify of resume if needed.
                 if (notify) {
                     try {
-                        mActiveListener.onRecognitionResumed();
+                        mKeyphraseListener.onRecognitionResumed();
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
                     }
@@ -499,7 +727,7 @@
             // Stop recognition (only if we haven't been aborted).
             int status = STATUS_OK;
             if (!mRecognitionAborted) {
-                status = mModule.stopRecognition(mCurrentSoundModelHandle);
+                status = mModule.stopRecognition(mCurrentKeyphraseModelHandle);
             } else {
                 mRecognitionAborted = false;
             }
@@ -507,17 +735,17 @@
                 Slog.w(TAG, "stopRecognition call failed with " + status);
                 if (notify) {
                     try {
-                        mActiveListener.onError(status);
+                        mKeyphraseListener.onError(status);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onError", e);
                     }
                 }
             } else {
-                mStarted = false;
+                mKeyphraseStarted = false;
                 // Notify of pause if needed.
                 if (notify) {
                     try {
-                        mActiveListener.onRecognitionPaused();
+                        mKeyphraseListener.onRecognitionPaused();
                     } catch (RemoteException e) {
                         Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
                     }
@@ -527,14 +755,11 @@
         }
     }
 
-    private void internalClearStateLocked() {
-        mStarted = false;
-        mRequested = false;
-
-        mKeyphraseId = INVALID_VALUE;
-        mRecognitionConfig = null;
-        mActiveListener = null;
-
+    // internalClearGlobalStateLocked() gets split into two routines. Cleanup that is
+    // specific to keyphrase sound models named as internalClearKeyphraseStateLocked() and
+    // internalClearGlobalStateLocked() for global state. The global cleanup routine will be used
+    // by the cleanup happening with the generic sound models.
+    private void internalClearGlobalStateLocked() {
         // Unregister from call state changes.
         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
 
@@ -545,8 +770,27 @@
         }
     }
 
-    private void internalClearSoundModelLocked() {
-        mCurrentSoundModelHandle = INVALID_VALUE;
+    private void internalClearKeyphraseStateLocked() {
+        mKeyphraseStarted = false;
+        mRequested = false;
+
+        mKeyphraseId = INVALID_VALUE;
+        mRecognitionConfig = null;
+        mKeyphraseListener = null;
+    }
+
+    private void internalClearGenericModelStateLocked() {
+        for (UUID modelId : mGenericModelDataMap.keySet()) {
+            ModelData modelData = mGenericModelDataMap.get(modelId);
+            modelData.clearState();
+            modelData.clearCallback();
+        }
+    }
+
+    // This routine is a replacement for internalClearSoundModelLocked(). However, we
+    // should see why this should be different from internalClearKeyphraseStateLocked().
+    private void internalClearKeyphraseSoundModelLocked() {
+        mCurrentKeyphraseModelHandle = INVALID_VALUE;
         mCurrentSoundModel = null;
     }
 
@@ -577,19 +821,251 @@
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mLock) {
             pw.print("  module properties=");
-            pw.println(moduleProperties == null ? "null" : moduleProperties);
+            pw.println(mModuleProperties == null ? "null" : mModuleProperties);
             pw.print("  keyphrase ID="); pw.println(mKeyphraseId);
-            pw.print("  sound model handle="); pw.println(mCurrentSoundModelHandle);
+            pw.print("  sound model handle="); pw.println(mCurrentKeyphraseModelHandle);
             pw.print("  sound model UUID=");
             pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid);
             pw.print("  current listener=");
-            pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder());
+            pw.println(mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder());
 
             pw.print("  requested="); pw.println(mRequested);
-            pw.print("  started="); pw.println(mStarted);
+            pw.print("  started="); pw.println(mKeyphraseStarted);
             pw.print("  call active="); pw.println(mCallActive);
             pw.print("  power save mode active="); pw.println(mIsPowerSaveMode);
             pw.print("  service disabled="); pw.println(mServiceDisabled);
         }
     }
+
+    private void initializeTelephonyAndPowerStateListeners() {
+        // Get the current call state synchronously for the first recognition.
+        mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
+
+        // Register for call state changes when the first call to start recognition occurs.
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+        // Register for power saver mode changes when the first call to start recognition
+        // occurs.
+        if (mPowerSaveModeListener == null) {
+            mPowerSaveModeListener = new PowerSaveModeListener();
+            mContext.registerReceiver(mPowerSaveModeListener,
+                    new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+        }
+        mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
+    }
+
+    private ModelData getOrCreateGenericModelData(UUID modelId) {
+        ModelData modelData = mGenericModelDataMap.get(modelId);
+        if (modelData == null) {
+            modelData = new ModelData(modelId);
+            modelData.setTypeGeneric();
+            mGenericModelDataMap.put(modelId, modelData);
+        }
+        return modelData;
+    }
+
+    // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
+    // iterate through to find the right object (since we don't expect 100s of models
+    // to be stored).
+    private ModelData getModelDataFor(int modelHandle) {
+        // Fetch ModelData object corresponding to the model handle.
+        for (ModelData model : mGenericModelDataMap.values()) {
+            if (model.getHandle() == modelHandle) {
+                return model;
+            }
+        }
+        return null;
+    }
+
+    // Whether we are allowed to run any recognition at all. The conditions that let us run
+    // a recognition include: no active phone call or not being in a power save mode. Also,
+    // the native service should be enabled.
+    private boolean isRecognitionAllowed() {
+        return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
+    }
+
+    private int startGenericRecognitionLocked(ModelData modelData, boolean notify) {
+        IRecognitionStatusCallback callback = modelData.getCallback();
+        int handle = modelData.getHandle();
+        RecognitionConfig config = modelData.getRecognitionConfig();
+        if (callback == null || handle == INVALID_VALUE || config == null) {
+            // Nothing to do here.
+            Slog.w(TAG, "startGenericRecognition: Bad data passed in.");
+            return STATUS_ERROR;
+        }
+
+        if (!isRecognitionAllowed()) {
+            // Nothing to do here.
+            Slog.w(TAG, "startGenericRecognition requested but not allowed.");
+            return STATUS_OK;
+        }
+
+        int status = mModule.startRecognition(handle, config);
+        if (status != SoundTrigger.STATUS_OK) {
+            Slog.w(TAG, "startRecognition failed with " + status);
+            // Notify of error if needed.
+            if (notify) {
+                try {
+                    callback.onError(status);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onError", e);
+                }
+            }
+        } else {
+            modelData.setStarted();
+            // Notify of resume if needed.
+            if (notify) {
+                try {
+                    callback.onRecognitionResumed();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
+                }
+            }
+        }
+        return status;
+    }
+
+    private int stopGenericRecognitionLocked(ModelData modelData, boolean notify) {
+        IRecognitionStatusCallback callback = modelData.getCallback();
+
+        // Stop recognition (only if we haven't been aborted).
+        int status = mModule.stopRecognition(modelData.getHandle());
+        if (status != SoundTrigger.STATUS_OK) {
+            Slog.w(TAG, "stopRecognition call failed with " + status);
+            if (notify) {
+                try {
+                    callback.onError(status);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onError", e);
+                }
+            }
+        } else {
+            modelData.setStopped();
+            // Notify of pause if needed.
+            if (notify) {
+                try {
+                    callback.onRecognitionPaused();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
+                }
+            }
+        }
+        return status;
+    }
+
+    // Computes whether we have any recognition running at all (voice or generic). Sets
+    // the mRecognitionRunning variable with the result.
+    private boolean computeRecognitionRunning() {
+        synchronized (mLock) {
+            if (mModuleProperties == null || mModule == null) {
+                mRecognitionRunning = false;
+                return mRecognitionRunning;
+            }
+            if (mKeyphraseListener != null &&
+                    mKeyphraseStarted &&
+                    mCurrentKeyphraseModelHandle != INVALID_VALUE &&
+                    mCurrentSoundModel != null) {
+                mRecognitionRunning = true;
+                return mRecognitionRunning;
+            }
+            for (UUID modelId : mGenericModelDataMap.keySet()) {
+                ModelData modelData = mGenericModelDataMap.get(modelId);
+                if (modelData.modelStarted()) {
+                    mRecognitionRunning = true;
+                    return mRecognitionRunning;
+                }
+            }
+            mRecognitionRunning = false;
+        }
+        return mRecognitionRunning;
+    }
+
+    // This class encapsulates the callbacks, state, handles and any other information that
+    // represents a model.
+    private static class ModelData {
+        // Model not loaded (and hence not started).
+        static final int MODEL_NOTLOADED = 0;
+
+        // Loaded implies model was successfully loaded. Model not started yet.
+        static final int MODEL_LOADED = 1;
+
+        // Started implies model was successfully loaded and start was called.
+        static final int MODEL_STARTED = 2;
+
+        // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
+        private int mModelState;
+
+        private UUID mModelId;
+
+        // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
+        // to SoundModel.TYPE_UNKNOWN;
+        private int mModelType = SoundModel.TYPE_UNKNOWN;
+        private IRecognitionStatusCallback mCallback = null;
+        private SoundModel mSoundModel = null;
+        private RecognitionConfig mRecognitionConfig = null;
+
+
+        // Model handle is an integer used by the HAL as an identifier for sound
+        // models.
+        private int mModelHandle = INVALID_VALUE;
+
+        ModelData(UUID modelId) {
+            mModelId = modelId;
+        }
+
+        synchronized void setTypeGeneric() {
+            mModelType = SoundModel.TYPE_GENERIC_SOUND;
+        }
+
+        synchronized void setCallback(IRecognitionStatusCallback callback) {
+            mCallback = callback;
+        }
+
+        synchronized IRecognitionStatusCallback getCallback() {
+            return mCallback;
+        }
+
+        synchronized boolean isModelLoaded() {
+            return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED) &&
+                    mSoundModel != null;
+        }
+
+        synchronized void setStarted() {
+            mModelState = MODEL_STARTED;
+        }
+
+        synchronized void setStopped() {
+            mModelState = MODEL_LOADED;
+        }
+
+        synchronized boolean modelStarted() {
+            return mModelState == MODEL_STARTED;
+        }
+
+        synchronized void clearState() {
+            mModelState = MODEL_NOTLOADED;
+            mSoundModel = null;
+            mModelHandle = INVALID_VALUE;
+        }
+
+        synchronized void clearCallback() {
+            mCallback = null;
+        }
+
+        synchronized void setHandle(int handle) {
+            mModelHandle = handle;
+        }
+
+        synchronized void setRecognitionConfig(RecognitionConfig config) {
+            mRecognitionConfig = config;
+        }
+
+        synchronized int getHandle() {
+            return mModelHandle;
+        }
+
+        synchronized RecognitionConfig getRecognitionConfig() {
+            return mRecognitionConfig;
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 682f4a4..251f314 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -15,6 +15,7 @@
  */
 
 package com.android.server.soundtrigger;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -47,13 +48,14 @@
  * @hide
  */
 public class SoundTriggerService extends SystemService {
-    static final String TAG = "SoundTriggerService";
-    static final boolean DEBUG = false;
+    private static final String TAG = "SoundTriggerService";
+    private static final boolean DEBUG = true;
 
     final Context mContext;
     private final SoundTriggerServiceStub mServiceStub;
     private final LocalSoundTriggerService mLocalSoundTriggerService;
     private SoundTriggerDbHelper mDbHelper;
+    private SoundTriggerHelper mSoundTriggerHelper;
 
     public SoundTriggerService(Context context) {
         super(context);
@@ -71,7 +73,8 @@
     @Override
     public void onBootPhase(int phase) {
         if (PHASE_SYSTEM_SERVICES_READY == phase) {
-            mLocalSoundTriggerService.initSoundTriggerHelper();
+            initSoundTriggerHelper();
+            mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
         } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
             mDbHelper = new SoundTriggerDbHelper(mContext);
         }
@@ -85,6 +88,20 @@
     public void onSwitchUser(int userHandle) {
     }
 
+    private synchronized void initSoundTriggerHelper() {
+        if (mSoundTriggerHelper == null) {
+            mSoundTriggerHelper = new SoundTriggerHelper(mContext);
+        }
+    }
+
+    private synchronized boolean isInitialized() {
+        if (mSoundTriggerHelper == null ) {
+            Slog.e(TAG, "SoundTriggerHelper not initialized.");
+            return false;
+        }
+        return true;
+    }
+
     class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
@@ -102,19 +119,32 @@
         }
 
         @Override
-        public void startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+        public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
+                RecognitionConfig config) {
             enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
             if (DEBUG) {
                 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
             }
+            if (!isInitialized()) return STATUS_ERROR;
+
+            GenericSoundModel model = getSoundModel(parcelUuid);
+            if (model == null) {
+                Slog.e(TAG, "Null model in database for id: " + parcelUuid);
+                return STATUS_ERROR;
+            }
+
+            return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
+                    callback, config);
         }
 
         @Override
-        public void stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
+        public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
             enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
             if (DEBUG) {
                 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
             }
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
         }
 
         @Override
@@ -123,10 +153,8 @@
             if (DEBUG) {
                 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
             }
-            SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(soundModelId.getUuid());
-            if (model == null) {
-                Slog.e(TAG, "Null model in database.");
-            }
+            SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
+                    soundModelId.getUuid());
             return model;
         }
 
@@ -157,38 +185,49 @@
             mContext = context;
         }
 
-        void initSoundTriggerHelper() {
-            if (mSoundTriggerHelper == null) {
-                mSoundTriggerHelper = new SoundTriggerHelper(mContext);
-            }
+        synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
+            mSoundTriggerHelper = helper;
         }
 
         @Override
         public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
                 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
-            return mSoundTriggerHelper.startRecognition(keyphraseId, soundModel, listener,
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
                     recognitionConfig);
         }
 
         @Override
-        public int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-            return mSoundTriggerHelper.stopRecognition(keyphraseId, listener);
+        public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+            if (!isInitialized()) return STATUS_ERROR;
+            return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
         }
 
         @Override
         public void stopAllRecognitions() {
+            if (!isInitialized()) return;
             mSoundTriggerHelper.stopAllRecognitions();
         }
 
         @Override
         public ModuleProperties getModuleProperties() {
+            if (!isInitialized()) return null;
             return mSoundTriggerHelper.getModuleProperties();
         }
 
         @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!isInitialized()) return;
             mSoundTriggerHelper.dump(fd, pw, args);
         }
+
+        private synchronized boolean isInitialized() {
+            if (mSoundTriggerHelper == null ) {
+                Slog.e(TAG, "SoundTriggerHelper not initialized.");
+                return false;
+            }
+            return true;
+        }
     }
 
     private void enforceCallingPermission(String permission) {
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index bec1e4f..84cffe1 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -191,6 +191,11 @@
     }
 
     @Override
+    public File getDataDir() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public File getFilesDir() {
         throw new UnsupportedOperationException();
     }
diff --git a/tests/SoundTriggerTestApp/Android.mk b/tests/SoundTriggerTestApp/Android.mk
index 7bcab5e..c327b09 100644
--- a/tests/SoundTriggerTestApp/Android.mk
+++ b/tests/SoundTriggerTestApp/Android.mk
@@ -8,5 +8,6 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_PRIVILEGED_MODULE := true
+LOCAL_CERTIFICATE := platform
 
 include $(BUILD_PACKAGE)
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
index 40619da..a72b3dd 100644
--- a/tests/SoundTriggerTestApp/AndroidManifest.xml
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -2,16 +2,22 @@
         package="com.android.test.soundtrigger">
 
     <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
-    <application
-         android:permission="android.permission.MANAGE_SOUND_TRIGGER">
+    <application>
         <activity
             android:name="TestSoundTriggerActivity"
             android:label="SoundTrigger Test Application"
-            android:theme="@android:style/Theme.Material.Light.Voice">
+            android:theme="@android:style/Theme.Material">
+            <!--
             <intent-filter>
                 <action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
+            -->
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
         </activity>
     </application>
 </manifest>
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
index 9d2b9d9..5ecc770 100644
--- a/tests/SoundTriggerTestApp/res/layout/main.xml
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -18,6 +18,11 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:orientation="vertical"
+    >
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
     >
 
     <Button
@@ -37,7 +42,57 @@
     <Button
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:text="@string/start_recog"
+        android:onClick="onStartRecognitionButtonClicked"
+        android:padding="20dp" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/stop_recog"
+        android:onClick="onStopRecognitionButtonClicked"
+        android:padding="20dp" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:text="@string/unenroll"
         android:onClick="onUnEnrollButtonClicked"
         android:padding="20dp" />
-</LinearLayout>
\ No newline at end of file
+
+</LinearLayout>
+
+<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:padding="20dp"
+        android:orientation="vertical">
+   <RadioButton android:id="@+id/model_one"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/model_one"
+        android:onClick="onRadioButtonClicked"/>
+   <RadioButton android:id="@+id/model_two"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/model_two"
+        android:onClick="onRadioButtonClicked"/>
+   <RadioButton android:id="@+id/model_three"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/model_three"
+        android:onClick="onRadioButtonClicked"/>
+</RadioGroup>
+
+    <TextView
+        android:id="@+id/console"
+        android:gravity="left"
+        android:paddingTop="20pt"
+        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:maxLines="40"
+        android:textSize="14dp"
+        android:scrollbars = "vertical"
+        android:text="@string/none">
+     </TextView>
+</LinearLayout>
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
index 07bac2a..5f0fb1d 100644
--- a/tests/SoundTriggerTestApp/res/values/strings.xml
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -16,7 +16,13 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <string name="enroll">Enroll</string>
-    <string name="reenroll">Re-enroll</string>
-    <string name="unenroll">Un-enroll</string>
-</resources>
\ No newline at end of file
+    <string name="enroll">Load</string>
+    <string name="reenroll">Re-load</string>
+    <string name="unenroll">Un-load</string>
+    <string name="start_recog">Start</string>
+    <string name="stop_recog">Stop</string>
+    <string name="model_one">Model One</string>
+    <string name="model_two">Model Two</string>
+    <string name="model_three">Model Three</string>
+    <string name="none">Debug messages appear here:</string>
+</resources>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index 4702835..1c95c25 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.soundtrigger.SoundTriggerDetector;
 import android.media.soundtrigger.SoundTriggerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -28,6 +29,7 @@
 
 import com.android.internal.app.ISoundTriggerService;
 
+import java.lang.RuntimeException;
 import java.util.UUID;
 
 /**
@@ -56,6 +58,9 @@
      */
     public boolean addOrUpdateSoundModel(GenericSoundModel soundModel) {
         try {
+            if (soundModel == null) {
+                throw new RuntimeException("Bad sound model");
+            }
             mSoundTriggerService.updateSoundModel(soundModel);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in updateSoundModel", e);
@@ -112,4 +117,10 @@
     public void deleteSoundModelUsingManager(UUID modelId) {
             mSoundTriggerManager.deleteModel(modelId);
     }
+
+    public SoundTriggerDetector createSoundTriggerDetector(UUID modelId,
+            SoundTriggerDetector.Callback callback) {
+        return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null);
+    }
+
 }
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
index 966179b..96a6966 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
@@ -22,11 +22,17 @@
 import android.app.Activity;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.AudioFormat;
+import android.media.soundtrigger.SoundTriggerDetector;
 import android.media.soundtrigger.SoundTriggerManager;
+import android.text.Editable;
+import android.text.method.ScrollingMovementMethod;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.util.Log;
 import android.view.View;
+import android.widget.RadioButton;
+import android.widget.TextView;
 import android.widget.Toast;
 
 public class TestSoundTriggerActivity extends Activity {
@@ -35,42 +41,75 @@
 
     private SoundTriggerUtil mSoundTriggerUtil;
     private Random mRandom;
-    private UUID mModelUuid = UUID.randomUUID();
+    private UUID mModelUuid1 = UUID.randomUUID();
     private UUID mModelUuid2 = UUID.randomUUID();
+    private UUID mModelUuid3 = UUID.randomUUID();
     private UUID mVendorUuid = UUID.randomUUID();
 
+    private SoundTriggerDetector mDetector1 = null;
+    private SoundTriggerDetector mDetector2 = null;
+    private SoundTriggerDetector mDetector3 = null;
+
+    private TextView mDebugView = null;
+    private int mSelectedModelId = 1;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         if (DBG) Log.d(TAG, "onCreate");
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
+        mDebugView = (TextView) findViewById(R.id.console);
+        mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
+        mDebugView.setMovementMethod(new ScrollingMovementMethod());
         mSoundTriggerUtil = new SoundTriggerUtil(this);
         mRandom = new Random();
     }
 
+    private void postMessage(String msg) {
+        Log.i(TAG, "Posted: " + msg);
+        ((Editable) mDebugView.getText()).append(msg + "\n");
+    }
+
+    private UUID getSelectedUuid() {
+        if (mSelectedModelId == 2) return mModelUuid2;
+        if (mSelectedModelId == 3) return mModelUuid3;
+        return mModelUuid1;  // Default.
+    }
+
+    private void setDetector(SoundTriggerDetector detector) {
+        if (mSelectedModelId == 2) mDetector2 = detector;
+        if (mSelectedModelId == 3) mDetector3 = detector;
+        mDetector1 = detector;
+    }
+
+    private SoundTriggerDetector getDetector() {
+        if (mSelectedModelId == 2) return mDetector2;
+        if (mSelectedModelId == 3) return mDetector3;
+        return mDetector1;
+    }
+
     /**
      * Called when the user clicks the enroll button.
      * Performs a fresh enrollment.
      */
     public void onEnrollButtonClicked(View v) {
+        postMessage("Loading model: " + mSelectedModelId);
         // Generate a fake model to push.
         byte[] data = new byte[1024];
         mRandom.nextBytes(data);
-        GenericSoundModel model = new GenericSoundModel(mModelUuid, mVendorUuid, data);
+        UUID modelUuid = getSelectedUuid();
+        GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data);
 
         boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
         if (status) {
             Toast.makeText(
-                    this, "Successfully created sound trigger model UUID=" + mModelUuid, Toast.LENGTH_SHORT)
-                    .show();
+                    this, "Successfully created sound trigger model UUID=" + modelUuid,
+                    Toast.LENGTH_SHORT).show();
         } else {
-            Toast.makeText(this, "Failed to enroll!!!" + mModelUuid, Toast.LENGTH_SHORT).show();
+            Toast.makeText(this, "Failed to enroll!!!" + modelUuid, Toast.LENGTH_SHORT).show();
         }
 
         // Test the SoundManager API.
-        SoundTriggerManager.Model tmpModel = SoundTriggerManager.Model.create(mModelUuid2,
-                mVendorUuid, data);
-        mSoundTriggerUtil.addOrUpdateSoundModel(tmpModel);
     }
 
     /**
@@ -78,12 +117,14 @@
      * Clears the enrollment information for the user.
      */
     public void onUnEnrollButtonClicked(View v) {
-        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+        postMessage("Unloading model: " + mSelectedModelId);
+        UUID modelUuid = getSelectedUuid();
+        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
         if (soundModel == null) {
             Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
             return;
         }
-        boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid);
+        boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid1);
         if (status) {
             Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
                     Toast.LENGTH_SHORT)
@@ -91,7 +132,6 @@
         } else {
             Toast.makeText(this, "Failed to delete sound model!!!", Toast.LENGTH_SHORT).show();
         }
-        mSoundTriggerUtil.deleteSoundModelUsingManager(mModelUuid2);
     }
 
     /**
@@ -99,7 +139,9 @@
      * Uses the previously enrolled sound model and makes changes to it before pushing it back.
      */
     public void onReEnrollButtonClicked(View v) {
-        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
+        postMessage("Re-loading model: " + mSelectedModelId);
+        UUID modelUuid = getSelectedUuid();
+        GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
         if (soundModel == null) {
             Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
             return;
@@ -118,4 +160,86 @@
             Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
         }
     }
+
+    public void onStartRecognitionButtonClicked(View v) {
+        UUID modelUuid = getSelectedUuid();
+        SoundTriggerDetector detector = getDetector();
+        if (detector == null) {
+            Log.i(TAG, "Created an instance of the SoundTriggerDetector.");
+            detector = mSoundTriggerUtil.createSoundTriggerDetector(modelUuid,
+                    new DetectorCallback());
+            setDetector(detector);
+        }
+        postMessage("Triggering start recognition for model: " + mSelectedModelId);
+        if (!detector.startRecognition(
+                SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) {
+            Log.e(TAG, "Fast failure attempting to start recognition.");
+        }
+    }
+
+    public void onStopRecognitionButtonClicked(View v) {
+        SoundTriggerDetector detector = getDetector();
+        if (detector == null) {
+            Log.e(TAG, "Stop called on null detector.");
+            return;
+        }
+        postMessage("Triggering stop recognition for model: " + mSelectedModelId);
+        if (!detector.stopRecognition()) {
+            Log.e(TAG, "Fast failure attempting to stop recognition.");
+        }
+    }
+
+    public void onRadioButtonClicked(View view) {
+        // Is the button now checked?
+        boolean checked = ((RadioButton) view).isChecked();
+        // Check which radio button was clicked
+        switch(view.getId()) {
+            case R.id.model_one:
+                if (checked) mSelectedModelId = 1;
+                postMessage("Selected model one.");
+                break;
+            case R.id.model_two:
+                if (checked) mSelectedModelId = 2;
+                postMessage("Selected model two.");
+                break;
+            case R.id.model_three:
+                if (checked) mSelectedModelId = 3;
+                postMessage("Selected model three.");
+                break;
+        }
+    }
+
+    // Implementation of SoundTriggerDetector.Callback.
+    public class DetectorCallback extends SoundTriggerDetector.Callback {
+        public void onAvailabilityChanged(int status) {
+            postMessage("Availability changed to: " + status);
+        }
+
+        public void onDetected(SoundTriggerDetector.EventPayload event) {
+            postMessage("onDetected(): " + eventPayloadToString(event));
+        }
+
+        public void onError() {
+            postMessage("onError()");
+        }
+
+        public void onRecognitionPaused() {
+            postMessage("onRecognitionPaused()");
+        }
+
+        public void onRecognitionResumed() {
+            postMessage("onRecognitionResumed()");
+        }
+    }
+
+    private String eventPayloadToString(SoundTriggerDetector.EventPayload event) {
+        String result = "EventPayload(";
+        AudioFormat format =  event.getCaptureAudioFormat();
+        result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
+        byte[] triggerAudio = event.getTriggerAudio();
+        result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
+        result = result + "CaptureSession: " + event.getCaptureSession();
+        result += " )";
+        return result;
+    }
 }
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index cb82ac3..f9d35ab 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -47,6 +47,7 @@
 	proto/ProtoHelpers.cpp \
 	proto/TableProtoDeserializer.cpp \
 	proto/TableProtoSerializer.cpp \
+	split/TableSplitter.cpp \
 	unflatten/BinaryResourceParser.cpp \
 	unflatten/ResChunkPullParser.cpp \
 	util/BigBuffer.cpp \
@@ -90,6 +91,7 @@
 	link/XmlReferenceLinker_test.cpp \
 	process/SymbolTable_test.cpp \
 	proto/TableProtoSerializer_test.cpp \
+	split/TableSplitter_test.cpp \
 	util/BigBuffer_test.cpp \
 	util/Maybe_test.cpp \
 	util/StringPiece_test.cpp \
@@ -103,6 +105,7 @@
 	ResourceParser_test.cpp \
 	ResourceTable_test.cpp \
 	ResourceUtils_test.cpp \
+	SdkConstants_test.cpp \
 	StringPool_test.cpp \
 	ValueVisitor_test.cpp \
 	xml/XmlDom_test.cpp \
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 4bea129..19bd521 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -30,7 +30,8 @@
 
 namespace aapt {
 
-struct PrintVisitor : public ValueVisitor {
+class PrintVisitor : public ValueVisitor {
+public:
     using ValueVisitor::visit;
 
     void visit(Attribute* attr) override {
@@ -69,7 +70,11 @@
         for (const auto& entry : style->entries) {
             std::cout << "\n        ";
             if (entry.key.name) {
-                std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry;
+                const ResourceName& name = entry.key.name.value();
+                if (!name.package.empty()) {
+                    std::cout << name.package << ":";
+                }
+                std::cout << name.entry;
             }
 
             if (entry.key.id) {
@@ -89,7 +94,21 @@
     }
 
     void visit(Styleable* styleable) override {
-        styleable->print(&std::cout);
+        std::cout << "(styleable)";
+        for (const auto& attr : styleable->entries) {
+            std::cout << "\n        ";
+            if (attr.name) {
+                const ResourceName& name = attr.name.value();
+                if (!name.package.empty()) {
+                    std::cout << name.package << ":";
+                }
+                std::cout << name.entry;
+            }
+
+            if (attr.id) {
+                std::cout << "(" << attr.id.value() << ")";
+            }
+        }
     }
 
     void visitItem(Item* item) override {
@@ -97,7 +116,9 @@
     }
 };
 
-void Debug::printTable(ResourceTable* table) {
+void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) {
+    PrintVisitor visitor;
+
     for (auto& package : table->packages) {
         std::cout << "Package name=" << package->name;
         if (package->id) {
@@ -106,7 +127,7 @@
         std::cout << std::endl;
 
         for (const auto& type : package->types) {
-            std::cout << "  type " << type->type;
+            std::cout << "\n  type " << type->type;
             if (type->id) {
                 std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
             }
@@ -142,10 +163,12 @@
 
                 std::cout << std::endl;
 
-                PrintVisitor visitor;
                 for (const auto& value : entry->values) {
                     std::cout << "      (" << value->config << ") ";
                     value->value->accept(&visitor);
+                    if (options.showSources && !value->value->getSource().path.empty()) {
+                        std::cout << " src=" << value->value->getSource();
+                    }
                     std::cout << std::endl;
                 }
             }
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index ba05be9..fbe6477 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -25,8 +25,12 @@
 
 namespace aapt {
 
+struct DebugPrintTableOptions {
+    bool showSources = false;
+};
+
 struct Debug {
-    static void printTable(ResourceTable* table);
+    static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {});
     static void printStyleGraph(ResourceTable* table,
                                 const ResourceName& targetStyle);
     static void dumpHex(const void* data, size_t len);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 3e73be4..8d734f3 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -277,20 +277,31 @@
                                      const Source& source,
                                      const StringPiece16& path,
                                      IDiagnostics* diag) {
-    return addFileReference(name, config, source, path, resolveValueCollision, diag);
+    return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag);
 }
 
-bool ResourceTable::addFileReference(const ResourceNameRef& name,
-                                     const ConfigDescription& config,
-                                     const Source& source,
-                                     const StringPiece16& path,
-                                     std::function<int(Value*,Value*)> conflictResolver,
-                                     IDiagnostics* diag) {
+bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name,
+                                                 const ConfigDescription& config,
+                                                 const Source& source,
+                                                 const StringPiece16& path,
+                                                 io::IFile* file,
+                                                 IDiagnostics* diag) {
+    return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag);
+}
+
+bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name,
+                                         const ConfigDescription& config,
+                                         const Source& source,
+                                         const StringPiece16& path,
+                                         io::IFile* file,
+                                         const char16_t* validChars,
+                                         IDiagnostics* diag) {
     std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
             stringPool.makeRef(path));
     fileRef->setSource(source);
+    fileRef->file = file;
     return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
-                           kValidNameChars, conflictResolver, diag);
+                           kValidNameChars, resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 8ffff1f..7f5c2b8 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -23,6 +23,7 @@
 #include "ResourceValues.h"
 #include "Source.h"
 #include "StringPool.h"
+#include "io/File.h"
 
 #include <android-base/macros.h>
 #include <map>
@@ -202,17 +203,17 @@
                      IDiagnostics* diag);
 
     bool addFileReference(const ResourceNameRef& name,
-                          const ConfigDescription& config,
-                          const Source& source,
-                          const StringPiece16& path,
-                          IDiagnostics* diag);
+                              const ConfigDescription& config,
+                              const Source& source,
+                              const StringPiece16& path,
+                              IDiagnostics* diag);
 
-    bool addFileReference(const ResourceNameRef& name,
-                          const ConfigDescription& config,
-                          const Source& source,
-                          const StringPiece16& path,
-                          std::function<int(Value*,Value*)> conflictResolver,
-                          IDiagnostics* diag);
+    bool addFileReferenceAllowMangled(const ResourceNameRef& name,
+                                      const ConfigDescription& config,
+                                      const Source& source,
+                                      const StringPiece16& path,
+                                      io::IFile* file,
+                                      IDiagnostics* diag);
 
     /**
      * Same as addResource, but doesn't verify the validity of the name. This is used
@@ -280,6 +281,14 @@
 private:
     ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
 
+    bool addFileReferenceImpl(const ResourceNameRef& name,
+                              const ConfigDescription& config,
+                              const Source& source,
+                              const StringPiece16& path,
+                              io::IFile* file,
+                              const char16_t* validChars,
+                              IDiagnostics* diag);
+
     bool addResourceImpl(const ResourceNameRef& name,
                          ResourceId resId,
                          const ConfigDescription& config,
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index ab9c792..dd7ff01 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -18,6 +18,7 @@
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
+#include "io/File.h"
 #include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
@@ -190,6 +191,7 @@
 
 FileReference* FileReference::clone(StringPool* newPool) const {
     FileReference* fr = new FileReference(newPool->makeRef(*path));
+    fr->file = file;
     fr->mComment = mComment;
     fr->mSource = mSource;
     return fr;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index dc2e28e..43354ac 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -20,6 +20,7 @@
 #include "Diagnostics.h"
 #include "Resource.h"
 #include "StringPool.h"
+#include "io/File.h"
 #include "util/Maybe.h"
 
 #include <array>
@@ -226,6 +227,11 @@
 struct FileReference : public BaseItem<FileReference> {
     StringPool::Ref path;
 
+    /**
+     * A handle to the file object from which this file can be read.
+     */
+    io::IFile* file = nullptr;
+
     FileReference() = default;
     FileReference(const StringPool::Ref& path);
 
diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp
new file mode 100644
index 0000000..e81f412
--- /dev/null
+++ b/tools/aapt2/SdkConstants_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "SdkConstants.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
+    EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
+}
+
+TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) {
+    EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
+    EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
+
+    EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
+    EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
+
+    EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
+    EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 0dd8e18..5f9719e 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -424,8 +424,17 @@
 class CompileContext : public IAaptContext {
 private:
     StdErrDiagnostics mDiagnostics;
+    bool mVerbose = false;
 
 public:
+    void setVerbose(bool val) {
+        mVerbose = val;
+    }
+
+    bool verbose() override {
+        return mVerbose;
+    }
+
     IDiagnostics* getDiagnostics() override {
        return &mDiagnostics;
     }
@@ -453,8 +462,10 @@
  * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
  */
 int compile(const std::vector<StringPiece>& args) {
+    CompileContext context;
     CompileOptions options;
 
+    bool verbose = false;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
             .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
@@ -462,12 +473,13 @@
                             "(en-XA and ar-XB)", &options.pseudolocalize)
             .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
                             &options.legacyMode)
-            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+            .optionalSwitch("-v", "Enables verbose logging", &verbose);
     if (!flags.parse("aapt2 compile", args, &std::cerr)) {
         return 1;
     }
 
-    CompileContext context;
+    context.setVerbose(verbose);
+
     std::unique_ptr<IArchiveWriter> archiveWriter;
 
     std::vector<ResourcePathData> inputData;
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
index 915fae8..ad7de0a 100644
--- a/tools/aapt2/dump/Dump.cpp
+++ b/tools/aapt2/dump/Dump.cpp
@@ -103,21 +103,32 @@
         return nullptr;
     }
 
+    bool verbose() override {
+        return mVerbose;
+    }
+
+    void setVerbose(bool val) {
+        mVerbose = val;
+    }
+
 private:
     StdErrDiagnostics mDiagnostics;
+    bool mVerbose = false;
 };
 
 /**
  * Entry point for dump command.
  */
 int dump(const std::vector<StringPiece>& args) {
-    //DumpOptions options;
-    Flags flags = Flags();
+    bool verbose = false;
+    Flags flags = Flags()
+            .optionalSwitch("-v", "increase verbosity of output", &verbose);
     if (!flags.parse("aapt2 dump", args, &std::cerr)) {
         return 1;
     }
 
     DumpContext context;
+    context.setVerbose(verbose);
 
     for (const std::string& arg : flags.getArgs()) {
         tryDumpFile(&context, arg);
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 3437ac0..d83f6def 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -38,6 +38,7 @@
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
 #include "proto/ProtoSerialize.h"
+#include "split/TableSplitter.h"
 #include "unflatten/BinaryResourceParser.h"
 #include "util/Files.h"
 #include "util/StringPiece.h"
@@ -63,15 +64,14 @@
     bool noAutoVersion = false;
     bool staticLib = false;
     bool generateNonFinalIds = false;
-    bool verbose = false;
     bool outputToDirectory = false;
     bool autoAddOverlay = false;
     bool doNotCompressAnything = false;
     std::vector<std::string> extensionsToNotCompress;
     Maybe<std::u16string> privateSymbols;
     ManifestFixerOptions manifestFixerOptions;
-    IConfigFilter* configFilter = nullptr;
     std::unordered_set<std::string> products;
+    TableSplitterOptions tableSplitterOptions;
 };
 
 struct LinkContext : public IAaptContext {
@@ -80,6 +80,7 @@
     std::u16string mCompilationPackage;
     uint8_t mPackageId;
     std::unique_ptr<ISymbolTable> mSymbols;
+    bool mVerbose = false;
 
     IDiagnostics* getDiagnostics() override {
         return &mDiagnostics;
@@ -100,8 +101,376 @@
     ISymbolTable* getExternalSymbols() override {
         return mSymbols.get();
     }
+
+    bool verbose() override {
+        return mVerbose;
+    }
 };
 
+static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
+                              uint32_t compressionFlags,
+                              IArchiveWriter* writer, IAaptContext* context) {
+    std::unique_ptr<io::IData> data = file->openAsData();
+    if (!data) {
+        context->getDiagnostics()->error(DiagMessage(file->getSource())
+                                         << "failed to open file");
+        return false;
+    }
+
+    CompiledFileInputStream inputStream(data->data(), data->size());
+    if (!inputStream.CompiledFile()) {
+        context->getDiagnostics()->error(DiagMessage(file->getSource())
+                                         << "invalid compiled file header");
+        return false;
+    }
+
+    if (context->verbose()) {
+        context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
+    }
+
+    if (writer->startEntry(outPath, compressionFlags)) {
+        if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
+                               inputStream.size())) {
+            if (writer->finishEntry()) {
+                return true;
+            }
+        }
+    }
+
+    context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
+    return false;
+}
+
+static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+                       bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
+    BigBuffer buffer(1024);
+    XmlFlattenerOptions options = {};
+    options.keepRawValues = keepRawValues;
+    options.maxSdkLevel = maxSdkLevel;
+    XmlFlattener flattener(&buffer, options);
+    if (!flattener.consume(context, xmlRes)) {
+        return false;
+    }
+
+    if (context->verbose()) {
+        DiagMessage msg;
+        msg << "writing " << path << " to archive";
+        if (maxSdkLevel) {
+            msg << " maxSdkLevel=" << maxSdkLevel.value();
+        }
+        context->getDiagnostics()->note(msg);
+    }
+
+    if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+        if (writer->writeEntry(buffer)) {
+            if (writer->finishEntry()) {
+                return true;
+            }
+        }
+    }
+    context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
+    return false;
+}
+
+/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
+                                                IDiagnostics* diag) {
+    std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+    BinaryResourceParser parser(diag, table.get(), source, data, len);
+    if (!parser.parse()) {
+        return {};
+    }
+    return table;
+}*/
+
+static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
+                                                      const void* data, size_t len,
+                                                      IDiagnostics* diag) {
+    pb::ResourceTable pbTable;
+    if (!pbTable.ParseFromArray(data, len)) {
+        diag->error(DiagMessage(source) << "invalid compiled table");
+        return {};
+    }
+
+    std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
+    if (!table) {
+        return {};
+    }
+    return table;
+}
+
+/**
+ * Inflates an XML file from the source path.
+ */
+static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
+    std::ifstream fin(path, std::ifstream::binary);
+    if (!fin) {
+        diag->error(DiagMessage(path) << strerror(errno));
+        return {};
+    }
+    return xml::inflate(&fin, diag, Source(path));
+}
+
+static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
+                                                                     const void* data, size_t len,
+                                                                     IDiagnostics* diag) {
+    CompiledFileInputStream inputStream(data, len);
+    if (!inputStream.CompiledFile()) {
+        diag->error(DiagMessage(source) << "invalid compiled file header");
+        return {};
+    }
+
+    const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
+    const size_t xmlDataLen = inputStream.size();
+
+    std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
+    if (!xmlRes) {
+        return {};
+    }
+    return xmlRes;
+}
+
+static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+                                                          const void* data, size_t len,
+                                                          IDiagnostics* diag) {
+    CompiledFileInputStream inputStream(data, len);
+    const pb::CompiledFile* pbFile = inputStream.CompiledFile();
+    if (!pbFile) {
+        diag->error(DiagMessage(source) << "invalid compiled file header");
+        return {};
+    }
+
+    std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
+    if (!resFile) {
+        return {};
+    }
+    return resFile;
+}
+
+struct ResourceFileFlattenerOptions {
+    bool noAutoVersion = false;
+    bool keepRawValues = false;
+    bool doNotCompressAnything = false;
+    std::vector<std::string> extensionsToNotCompress;
+};
+
+class ResourceFileFlattener {
+public:
+    ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
+                          IAaptContext* context, proguard::KeepSet* keepSet) :
+            mOptions(options), mContext(context), mKeepSet(keepSet) {
+    }
+
+    bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
+
+private:
+    struct FileOperation {
+        io::IFile* fileToCopy;
+        std::unique_ptr<xml::XmlResource> xmlToFlatten;
+        std::string dstPath;
+    };
+
+    uint32_t getCompressionFlags(const StringPiece& str);
+
+    std::unique_ptr<xml::XmlResource> linkAndVersionXmlFile(const ResourceEntry* entry,
+                                                            const ResourceFile& fileDesc,
+                                                            io::IFile* file,
+                                                            ResourceTable* table);
+
+    ResourceFileFlattenerOptions mOptions;
+    IAaptContext* mContext;
+    proguard::KeepSet* mKeepSet;
+};
+
+uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
+    if (mOptions.doNotCompressAnything) {
+        return 0;
+    }
+
+    for (const std::string& extension : mOptions.extensionsToNotCompress) {
+        if (util::stringEndsWith<char>(str, extension)) {
+            return 0;
+        }
+    }
+    return ArchiveEntry::kCompress;
+}
+
+std::unique_ptr<xml::XmlResource> ResourceFileFlattener::linkAndVersionXmlFile(
+        const ResourceEntry* entry,
+        const ResourceFile& fileDesc,
+        io::IFile* file,
+        ResourceTable* table) {
+    const StringPiece srcPath = file->getSource().path;
+    if (mContext->verbose()) {
+        mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
+    }
+
+    std::unique_ptr<io::IData> data = file->openAsData();
+    if (!data) {
+        mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
+        return {};
+    }
+
+    std::unique_ptr<xml::XmlResource> xmlRes;
+    if (util::stringEndsWith<char>(srcPath, ".flat")) {
+        xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), data->data(), data->size(),
+                                             mContext->getDiagnostics());
+    } else {
+        xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
+                              file->getSource());
+    }
+
+    if (!xmlRes) {
+        return {};
+    }
+
+    // Copy the the file description header.
+    xmlRes->file = fileDesc;
+
+    XmlReferenceLinker xmlLinker;
+    if (!xmlLinker.consume(mContext, xmlRes.get())) {
+        return {};
+    }
+
+    if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), mKeepSet)) {
+        return {};
+    }
+
+    if (!mOptions.noAutoVersion) {
+        // Find the first SDK level used that is higher than this defined config and
+        // not superseded by a lower or equal SDK level resource.
+        for (int sdkLevel : xmlLinker.getSdkLevels()) {
+            if (sdkLevel > xmlRes->file.config.sdkVersion) {
+                if (!shouldGenerateVersionedResource(entry, xmlRes->file.config, sdkLevel)) {
+                    // If we shouldn't generate a versioned resource, stop checking.
+                    break;
+                }
+
+                ResourceFile versionedFileDesc = xmlRes->file;
+                versionedFileDesc.config.sdkVersion = sdkLevel;
+
+                if (mContext->verbose()) {
+                    mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
+                                                     << "auto-versioning resource from config '"
+                                                     << xmlRes->file.config << "' -> '"
+                                                     << versionedFileDesc.config << "'");
+                }
+
+                std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
+                        versionedFileDesc, mContext->getNameMangler()));
+
+                bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
+                                                                 versionedFileDesc.config,
+                                                                 versionedFileDesc.source,
+                                                                 genPath,
+                                                                 file,
+                                                                 mContext->getDiagnostics());
+                if (!added) {
+                    return {};
+                }
+                break;
+            }
+        }
+    }
+    return xmlRes;
+}
+
+/**
+ * Do not insert or remove any resources while executing in this function. It will
+ * corrupt the iteration order.
+ */
+bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
+    bool error = false;
+    std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
+
+    for (auto& pkg : table->packages) {
+        for (auto& type : pkg->types) {
+            // Sort by config and name, so that we get better locality in the zip file.
+            configSortedFiles.clear();
+            for (auto& entry : type->entries) {
+                // Iterate via indices because auto generated values can be inserted ahead of
+                // the value being processed.
+                for (size_t i = 0; i < entry->values.size(); i++) {
+                    ResourceConfigValue* configValue = entry->values[i].get();
+
+                    FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
+                    if (!fileRef) {
+                        continue;
+                    }
+
+                    io::IFile* file = fileRef->file;
+                    if (!file) {
+                        mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
+                                                          << "file not found");
+                        return false;
+                    }
+
+                    FileOperation fileOp;
+                    fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
+
+                    const StringPiece srcPath = file->getSource().path;
+                    if (type->type != ResourceType::kRaw &&
+                            (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
+                            util::stringEndsWith<char>(srcPath, ".xml"))) {
+                        ResourceFile fileDesc;
+                        fileDesc.config = configValue->config;
+                        fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
+                        fileDesc.source = fileRef->getSource();
+                        fileOp.xmlToFlatten = linkAndVersionXmlFile(entry.get(), fileDesc,
+                                                                    file, table);
+                        if (!fileOp.xmlToFlatten) {
+                            error = true;
+                            continue;
+                        }
+
+                    } else {
+                        fileOp.fileToCopy = file;
+                    }
+
+                    // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
+                    // we end up copying the string in the std::make_pair() method, then creating
+                    // a StringPiece16 from the copy, which would cause us to end up referencing
+                    // garbage in the map.
+                    const StringPiece16 entryName(entry->name);
+                    configSortedFiles[std::make_pair(configValue->config, entryName)] =
+                                      std::move(fileOp);
+                }
+            }
+
+            if (error) {
+                return false;
+            }
+
+            // Now flatten the sorted values.
+            for (auto& mapEntry : configSortedFiles) {
+                const ConfigDescription& config = mapEntry.first.first;
+                const FileOperation& fileOp = mapEntry.second;
+
+                if (fileOp.xmlToFlatten) {
+                    Maybe<size_t> maxSdkLevel;
+                    if (!mOptions.noAutoVersion) {
+                        maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
+                    }
+
+                    bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
+                                             mOptions.keepRawValues,
+                                             archiveWriter, mContext);
+                    if (!result) {
+                        error = true;
+                    }
+                } else {
+                    bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
+                                                    getCompressionFlags(fileOp.dstPath),
+                                                    archiveWriter, mContext);
+                    if (!result) {
+                        error = true;
+                    }
+                }
+            }
+        }
+    }
+    return !error;
+}
+
 class LinkCommand {
 public:
     LinkCommand(LinkContext* context, const LinkOptions& options) :
@@ -123,7 +492,7 @@
     std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
         AssetManagerSymbolTableBuilder builder;
         for (const std::string& path : mOptions.includePaths) {
-            if (mOptions.verbose) {
+            if (mContext->verbose()) {
                 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
             }
 
@@ -140,125 +509,6 @@
         return builder.build();
     }
 
-    std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
-        std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-        BinaryResourceParser parser(mContext, table.get(), source, data, len);
-        if (!parser.parse()) {
-            return {};
-        }
-        return table;
-    }
-
-    std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
-                                                   const void* data, size_t len) {
-        pb::ResourceTable pbTable;
-        if (!pbTable.ParseFromArray(data, len)) {
-            mContext->getDiagnostics()->error(DiagMessage(source) << "invalid compiled table");
-            return {};
-        }
-
-        std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
-                                                                      mContext->getDiagnostics());
-        if (!table) {
-            return {};
-        }
-        return table;
-    }
-
-    /**
-     * Inflates an XML file from the source path.
-     */
-    static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
-        std::ifstream fin(path, std::ifstream::binary);
-        if (!fin) {
-            diag->error(DiagMessage(path) << strerror(errno));
-            return {};
-        }
-
-        return xml::inflate(&fin, diag, Source(path));
-    }
-
-    static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
-            const Source& source,
-            const void* data, size_t len,
-            IDiagnostics* diag) {
-        CompiledFileInputStream inputStream(data, len);
-        if (!inputStream.CompiledFile()) {
-            diag->error(DiagMessage(source) << "invalid compiled file header");
-            return {};
-        }
-
-        const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
-        const size_t xmlDataLen = inputStream.size();
-
-        std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
-        if (!xmlRes) {
-            return {};
-        }
-        return xmlRes;
-    }
-
-    static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
-                                                              const void* data, size_t len,
-                                                              IDiagnostics* diag) {
-        CompiledFileInputStream inputStream(data, len);
-        const pb::CompiledFile* pbFile = inputStream.CompiledFile();
-        if (!pbFile) {
-            diag->error(DiagMessage(source) << "invalid compiled file header");
-            return {};
-        }
-
-        std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source,
-                                                                              diag);
-        if (!resFile) {
-            return {};
-        }
-        return resFile;
-    }
-
-    uint32_t getCompressionFlags(const StringPiece& str) {
-        if (mOptions.doNotCompressAnything) {
-            return 0;
-        }
-
-        for (const std::string& extension : mOptions.extensionsToNotCompress) {
-            if (util::stringEndsWith<char>(str, extension)) {
-                return 0;
-            }
-        }
-        return ArchiveEntry::kCompress;
-    }
-
-    bool copyFileToArchive(io::IFile* file, const std::string& outPath,
-                           IArchiveWriter* writer) {
-        std::unique_ptr<io::IData> data = file->openAsData();
-        if (!data) {
-            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
-                                             << "failed to open file");
-            return false;
-        }
-
-        CompiledFileInputStream inputStream(data->data(), data->size());
-        if (!inputStream.CompiledFile()) {
-            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
-                                              << "invalid compiled file header");
-            return false;
-        }
-
-        if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
-            if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
-                                   inputStream.size())) {
-                if (writer->finishEntry()) {
-                    return true;
-                }
-            }
-        }
-
-        mContext->getDiagnostics()->error(
-                DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
-        return false;
-    }
-
     Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
         // Make sure the first element is <manifest> with package attribute.
         if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
@@ -349,28 +599,6 @@
         return false;
     }
 
-    bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
-                    IArchiveWriter* writer) {
-        BigBuffer buffer(1024);
-        XmlFlattenerOptions options = {};
-        options.keepRawValues = mOptions.staticLib;
-        options.maxSdkLevel = maxSdkLevel;
-        XmlFlattener flattener(&buffer, options);
-        if (!flattener.consume(mContext, xmlRes)) {
-            return false;
-        }
-
-        if (writer->startEntry(path, ArchiveEntry::kCompress)) {
-            if (writer->writeEntry(buffer)) {
-                if (writer->finishEntry()) {
-                    return true;
-                }
-            }
-        }
-        mContext->getDiagnostics()->error(
-                DiagMessage() << "failed to write " << path << " to archive");
-        return false;
-    }
 
     bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
                        const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
@@ -456,7 +684,7 @@
     }
 
     bool mergeResourceTable(io::IFile* file, bool override) {
-        if (mOptions.verbose) {
+        if (mContext->verbose()) {
             mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
         }
 
@@ -467,8 +695,9 @@
             return false;
         }
 
-        std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), data->data(),
-                                                               data->size());
+        std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
+                                                               data->data(), data->size(),
+                                                               mContext->getDiagnostics());
         if (!table) {
             return false;
         }
@@ -483,7 +712,7 @@
     }
 
     bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
-        if (mOptions.verbose) {
+        if (mContext->verbose()) {
             mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
         }
 
@@ -628,10 +857,9 @@
 
         TableMergerOptions tableMergerOptions;
         tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
-        tableMergerOptions.filter = mOptions.configFilter;
         mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
 
-        if (mOptions.verbose) {
+        if (mContext->verbose()) {
             mContext->getDiagnostics()->note(
                     DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' "
                                   << "with package ID " << std::hex << (int) mContext->mPackageId);
@@ -692,6 +920,15 @@
                 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
                 return 1;
             }
+
+            // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
+            // level.
+            TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
+            if (!tableSplitter.verifySplitConstraints(mContext)) {
+                return 1;
+            }
+
+            tableSplitter.splitTable(&mFinalTable);
         }
 
         proguard::KeepSet proguardKeepSet;
@@ -728,8 +965,10 @@
                     }
                 }
 
-                if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
-                                archiveWriter.get())) {
+                const bool keepRawValues = mOptions.staticLib;
+                bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
+                                         keepRawValues, archiveWriter.get(), mContext);
+                if (!result) {
                     error = true;
                 }
             } else {
@@ -742,113 +981,14 @@
             return 1;
         }
 
-        for (auto& mergeEntry : mTableMerger->getFilesToMerge()) {
-            const ResourceKeyRef& key = mergeEntry.first;
-            const FileToMerge& fileToMerge = mergeEntry.second;
+        ResourceFileFlattenerOptions fileFlattenerOptions;
+        fileFlattenerOptions.keepRawValues = mOptions.staticLib;
+        fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
+        fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
+        fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
+        ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
 
-            const StringPiece path = fileToMerge.file->getSource().path;
-
-            if (key.name.type != ResourceType::kRaw &&
-                    (util::stringEndsWith<char>(path, ".xml.flat") ||
-                    util::stringEndsWith<char>(path, ".xml"))) {
-                if (mOptions.verbose) {
-                    mContext->getDiagnostics()->note(DiagMessage() << "linking " << path);
-                }
-
-                io::IFile* file = fileToMerge.file;
-                std::unique_ptr<io::IData> data = file->openAsData();
-                if (!data) {
-                    mContext->getDiagnostics()->error(DiagMessage(file->getSource())
-                                                     << "failed to open file");
-                    return 1;
-                }
-
-                std::unique_ptr<xml::XmlResource> xmlRes;
-                if (util::stringEndsWith<char>(path, ".flat")) {
-                    xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
-                                                         data->data(), data->size(),
-                                                         mContext->getDiagnostics());
-                } else {
-                    xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
-                                          file->getSource());
-                }
-
-                if (!xmlRes) {
-                    return 1;
-                }
-
-                // Create the file description header.
-                xmlRes->file = ResourceFile{
-                        key.name.toResourceName(),
-                        key.config,
-                        fileToMerge.originalSource,
-                };
-
-                XmlReferenceLinker xmlLinker;
-                if (xmlLinker.consume(mContext, xmlRes.get())) {
-                    if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
-                                                        &proguardKeepSet)) {
-                        error = true;
-                    }
-
-                    Maybe<size_t> maxSdkLevel;
-                    if (!mOptions.noAutoVersion) {
-                        maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
-                    }
-
-                    if (!flattenXml(xmlRes.get(), fileToMerge.dstPath, maxSdkLevel,
-                                    archiveWriter.get())) {
-                        error = true;
-                    }
-
-                    if (!mOptions.noAutoVersion) {
-                        Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
-                                xmlRes->file.name);
-                        for (int sdkLevel : xmlLinker.getSdkLevels()) {
-                            if (sdkLevel > xmlRes->file.config.sdkVersion &&
-                                    shouldGenerateVersionedResource(result.value().entry,
-                                                                    xmlRes->file.config,
-                                                                    sdkLevel)) {
-                                xmlRes->file.config.sdkVersion = sdkLevel;
-
-                                std::string genResourcePath = ResourceUtils::buildResourceFileName(
-                                        xmlRes->file, mContext->getNameMangler());
-
-                                bool added = mFinalTable.addFileReference(
-                                        xmlRes->file.name,
-                                        xmlRes->file.config,
-                                        xmlRes->file.source,
-                                        util::utf8ToUtf16(genResourcePath),
-                                        mContext->getDiagnostics());
-                                if (!added) {
-                                    error = true;
-                                    continue;
-                                }
-
-                                if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel,
-                                                archiveWriter.get())) {
-                                    error = true;
-                                }
-                            }
-                        }
-                    }
-
-                } else {
-                    error = true;
-                }
-            } else {
-                if (mOptions.verbose) {
-                    mContext->getDiagnostics()->note(DiagMessage() << "copying " << path);
-                }
-
-                if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
-                                       archiveWriter.get())) {
-                    error = true;
-                }
-            }
-        }
-
-        if (error) {
+        if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
             mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
             return 1;
         }
@@ -912,8 +1052,10 @@
             }
         }
 
-        if (mOptions.verbose) {
-            Debug::printTable(&mFinalTable);
+        if (mContext->verbose()) {
+            DebugPrintTableOptions debugPrintTableOptions;
+            debugPrintTableOptions.showSources = true;
+            Debug::printTable(&mFinalTable, debugPrintTableOptions);
         }
         return 0;
     }
@@ -934,6 +1076,7 @@
 };
 
 int link(const std::vector<StringPiece>& args) {
+    LinkContext context;
     LinkOptions options;
     Maybe<std::string> privateSymbolsPackage;
     Maybe<std::string> minSdkVersion, targetSdkVersion;
@@ -942,6 +1085,7 @@
     Maybe<std::string> customJavaPackage;
     std::vector<std::string> extraJavaPackages;
     Maybe<std::string> configs;
+    Maybe<std::string> preferredDensity;
     Maybe<std::string> productList;
     bool legacyXFlag = false;
     bool requireLocalization = false;
@@ -966,6 +1110,9 @@
                             &requireLocalization)
             .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
                                 "is all configurations", &configs)
+            .optionalFlag("--preferred-density",
+                          "Selects the closest matching density and strips out all others.",
+                          &preferredDensity)
             .optionalFlag("--product", "Comma separated list of product names to keep",
                           &productList)
             .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
@@ -1001,14 +1148,12 @@
                           &renameInstrumentationTargetPackage)
             .optionalFlagList("-0", "File extensions not to compress",
                               &options.extensionsToNotCompress)
-            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
+            .optionalSwitch("-v", "Enables verbose logging", &context.mVerbose);
 
     if (!flags.parse("aapt2 link", args, &std::cerr)) {
         return 1;
     }
 
-    LinkContext context;
-
     if (privateSymbolsPackage) {
         options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
     }
@@ -1082,7 +1227,29 @@
             }
         }
 
-        options.configFilter = &filter;
+        options.tableSplitterOptions.configFilter = &filter;
+    }
+
+    if (preferredDensity) {
+        ConfigDescription preferredDensityConfig;
+        if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
+            context.getDiagnostics()->error(DiagMessage() << "invalid density '"
+                                            << preferredDensity.value()
+                                            << "' for --preferred-density option");
+            return 1;
+        }
+
+        // Clear the version that can be automatically added.
+        preferredDensityConfig.sdkVersion = 0;
+
+        if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
+                != ConfigDescription::CONFIG_DENSITY) {
+            context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
+                                            << preferredDensity.value() << "'. "
+                                            << "Preferred density must only be a density value");
+            return 1;
+        }
+        options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
     }
 
     LinkCommand cmd(&context, options);
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 2ecd5b0..5f11745 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -98,8 +98,7 @@
                 return false;
             }
 
-            mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
-                    f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
+            newFile->file = f;
             return true;
         };
 
@@ -198,10 +197,6 @@
             for (auto& srcValue : srcEntry->values) {
                 ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
                                                                     srcValue->product);
-
-                const bool stripConfig = mOptions.filter ?
-                        !mOptions.filter->match(srcValue->config) : false;
-
                 if (dstValue) {
                     const int collisionResult = ResourceTable::resolveValueCollision(
                             dstValue->value.get(), srcValue->value.get());
@@ -227,10 +222,6 @@
 
                 }
 
-                if (stripConfig) {
-                    continue;
-                }
-
                 if (!dstValue) {
                     // Force create the entry if we didn't have it.
                     dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
@@ -286,6 +277,7 @@
     std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
             table.stringPool.makeRef(path));
     fileRef->setSource(fileDesc.source);
+    fileRef->file = file;
 
     ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
     pkg->findOrCreateType(fileDesc.name.type)
@@ -293,15 +285,8 @@
             ->findOrCreateValue(fileDesc.config, {})
             ->value = std::move(fileRef);
 
-    auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
-                       FileReference* newFile, FileReference* oldFile) -> bool {
-        mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
-                file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
-        return true;
-    };
-
     return doMerge(file->getSource(), &table, pkg,
-                   false /* mangle */, overlay /* overlay */, true /* allow new */, callback);
+                   false /* mangle */, overlay /* overlay */, true /* allow new */, {});
 }
 
 bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 4539679..b3c22dd 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -30,33 +30,11 @@
 
 namespace aapt {
 
-struct FileToMerge {
-    /**
-     * The compiled file from which to read the data.
-     */
-    io::IFile* file;
-
-    /**
-     * Where the original, uncompiled file came from.
-     */
-    Source originalSource;
-
-    /**
-     * The destination path within the APK/archive.
-     */
-    std::string dstPath;
-};
-
 struct TableMergerOptions {
     /**
      * If true, resources in overlays can be added without previously having existed.
      */
     bool autoAddOverlay = false;
-
-    /**
-     * A filter that removes resources whose configurations don't match.
-     */
-    IConfigFilter* filter = nullptr;
 };
 
 /**
@@ -81,10 +59,6 @@
      */
     TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options);
 
-    const std::map<ResourceKeyRef, FileToMerge>& getFilesToMerge() {
-        return mFilesToMerge;
-    }
-
     const std::set<std::u16string>& getMergedPackages() const {
         return mMergedPackages;
     }
@@ -119,8 +93,7 @@
 private:
     using FileMergeCallback = std::function<bool(const ResourceNameRef&,
                                                  const ConfigDescription& config,
-                                                 FileReference*,
-                                                 FileReference*)>;
+                                                 FileReference*, FileReference*)>;
 
     IAaptContext* mContext;
     ResourceTable* mMasterTable;
@@ -128,7 +101,6 @@
     ResourceTablePackage* mMasterPackage;
 
     std::set<std::u16string> mMergedPackages;
-    std::map<ResourceKeyRef, FileToMerge> mFilesToMerge;
 
     bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay);
 
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 45c8c98..4a80d3f 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -97,16 +97,6 @@
                                                                  test::parseConfigOrDie("hdpi-v4"));
     ASSERT_NE(nullptr, file);
     EXPECT_EQ(std::u16string(u"res/layout-hdpi-v4/main.xml"), *file->path);
-
-    ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
-    ResourceKeyRef key = { name, test::parseConfigOrDie("hdpi-v4") };
-
-    auto iter = merger.getFilesToMerge().find(key);
-    ASSERT_NE(merger.getFilesToMerge().end(), iter);
-
-    const FileToMerge& actualFileToMerge = iter->second;
-    EXPECT_EQ(&testFile, actualFileToMerge.file);
-    EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), actualFileToMerge.dstPath);
 }
 
 TEST_F(TableMergerTest, MergeFileOverlay) {
@@ -122,14 +112,6 @@
 
     ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
     ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
-
-    ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/foo");
-    ResourceKeyRef key = { name, ConfigDescription{} };
-    auto iter = merger.getFilesToMerge().find(key);
-    ASSERT_NE(merger.getFilesToMerge().end(), iter);
-
-    const FileToMerge& actualFileToMerge = iter->second;
-    EXPECT_EQ(&fileB, actualFileToMerge.file);
 }
 
 TEST_F(TableMergerTest, MergeFileReferences) {
@@ -157,15 +139,6 @@
     f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file");
     ASSERT_NE(f, nullptr);
     EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
-
-    ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/com.app.b$file");
-    ResourceKeyRef key = { name, ConfigDescription{} };
-    auto iter = merger.getFilesToMerge().find(key);
-    ASSERT_NE(merger.getFilesToMerge().end(), iter);
-
-    const FileToMerge& actualFileToMerge = iter->second;
-    EXPECT_EQ(Source("res/xml/file.xml"), actualFileToMerge.file->getSource());
-    EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), actualFileToMerge.dstPath);
 }
 
 TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
@@ -244,36 +217,4 @@
     ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
 }
 
-TEST_F(TableMergerTest, MergeAndStripResourcesNotMatchingFilter) {
-    ResourceTable finalTable;
-    TableMergerOptions options;
-    options.autoAddOverlay = false;
-
-    AxisConfigFilter filter;
-    filter.addConfig(test::parseConfigOrDie("en"));
-    options.filter = &filter;
-
-    test::TestFile fileA("res/layout-en/main.xml"), fileB("res/layout-fr-rFR/main.xml");
-    const ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
-    const ConfigDescription configEn = test::parseConfigOrDie("en");
-    const ConfigDescription configFr = test::parseConfigOrDie("fr-rFR");
-
-    TableMerger merger(mContext.get(), &finalTable, options);
-    ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configEn }, &fileA));
-    ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configFr }, &fileB));
-
-    EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(&finalTable,
-                                                              u"@com.app.a:layout/main",
-                                                              configEn));
-    EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(&finalTable,
-                                                              u"@com.app.a:layout/main",
-                                                              configFr));
-
-    EXPECT_NE(merger.getFilesToMerge().end(),
-              merger.getFilesToMerge().find(ResourceKeyRef(name, configEn)));
-
-    EXPECT_EQ(merger.getFilesToMerge().end(),
-              merger.getFilesToMerge().find(ResourceKeyRef(name, configFr)));
-}
-
 } // namespace aapt
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index a2528d2..3a88044 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -40,6 +40,7 @@
     virtual StringPiece16 getCompilationPackage() = 0;
     virtual uint8_t getPackageId() = 0;
     virtual NameMangler* getNameMangler() = 0;
+    virtual bool verbose() = 0;
 };
 
 struct IResourceTableConsumer {
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
new file mode 100644
index 0000000..0f7649b
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "split/TableSplitter.h"
+
+#include <map>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+namespace aapt {
+
+using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
+using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
+
+static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
+    ConfigDescription withoutDensity = config;
+    withoutDensity.density = 0;
+    return withoutDensity;
+}
+
+/**
+ * Selects values that match exactly the constraints given.
+ */
+class SplitValueSelector {
+public:
+    SplitValueSelector(const SplitConstraints& constraints) {
+        for (const ConfigDescription& config : constraints.configs) {
+            if (config.density == 0) {
+                mDensityIndependentConfigs.insert(config);
+            } else {
+                mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density;
+            }
+        }
+    }
+
+    std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
+                                                   ConfigClaimedMap* claimedValues) {
+        std::vector<ResourceConfigValue*> selected;
+
+        // Select the regular values.
+        for (auto& entry : *claimedValues) {
+            // Check if the entry has a density.
+            ResourceConfigValue* configValue = entry.first;
+            if (configValue->config.density == 0 && !entry.second) {
+                // This is still available.
+                if (mDensityIndependentConfigs.find(configValue->config) !=
+                        mDensityIndependentConfigs.end()) {
+                    selected.push_back(configValue);
+
+                    // Mark the entry as taken.
+                    entry.second = true;
+                }
+            }
+        }
+
+        // Now examine the densities
+        for (auto& entry : densityGroups) {
+            // We do not care if the value is claimed, since density values can be
+            // in multiple splits.
+            const ConfigDescription& config = entry.first;
+            const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
+
+            auto densityValueIter = mDensityDependentConfigToDensityMap.find(config);
+            if (densityValueIter != mDensityDependentConfigToDensityMap.end()) {
+                // Select the best one!
+                ConfigDescription targetDensity = config;
+                targetDensity.density = densityValueIter->second;
+
+                ResourceConfigValue* bestValue = nullptr;
+                for (ResourceConfigValue* thisValue : relatedValues) {
+                    if (!bestValue ||
+                            thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
+                        bestValue = thisValue;
+                    }
+
+                    // When we select one of these, they are all claimed such that the base
+                    // doesn't include any anymore.
+                    (*claimedValues)[thisValue] = true;
+                }
+                assert(bestValue);
+                selected.push_back(bestValue);
+            }
+        }
+        return selected;
+    }
+
+private:
+    std::set<ConfigDescription> mDensityIndependentConfigs;
+    std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
+};
+
+/**
+ * Marking non-preferred densities as claimed will make sure the base doesn't include them,
+ * leaving only the preferred density behind.
+ */
+static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity,
+                                               const ConfigDensityGroups& densityGroups,
+                                               ConfigClaimedMap* configClaimedMap) {
+    for (auto& entry : densityGroups) {
+        const ConfigDescription& config = entry.first;
+        const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
+
+        ConfigDescription targetDensity = config;
+        targetDensity.density = preferredDensity;
+        ResourceConfigValue* bestValue = nullptr;
+        for (ResourceConfigValue* thisValue : relatedValues) {
+            if (!bestValue) {
+                bestValue = thisValue;
+            } else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
+                // Claim the previous value so that it is not included in the base.
+                (*configClaimedMap)[bestValue] = true;
+                bestValue = thisValue;
+            } else {
+                // Claim this value so that it is not included in the base.
+                (*configClaimedMap)[thisValue] = true;
+            }
+        }
+        assert(bestValue);
+    }
+}
+
+bool TableSplitter::verifySplitConstraints(IAaptContext* context) {
+    bool error = false;
+    for (size_t i = 0; i < mSplitConstraints.size(); i++) {
+        for (size_t j = i + 1; j < mSplitConstraints.size(); j++) {
+            for (const ConfigDescription& config : mSplitConstraints[i].configs) {
+                if (mSplitConstraints[j].configs.find(config) !=
+                        mSplitConstraints[j].configs.end()) {
+                    context->getDiagnostics()->error(DiagMessage() << "config '" << config
+                                                     << "' appears in multiple splits, "
+                                                     << "target split ambiguous");
+                    error = true;
+                }
+            }
+        }
+    }
+    return !error;
+}
+
+void TableSplitter::splitTable(ResourceTable* originalTable) {
+    const size_t splitCount = mSplitConstraints.size();
+    for (auto& pkg : originalTable->packages) {
+        // Initialize all packages for splits.
+        for (size_t idx = 0; idx < splitCount; idx++) {
+            ResourceTable* splitTable = mSplits[idx].get();
+            splitTable->createPackage(pkg->name, pkg->id);
+        }
+
+        for (auto& type : pkg->types) {
+            if (type->type == ResourceType::kMipmap) {
+                // Always keep mipmaps.
+                continue;
+            }
+
+            for (auto& entry : type->entries) {
+                if (mConfigFilter) {
+                    // First eliminate any resource that we definitely don't want.
+                    for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+                        if (!mConfigFilter->match(configValue->config)) {
+                            // null out the entry. We will clean up and remove nulls at the end
+                            // for performance reasons.
+                            configValue.reset();
+                        }
+                    }
+                }
+
+                // Organize the values into two separate buckets. Those that are density-dependent
+                // and those that are density-independent.
+                // One density technically matches all density, it's just that some densities
+                // match better. So we need to be aware of the full set of densities to make this
+                // decision.
+                ConfigDensityGroups densityGroups;
+                ConfigClaimedMap configClaimedMap;
+                for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+                    if (configValue) {
+                        configClaimedMap[configValue.get()] = false;
+
+                        if (configValue->config.density != 0) {
+                            // Create a bucket for this density-dependent config.
+                            densityGroups[copyWithoutDensity(configValue->config)]
+                                          .push_back(configValue.get());
+                        }
+                    }
+                }
+
+                // First we check all the splits. If it doesn't match one of the splits, we
+                // leave it in the base.
+                for (size_t idx = 0; idx < splitCount; idx++) {
+                    const SplitConstraints& splitConstraint = mSplitConstraints[idx];
+                    ResourceTable* splitTable = mSplits[idx].get();
+
+                    // Select the values we want from this entry for this split.
+                    SplitValueSelector selector(splitConstraint);
+                    std::vector<ResourceConfigValue*> selectedValues =
+                            selector.selectValues(densityGroups, &configClaimedMap);
+
+                    // No need to do any work if we selected nothing.
+                    if (!selectedValues.empty()) {
+                        // Create the same resource structure in the split. We do this lazily
+                        // because we might not have actual values for each type/entry.
+                        ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name);
+                        ResourceTableType* splitType = splitPkg->findOrCreateType(type->type);
+                        if (!splitType->id) {
+                            splitType->id = type->id;
+                            splitType->symbolStatus = type->symbolStatus;
+                        }
+
+                        ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
+                        if (!splitEntry->id) {
+                            splitEntry->id = entry->id;
+                            splitEntry->symbolStatus = entry->symbolStatus;
+                        }
+
+                        // Copy the selected values into the new Split Entry.
+                        for (ResourceConfigValue* configValue : selectedValues) {
+                            ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue(
+                                    configValue->config, configValue->product);
+                            newConfigValue->value = std::unique_ptr<Value>(
+                                    configValue->value->clone(&splitTable->stringPool));
+                        }
+                    }
+                }
+
+                if (mPreferredDensity) {
+                    markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
+                                                       densityGroups,
+                                                       &configClaimedMap);
+                }
+
+                // All splits are handled, now check to see what wasn't claimed and remove
+                // whatever exists in other splits.
+                for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
+                    if (configValue && configClaimedMap[configValue.get()]) {
+                        // Claimed, remove from base.
+                        configValue.reset();
+                    }
+                }
+
+                // Now erase all nullptrs.
+                entry->values.erase(
+                        std::remove(entry->values.begin(), entry->values.end(), nullptr),
+                        entry->values.end());
+            }
+        }
+    }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h
new file mode 100644
index 0000000..15e0764
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef AAPT_SPLIT_TABLESPLITTER_H
+#define AAPT_SPLIT_TABLESPLITTER_H
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "filter/ConfigFilter.h"
+#include "process/IResourceTableConsumer.h"
+
+#include <android-base/macros.h>
+#include <set>
+#include <vector>
+
+namespace aapt {
+
+struct SplitConstraints {
+    std::set<ConfigDescription> configs;
+};
+
+struct TableSplitterOptions {
+    /**
+     * The preferred density to keep in the table, stripping out all others.
+     */
+    Maybe<uint16_t> preferredDensity;
+
+    /**
+     * Configuration filter that determines which resource configuration values end up in
+     * the final table.
+     */
+    IConfigFilter* configFilter = nullptr;
+};
+
+class TableSplitter {
+public:
+    TableSplitter(const std::vector<SplitConstraints>& splits,
+                  const TableSplitterOptions& options) :
+            mSplitConstraints(splits), mPreferredDensity(options.preferredDensity),
+            mConfigFilter(options.configFilter) {
+        for (size_t i = 0; i < mSplitConstraints.size(); i++) {
+            mSplits.push_back(util::make_unique<ResourceTable>());
+        }
+    }
+
+    bool verifySplitConstraints(IAaptContext* context);
+
+    void splitTable(ResourceTable* originalTable);
+
+    const std::vector<std::unique_ptr<ResourceTable>>& getSplits() {
+        return mSplits;
+    }
+
+private:
+    std::vector<SplitConstraints> mSplitConstraints;
+    std::vector<std::unique_ptr<ResourceTable>> mSplits;
+    Maybe<uint16_t> mPreferredDensity;
+    IConfigFilter* mConfigFilter;
+
+    DISALLOW_COPY_AND_ASSIGN(TableSplitter);
+};
+
+}
+
+#endif /* AAPT_SPLIT_TABLESPLITTER_H */
diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp
new file mode 100644
index 0000000..74ca32e
--- /dev/null
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "split/TableSplitter.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(TableSplitterTest, NoSplitPreferredDensity) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addFileReference(u"@android:drawable/icon", u"res/drawable-mdpi/icon.png",
+                              test::parseConfigOrDie("mdpi"))
+            .addFileReference(u"@android:drawable/icon", u"res/drawable-hdpi/icon.png",
+                              test::parseConfigOrDie("hdpi"))
+            .addFileReference(u"@android:drawable/icon", u"res/drawable-xhdpi/icon.png",
+                              test::parseConfigOrDie("xhdpi"))
+            .addFileReference(u"@android:drawable/icon", u"res/drawable-xxhdpi/icon.png",
+                              test::parseConfigOrDie("xxhdpi"))
+            .addSimple(u"@android:string/one", {})
+            .build();
+
+    TableSplitterOptions options;
+    options.preferredDensity = ConfigDescription::DENSITY_XHIGH;
+    TableSplitter splitter({}, options);
+    splitter.splitTable(table.get());
+
+    EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+                                                              u"@android:drawable/icon",
+                                                              test::parseConfigOrDie("mdpi")));
+    EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+                                                              u"@android:drawable/icon",
+                                                              test::parseConfigOrDie("hdpi")));
+    EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(),
+                                                              u"@android:drawable/icon",
+                                                              test::parseConfigOrDie("xhdpi")));
+    EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
+                                                              u"@android:drawable/icon",
+                                                              test::parseConfigOrDie("xxhdpi")));
+    EXPECT_NE(nullptr, test::getValue<Id>(table.get(), u"@android:string/one"));
+}
+
+TEST(TableSplitterTest, SplitTableByConfigAndDensity) {
+    ResourceTable table;
+
+    const ResourceName foo = test::parseNameOrDie(u"@android:string/foo");
+    ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-hdpi"), {},
+                                  util::make_unique<Id>(),
+                                  test::getDiagnostics()));
+    ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xhdpi"), {},
+                                  util::make_unique<Id>(),
+                                  test::getDiagnostics()));
+    ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xxhdpi"), {},
+                                  util::make_unique<Id>(),
+                                  test::getDiagnostics()));
+
+    std::vector<SplitConstraints> constraints;
+    constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-mdpi") } });
+    constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-xhdpi") } });
+
+    TableSplitter splitter(constraints, TableSplitterOptions{});
+    splitter.splitTable(&table);
+
+    ASSERT_EQ(2u, splitter.getSplits().size());
+
+    ResourceTable* splitOne = splitter.getSplits()[0].get();
+    ResourceTable* splitTwo = splitter.getSplits()[1].get();
+
+    // Since a split was defined, all densities should be gone from base.
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-hdpi")));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-xhdpi")));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-xxhdpi")));
+
+    EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-hdpi")));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-xhdpi")));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-xxhdpi")));
+
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-hdpi")));
+    EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-xhdpi")));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
+                                                   test::parseConfigOrDie("land-xxhdpi")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 555a539..e540cd7 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -71,6 +71,10 @@
         assert(mNameMangler && "test name mangler not set");
         return mNameMangler.get();
     }
+
+    bool verbose() override {
+        return false;
+    }
 };
 
 class ContextBuilder {
diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
index 5bb3e3e..3681f2a 100644
--- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
+++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <component name="InspectionProjectProfileManager">
   <profile version="1.0" is_locked="false">
     <option name="myName" value="Project Default" />
@@ -8,6 +7,15 @@
       <option name="CHECK_TRY_CATCH_SECTION" value="true" />
       <option name="CHECK_METHOD_BODY" value="true" />
     </inspection_tool>
+    <inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
+      <option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
+    </inspection_tool>
     <inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" />
+      <option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="false" />
+      <option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" />
+    </inspection_tool>
   </profile>
 </component>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml
index b474bdc..44b47f2 100644
--- a/tools/layoutlib/.idea/misc.xml
+++ b/tools/layoutlib/.idea/misc.xml
@@ -37,7 +37,7 @@
       </value>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/out" />
   </component>
 </project>
\ No newline at end of file
diff --git a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
index 4f0eb8d..b402849 100644
--- a/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
+++ b/tools/layoutlib/.idea/runConfigurations/Bridge_quick.xml
@@ -26,4 +26,4 @@
     <ConfigurationWrapper RunnerId="Run" />
     <method />
   </configuration>
-</component>
\ No newline at end of file
+</component>
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 53bfc15..c2ad9ef 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -16,7 +16,7 @@
 LOCAL_PATH := $(my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_JAVACFLAGS := -source 6 -target 6
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 
 #
 # Define rules to build temp_layoutlib.jar, which contains a subset of
diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk
index 0dbdd56..16e5913 100644
--- a/tools/layoutlib/bridge/Android.mk
+++ b/tools/layoutlib/bridge/Android.mk
@@ -18,8 +18,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_JAVA_RESOURCE_DIRS := resources
-LOCAL_JAVACFLAGS := -source 6 -target 6
-
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
 
 LOCAL_JAVA_LIBRARIES := \
 	layoutlib_api-prebuilt \
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
index 0e39243..fe46480 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
@@ -49,6 +49,7 @@
 import java.io.InputStream;
 import java.util.Iterator;
 
+@SuppressWarnings("deprecation")
 public final class BridgeResources extends Resources {
 
     private BridgeContext mContext;
@@ -70,6 +71,7 @@
 
         @Override
         public boolean markSupported() {
+            //noinspection SimplifiableIfStatement
             if (mFakeMarkSupport) {
                 // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
                 return true;
@@ -595,7 +597,6 @@
         if (value != null) {
             ResourceValue resValue = value.getSecond();
 
-            assert resValue != null;
             if (resValue != null) {
                 String v = resValue.getValue();
                 if (v != null) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index 0737682..e3bb3e3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -17,9 +17,14 @@
 package android.graphics;
 
 import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.RenderAction;
 import com.android.resources.Density;
+import com.android.resources.ResourceType;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.annotation.Nullable;
@@ -38,6 +43,7 @@
 import java.util.Set;
 
 import javax.imageio.ImageIO;
+import libcore.util.NativeAllocationRegistry_Delegate;
 
 /**
  * Delegate implementing the native methods of android.graphics.Bitmap
@@ -62,12 +68,13 @@
     // ---- delegate manager ----
     private static final DelegateManager<Bitmap_Delegate> sManager =
             new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class);
+    private static long sFinalizer = -1;
 
     // ---- delegate helper data ----
 
     // ---- delegate data ----
     private final Config mConfig;
-    private BufferedImage mImage;
+    private final BufferedImage mImage;
     private boolean mHasAlpha = true;
     private boolean mHasMipMap = false;      // TODO: check the default.
     private boolean mIsPremultiplied = true;
@@ -114,10 +121,25 @@
      * @see Bitmap#isMutable()
      * @see Bitmap#getDensity()
      */
-    public static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
+    private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
             Density density) throws IOException {
         // create a delegate with the content of the file.
-        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
+        BufferedImage image = ImageIO.read(input);
+        if (image == null && input.exists()) {
+            // There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
+            // Replace with a broken image icon.
+            BridgeContext currentContext = RenderAction.getCurrentContext();
+            if (currentContext != null) {
+                RenderResources resources = currentContext.getRenderResources();
+                ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
+                        "ic_menu_report_image");
+                File brokenFile = new File(broken.getValue());
+                if (brokenFile.exists()) {
+                    image = ImageIO.read(brokenFile);
+                }
+            }
+        }
+        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
 
         return createBitmap(delegate, createFlags, density.getDpiValue());
     }
@@ -281,8 +303,13 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nativeDestructor(long nativeBitmap) {
-        sManager.removeJavaReferenceFor(nativeBitmap);
+    /*package*/ static long nativeGetNativeFinalizer() {
+        synchronized (Bitmap_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+            }
+            return sFinalizer;
+        }
     }
 
     @LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index ba0d399..c4fbd56 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -21,6 +21,7 @@
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.layoutlib.bridge.impl.GcSnapshot;
 import com.android.layoutlib.bridge.impl.PorterDuffUtility;
+import com.android.ninepatch.NinePatchChunk;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.annotation.Nullable;
@@ -38,6 +39,8 @@
 import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 
+import libcore.util.NativeAllocationRegistry_Delegate;
+
 
 /**
  * Delegate implementing the native methods of android.graphics.Canvas
@@ -57,6 +60,7 @@
     // ---- delegate manager ----
     private static final DelegateManager<Canvas_Delegate> sManager =
             new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class);
+    private static long sFinalizer = -1;
 
 
     // ---- delegate helper data ----
@@ -160,6 +164,9 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static void native_setHighContrastText(long nativeCanvas, boolean highContrastText){}
+
+    @LayoutlibDelegate
     /*package*/ static int native_getWidth(long nativeCanvas) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
@@ -749,6 +756,61 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static void native_drawRegion(long nativeCanvas, long nativeRegion,
+            long nativePaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Some canvas paths may not be drawn", null, null);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_drawNinePatch(Canvas thisCanvas, long nativeCanvas,
+            long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop,
+            final float dstRight, final float dstBottom, long nativePaintOrZero,
+            final int screenDensity, final int bitmapDensity) {
+
+        // get the delegate from the native int.
+        final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        byte[] c = NinePatch_Delegate.getChunk(ninePatch);
+        if (c == null) {
+            // not a 9-patch?
+            BufferedImage image = bitmapDelegate.getImage();
+            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
+                    image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
+                    (int) dstBottom);
+            return;
+        }
+
+        final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
+        assert chunkObject != null;
+        if (chunkObject == null) {
+            return;
+        }
+
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // this one can be null
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+            @Override
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
+                        (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
+                        bitmapDensity);
+            }
+        }, paintDelegate, true, false);
+
+    }
+
+    @LayoutlibDelegate
     /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
                                                  float left, float top,
                                                  long nativePaintOrZero,
@@ -934,20 +996,21 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void finalizer(long nativeCanvas) {
-        // get the delegate from the native int so that it can be disposed.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            return;
+    /*package*/ static long getNativeFinalizer() {
+        synchronized (Canvas_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
+                    Canvas_Delegate delegate = sManager.getDelegate(nativePtr);
+                    if (delegate != null) {
+                        delegate.dispose();
+                    }
+                    sManager.removeJavaReferenceFor(nativePtr);
+                });
+            }
         }
-
-        canvasDelegate.dispose();
-
-        // remove it from the manager.
-        sManager.removeJavaReferenceFor(nativeCanvas);
+        return sFinalizer;
     }
 
-
     // ---- Private delegate/helper methods ----
 
     /**
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index b6588b6..746ef36 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -33,13 +33,13 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Scanner;
 import java.util.Set;
 
@@ -73,7 +73,7 @@
     private static final Map<String, FontInfo> sCache =
             new LinkedHashMap<String, FontInfo>(CACHE_SIZE) {
         @Override
-        protected boolean removeEldestEntry(Entry<String, FontInfo> eldest) {
+        protected boolean removeEldestEntry(Map.Entry<String, FontInfo> eldest) {
             return size() > CACHE_SIZE;
         }
 
@@ -213,7 +213,7 @@
         return mValid;
     }
 
-    /*package*/ static Font loadFont(String path) {
+    private static Font loadFont(String path) {
         if (path.startsWith(SYSTEM_FONTS) ) {
             String relativePath = path.substring(SYSTEM_FONTS.length());
             File f = new File(sFontLocation, relativePath);
@@ -270,16 +270,12 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean nAddFont(long nativeFamily, final String path) {
+    /*package*/ static boolean nAddFont(long nativeFamily, final String path, int ttcIndex) {
+        // FIXME: support ttc fonts. Hack JRE??
         final FontFamily_Delegate delegate = getDelegate(nativeFamily);
         if (delegate != null) {
             if (sFontLocation == null) {
-                delegate.mPostInitRunnables.add(new Runnable() {
-                    @Override
-                    public void run() {
-                        delegate.addFont(path);
-                    }
-                });
+                delegate.mPostInitRunnables.add(() -> delegate.addFont(path));
                 return true;
             }
             return delegate.addFont(path);
@@ -289,18 +285,18 @@
 
     @LayoutlibDelegate
     /*package*/ static boolean nAddFontWeightStyle(long nativeFamily,
-            final String path, final int index, final List<FontListParser.Axis> axes,
+            ByteBuffer buffer, final List<FontListParser.Axis> axes,
             final int weight, final boolean isItalic) {
-        // 'index' and 'axes' are not supported by java.awt.Font
+        assert false : "The only client of this method has been overriden.";
+        return false;
+    }
+
+    static boolean addFont(long nativeFamily, final String path, final int weight,
+            final boolean isItalic) {
         final FontFamily_Delegate delegate = getDelegate(nativeFamily);
         if (delegate != null) {
             if (sFontLocation == null) {
-                delegate.mPostInitRunnables.add(new Runnable() {
-                    @Override
-                    public void run() {
-                        delegate.addFont(path, weight, isItalic);
-                    }
-                });
+                delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic));
                 return true;
             }
             return delegate.addFont(path, weight, isItalic);
@@ -311,6 +307,9 @@
     @LayoutlibDelegate
     /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
         FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+        if (ffd == null) {
+            return false;
+        }
         ffd.mValid = true;
         if (mgr == null) {
             return false;
@@ -454,6 +453,7 @@
     private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
         int desiredWeight = outFont.mWeight;
         int srcWeight = srcFont.mWeight;
+        assert srcFont.mFont != null;
         Font derivedFont = srcFont.mFont;
         // Embolden the font if required.
         if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index e8d34d0..1f0eb3b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -169,77 +169,18 @@
         sManager.removeJavaReferenceFor(chunk);
     }
 
-    @LayoutlibDelegate
-    /*package*/ static void nativeDraw(long canvas_instance, RectF loc, Bitmap bitmap_instance,
-            long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
-        draw(canvas_instance,
-                (int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom,
-                bitmap_instance, chunk, paint_instance_or_null,
-                destDensity, srcDensity);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nativeDraw(long canvas_instance, Rect loc, Bitmap bitmap_instance,
-            long chunk, long paint_instance_or_null, int destDensity, int srcDensity) {
-        draw(canvas_instance,
-                loc.left, loc.top, loc.right, loc.bottom,
-                bitmap_instance, chunk, paint_instance_or_null,
-                destDensity, srcDensity);
-    }
 
     @LayoutlibDelegate
     /*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) {
         return 0;
     }
 
-    // ---- Private Helper methods ----
-
-    private static void draw(long canvas_instance,
-            final int left, final int top, final int right, final int bottom,
-            Bitmap bitmap_instance, long chunk, long paint_instance_or_null,
-            final int destDensity, final int srcDensity) {
-        // get the delegate from the native int.
-        final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
-        if (bitmap_delegate == null) {
-            return;
-        }
-
-        byte[] c = null;
-        NinePatch_Delegate delegate = sManager.getDelegate(chunk);
+    static byte[] getChunk(long nativeNinePatch) {
+        NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
         if (delegate != null) {
-            c = delegate.chunk;
+            return delegate.chunk;
         }
-        if (c == null) {
-            // not a 9-patch?
-            BufferedImage image = bitmap_delegate.getImage();
-            Canvas_Delegate.native_drawBitmap(null, canvas_instance, bitmap_instance,
-                    0f, 0f, (float)image.getWidth(), (float)image.getHeight(),
-                    (float)left, (float)top, (float)right, (float)bottom,
-                    paint_instance_or_null, destDensity, srcDensity);
-            return;
-        }
+        return null;
+    }
 
-        final NinePatchChunk chunkObject = getChunk(c);
-        assert chunkObject != null;
-        if (chunkObject == null) {
-            return;
-        }
-
-        Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance);
-        if (canvas_delegate == null) {
-            return;
-        }
-
-        // this one can be null
-        Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
-
-        canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
-                @Override
-                public void draw(Graphics2D graphics, Paint_Delegate paint) {
-                    chunkObject.draw(bitmap_delegate.getImage(), graphics,
-                            left, top, right - left, bottom - top, destDensity, srcDensity);
-                }
-            }, paint_delegate, true /*compositeOnly*/, false /*forceSrcMode*/);
-
-     }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index dbd45c4..514d785 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -39,6 +39,8 @@
 import java.util.List;
 import java.util.Locale;
 
+import libcore.util.NativeAllocationRegistry_Delegate;
+
 /**
  * Delegate implementing the native methods of android.graphics.Paint
  *
@@ -65,6 +67,7 @@
     // ---- delegate manager ----
     private static final DelegateManager<Paint_Delegate> sManager =
             new DelegateManager<Paint_Delegate>(Paint_Delegate.class);
+    private static long sFinalizer = -1;
 
     // ---- delegate helper data ----
 
@@ -250,9 +253,9 @@
     // ---- native methods ----
 
     @LayoutlibDelegate
-    /*package*/ static int getFlags(Paint thisPaint) {
+    /*package*/ static int nGetFlags(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
@@ -263,9 +266,9 @@
 
 
     @LayoutlibDelegate
-    /*package*/ static void setFlags(Paint thisPaint, int flags) {
+    /*package*/ static void nSetFlags(Paint thisPaint, long nativePaint, int flags) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -274,14 +277,14 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) {
-        setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter);
+    /*package*/ static void nSetFilterBitmap(Paint thisPaint, long nativePaint, boolean filter) {
+        setFlag(nativePaint, Paint.FILTER_BITMAP_FLAG, filter);
     }
 
     @LayoutlibDelegate
-    /*package*/ static int getHinting(Paint thisPaint) {
+    /*package*/ static int nGetHinting(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return Paint.HINTING_ON;
         }
@@ -290,9 +293,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setHinting(Paint thisPaint, int mode) {
+    /*package*/ static void nSetHinting(Paint thisPaint, long nativePaint, int mode) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -301,44 +304,48 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) {
-        setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa);
+    /*package*/ static void nSetAntiAlias(Paint thisPaint, long nativePaint, boolean aa) {
+        setFlag(nativePaint, Paint.ANTI_ALIAS_FLAG, aa);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) {
-        setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
+    /*package*/ static void nSetSubpixelText(Paint thisPaint, long nativePaint,
+            boolean subpixelText) {
+        setFlag(nativePaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) {
-        setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
+    /*package*/ static void nSetUnderlineText(Paint thisPaint, long nativePaint,
+            boolean underlineText) {
+        setFlag(nativePaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) {
-        setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
+    /*package*/ static void nSetStrikeThruText(Paint thisPaint, long nativePaint,
+            boolean strikeThruText) {
+        setFlag(nativePaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) {
-        setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
+    /*package*/ static void nSetFakeBoldText(Paint thisPaint, long nativePaint,
+            boolean fakeBoldText) {
+        setFlag(nativePaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setDither(Paint thisPaint, boolean dither) {
-        setFlag(thisPaint, Paint.DITHER_FLAG, dither);
+    /*package*/ static void nSetDither(Paint thisPaint, long nativePaint, boolean dither) {
+        setFlag(nativePaint, Paint.DITHER_FLAG, dither);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) {
-        setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText);
+    /*package*/ static void nSetLinearText(Paint thisPaint, long nativePaint, boolean linearText) {
+        setFlag(nativePaint, Paint.LINEAR_TEXT_FLAG, linearText);
     }
 
     @LayoutlibDelegate
-    /*package*/ static int getColor(Paint thisPaint) {
+    /*package*/ static int nGetColor(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
@@ -347,9 +354,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setColor(Paint thisPaint, int color) {
+    /*package*/ static void nSetColor(Paint thisPaint, long nativePaint, int color) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -358,9 +365,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int getAlpha(Paint thisPaint) {
+    /*package*/ static int nGetAlpha(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
@@ -369,9 +376,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setAlpha(Paint thisPaint, int a) {
+    /*package*/ static void nSetAlpha(Paint thisPaint, long nativePaint, int a) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -380,9 +387,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float getStrokeWidth(Paint thisPaint) {
+    /*package*/ static float nGetStrokeWidth(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 1.f;
         }
@@ -391,9 +398,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setStrokeWidth(Paint thisPaint, float width) {
+    /*package*/ static void nSetStrokeWidth(Paint thisPaint, long nativePaint, float width) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -402,9 +409,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float getStrokeMiter(Paint thisPaint) {
+    /*package*/ static float nGetStrokeMiter(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 1.f;
         }
@@ -413,9 +420,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) {
+    /*package*/ static void nSetStrokeMiter(Paint thisPaint, long nativePaint, float miter) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -424,7 +431,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setShadowLayer(long paint, float radius, float dx, float dy,
+    /*package*/ static void nSetShadowLayer(long paint, float radius, float dx, float dy,
             int color) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -432,7 +439,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_hasShadowLayer(long paint) {
+    /*package*/ static boolean nHasShadowLayer(long paint) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                 "Paint.hasShadowLayer is not supported.", null, null /*data*/);
@@ -440,16 +447,17 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean isElegantTextHeight(Paint thisPaint) {
+    /*package*/ static boolean nIsElegantTextHeight(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT;
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) {
+    /*package*/ static void nSetElegantTextHeight(Paint thisPaint, long nativePaint,
+            boolean elegant) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -458,9 +466,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float getTextSize(Paint thisPaint) {
+    /*package*/ static float nGetTextSize(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 1.f;
         }
@@ -469,9 +477,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setTextSize(Paint thisPaint, float textSize) {
+    /*package*/ static void nSetTextSize(Paint thisPaint, long nativePaint, float textSize) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -483,9 +491,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float getTextScaleX(Paint thisPaint) {
+    /*package*/ static float nGetTextScaleX(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 1.f;
         }
@@ -494,9 +502,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) {
+    /*package*/ static void nSetTextScaleX(Paint thisPaint, long nativePaint, float scaleX) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -508,9 +516,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float getTextSkewX(Paint thisPaint) {
+    /*package*/ static float nGetTextSkewX(Paint thisPaint, long nativePaint) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 1.f;
         }
@@ -519,9 +527,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) {
+    /*package*/ static void nSetTextSkewX(Paint thisPaint, long nativePaint, float skewX) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
@@ -533,9 +541,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float ascent(Paint thisPaint) {
+    /*package*/ static float nAscent(Paint thisPaint, long nativePaint, long nativeTypeface) {
         // get the delegate
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
@@ -550,9 +558,9 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float descent(Paint thisPaint) {
+    /*package*/ static float nDescent(Paint thisPaint, long nativePaint, long nativeTypeface) {
         // get the delegate
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
@@ -567,9 +575,10 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) {
+    /*package*/ static float nGetFontMetrics(Paint thisPaint, long nativePaint, long nativeTypeface,
+            FontMetrics metrics) {
         // get the delegate
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
@@ -578,9 +587,10 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) {
+    /*package*/ static int nGetFontMetricsInt(Paint thisPaint, long nativePaint,
+            long nativeTypeface, FontMetricsInt fmi) {
         // get the delegate
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
         }
@@ -603,31 +613,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index,
-            int count, int bidiFlags) {
-        // get the delegate
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
-        if (delegate == null) {
-            return 0;
-        }
-
-        RectF bounds = delegate.measureText(text, index, count, null, 0, bidiFlags);
-        return bounds.right - bounds.left;
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end,
-        int bidiFlags) {
-        return native_measureText(thisPaint, text.toCharArray(), start, end - start, bidiFlags);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static float native_measureText(Paint thisPaint, String text, int bidiFlags) {
-        return native_measureText(thisPaint, text.toCharArray(), 0, text.length(), bidiFlags);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, char[] text,
+    /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, char[] text,
             int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
 
         // get the delegate
@@ -669,21 +655,21 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, String text,
+    /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, String text,
             boolean measureForwards,
             float maxWidth, int bidiFlags, float[] measuredWidth) {
-        return native_breakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
+        return nBreakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
                 maxWidth, bidiFlags, measuredWidth);
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_init() {
+    /*package*/ static long nInit() {
         Paint_Delegate newDelegate = new Paint_Delegate();
         return sManager.addNewDelegate(newDelegate);
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_initWithPaint(long paint) {
+    /*package*/ static long nInitWithPaint(long paint) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(paint);
         if (delegate == null) {
@@ -695,7 +681,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_reset(long native_object) {
+    /*package*/ static void nReset(long native_object) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -706,7 +692,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_set(long native_dst, long native_src) {
+    /*package*/ static void nSet(long native_dst, long native_src) {
         // get the delegate from the native int.
         Paint_Delegate delegate_dst = sManager.getDelegate(native_dst);
         if (delegate_dst == null) {
@@ -723,7 +709,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getStyle(long native_object) {
+    /*package*/ static int nGetStyle(long native_object) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -734,7 +720,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setStyle(long native_object, int style) {
+    /*package*/ static void nSetStyle(long native_object, int style) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -745,7 +731,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getStrokeCap(long native_object) {
+    /*package*/ static int nGetStrokeCap(long native_object) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -756,7 +742,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setStrokeCap(long native_object, int cap) {
+    /*package*/ static void nSetStrokeCap(long native_object, int cap) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -767,7 +753,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getStrokeJoin(long native_object) {
+    /*package*/ static int nGetStrokeJoin(long native_object) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -778,7 +764,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setStrokeJoin(long native_object, int join) {
+    /*package*/ static void nSetStrokeJoin(long native_object, int join) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -789,7 +775,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_getFillPath(long native_object, long src, long dst) {
+    /*package*/ static boolean nGetFillPath(long native_object, long src, long dst) {
         Paint_Delegate paint = sManager.getDelegate(native_object);
         if (paint == null) {
             return false;
@@ -815,7 +801,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_setShader(long native_object, long shader) {
+    /*package*/ static long nSetShader(long native_object, long shader) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -828,7 +814,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_setColorFilter(long native_object, long filter) {
+    /*package*/ static long nSetColorFilter(long native_object, long filter) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -847,7 +833,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_setXfermode(long native_object, long xfermode) {
+    /*package*/ static long nSetXfermode(long native_object, long xfermode) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -860,7 +846,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_setPathEffect(long native_object, long effect) {
+    /*package*/ static long nSetPathEffect(long native_object, long effect) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -873,7 +859,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_setMaskFilter(long native_object, long maskfilter) {
+    /*package*/ static long nSetMaskFilter(long native_object, long maskfilter) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -892,7 +878,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_setTypeface(long native_object, long typeface) {
+    /*package*/ static long nSetTypeface(long native_object, long typeface) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -909,7 +895,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long native_setRasterizer(long native_object, long rasterizer) {
+    /*package*/ static long nSetRasterizer(long native_object, long rasterizer) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -928,7 +914,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getTextAlign(long native_object) {
+    /*package*/ static int nGetTextAlign(long native_object) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -939,7 +925,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setTextAlign(long native_object, int align) {
+    /*package*/ static void nSetTextAlign(long native_object, int align) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
@@ -950,58 +936,27 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setTextLocale(long native_object, String locale) {
-        // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(native_object);
-        if (delegate == null) {
-            return;
-        }
-
-        delegate.setTextLocale(locale);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static int native_getTextWidths(long native_object, long native_typeface,
-            char[] text, int index, int count, int bidiFlags, float[] widths) {
-
-        if (widths != null) {
-            for (int i = 0; i< count; i++) {
-                widths[i]=0;
-            }
-        }
+    /*package*/ static int nSetTextLocales(long native_object, String locale) {
         // get the delegate from the native int.
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
             return 0;
         }
 
-        // native_typeface is passed here since Framework's old implementation did not have the
-        // typeface object associated with the Paint. Since, we follow the new framework way,
-        // we store the typeface with the paint and use it directly.
-        assert (native_typeface == delegate.mNativeTypeface);
-
-        RectF bounds = delegate.measureText(text, index, count, widths, 0, bidiFlags);
-        return ((int) (bounds.right - bounds.left));
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static int native_getTextWidths(long native_object, long native_typeface,
-            String text, int start, int end, int bidiFlags, float[] widths) {
-        return native_getTextWidths(native_object, native_typeface, text.toCharArray(), start,
-                end - start, bidiFlags, widths);
-    }
-
-    @LayoutlibDelegate
-    /* package */static int native_getTextGlyphs(long native_object, String text, int start,
-            int end, int contextStart, int contextEnd, int flags, char[] glyphs) {
-        // FIXME
+        delegate.setTextLocale(locale);
         return 0;
     }
 
     @LayoutlibDelegate
-    /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface,
+    /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+            int mMinikinLangListId) {
+        // FIXME
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
             char[] text, int index, int count, int contextIndex, int contextCount,
-            boolean isRtl, float[] advances, int advancesIndex) {
+            int bidiFlags, float[] advances, int advancesIndex) {
 
         if (advances != null)
             for (int i = advancesIndex; i< advancesIndex+count; i++)
@@ -1017,25 +972,25 @@
         // we store the typeface with the paint and use it directly.
         assert (native_typeface == delegate.mNativeTypeface);
 
-        RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, isRtl);
+        RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags);
         return bounds.right - bounds.left;
     }
 
     @LayoutlibDelegate
-    /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface,
+    /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
             String text, int start, int end, int contextStart, int contextEnd,
-            boolean isRtl, float[] advances, int advancesIndex) {
+            int bidiFlags, float[] advances, int advancesIndex) {
         // FIXME: support contextStart and contextEnd
         int count = end - start;
         char[] buffer = TemporaryBuffer.obtain(count);
         TextUtils.getChars(text, start, end, buffer, 0);
 
-        return native_getTextRunAdvances(native_object, native_typeface, buffer, 0, count,
-                contextStart, contextEnd - contextStart, isRtl, advances, advancesIndex);
+        return nGetTextAdvances(native_object, native_typeface, buffer, 0, count,
+                contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex);
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, char[] text,
+    /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, char[] text,
             int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1044,7 +999,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, String text,
+    /*package*/ static int nGetTextRunCursor(Paint thisPaint, long native_object, String text,
             int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1053,7 +1008,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_getTextPath(long native_object, long native_typeface,
+    /*package*/ static void nGetTextPath(long native_object, long native_typeface,
             int bidiFlags, char[] text, int index, int count, float x, float y, long path) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1061,7 +1016,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_getTextPath(long native_object, long native_typeface,
+    /*package*/ static void nGetTextPath(long native_object, long native_typeface,
             int bidiFlags, String text, int start, int end, float x, float y, long path) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1069,14 +1024,14 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nativeGetStringBounds(long nativePaint, long native_typeface,
+    /*package*/ static void nGetStringBounds(long nativePaint, long native_typeface,
             String text, int start, int end, int bidiFlags, Rect bounds) {
-        nativeGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
+        nGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
                 end - start, bidiFlags, bounds);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nativeGetCharArrayBounds(long nativePaint, long native_typeface,
+    /*package*/ static void nGetCharArrayBounds(long nativePaint, long native_typeface,
             char[] text, int index, int count, int bidiFlags, Rect bounds) {
 
         // get the delegate from the native int.
@@ -1092,12 +1047,18 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void finalizer(long nativePaint) {
-        sManager.removeJavaReferenceFor(nativePaint);
+    /*package*/ static long nGetNativeFinalizer() {
+        synchronized (Paint_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+                        sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
     }
 
     @LayoutlibDelegate
-    /*package*/ static float native_getLetterSpacing(long nativePaint) {
+    /*package*/ static float nGetLetterSpacing(long nativePaint) {
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
@@ -1106,7 +1067,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setLetterSpacing(long nativePaint, float letterSpacing) {
+    /*package*/ static void nSetLetterSpacing(long nativePaint, float letterSpacing) {
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
                 "Paint.setLetterSpacing() not supported.", null, null);
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -1117,13 +1078,13 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setFontFeatureSettings(long nativePaint, String settings) {
+    /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) {
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
                 "Paint.setFontFeatureSettings() not supported.", null, null);
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getHyphenEdit(long nativePaint) {
+    /*package*/ static int nGetHyphenEdit(long nativePaint) {
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return 0;
@@ -1132,7 +1093,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void native_setHyphenEdit(long nativePaint, int hyphen) {
+    /*package*/ static void nSetHyphenEdit(long nativePaint, int hyphen) {
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
@@ -1141,7 +1102,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean native_hasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
+    /*package*/ static boolean nHasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
             String string) {
         Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
@@ -1169,13 +1130,14 @@
 
 
     @LayoutlibDelegate
-    /*package*/ static float native_getRunAdvance(long nativePaint, long nativeTypeface,
+    /*package*/ static float nGetRunAdvance(long nativePaint, long nativeTypeface,
             @NonNull char[] text, int start, int end, int contextStart, int contextEnd,
             boolean isRtl, int offset) {
         int count = end - start;
         float[] advances = new float[count];
-        native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
-                contextStart, contextEnd - contextStart, isRtl, advances, 0);
+        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+        nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
+                contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
         int startOffset = offset - start;  // offset from start.
         float sum = 0;
         for (int i = 0; i < startOffset; i++) {
@@ -1185,13 +1147,14 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int native_getOffsetForAdvance(long nativePaint, long nativeTypeface,
+    /*package*/ static int nGetOffsetForAdvance(long nativePaint, long nativeTypeface,
             char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
             float advance) {
         int count = end - start;
         float[] advances = new float[count];
-        native_getTextRunAdvances(nativePaint, nativeTypeface, text, start, count,
-                contextStart, contextEnd - contextStart, isRtl, advances, 0);
+        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+        nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
+                contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
         float sum = 0;
         int i;
         for (i = 0; i < count && sum < advance; i++) {
@@ -1359,9 +1322,9 @@
         mLocale = new Locale(locale);
     }
 
-    private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) {
+    private static void setFlag(long nativePaint, int flagMask, boolean flagValue) {
         // get the delegate from the native int.
-        Paint_Delegate delegate = sManager.getDelegate(thisPaint.getNativeInstance());
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
         if (delegate == null) {
             return;
         }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index a10ac00..e1da27b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -75,7 +75,7 @@
         return sManager.getDelegate(nPath);
     }
 
-    public Shape getJavaShape() {
+    public Path2D getJavaShape() {
         return mPath;
     }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 85e65e6..5cd34f6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -24,8 +24,10 @@
 
 import java.awt.Font;
 import java.io.File;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 import static android.graphics.FontFamily_Delegate.getFontLocation;
 
@@ -205,6 +207,17 @@
         return new File(getFontLocation());
     }
 
+    @LayoutlibDelegate
+    /*package*/ static FontFamily makeFamilyFromParsed(FontListParser.Family family,
+            Map<String, ByteBuffer> bufferForPath) {
+        FontFamily fontFamily = new FontFamily(family.lang, family.variant);
+        for (FontListParser.Font font : family.fonts) {
+            FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.fontName, font.weight,
+                    font.isItalic);
+        }
+        return fontFamily;
+    }
+
     // ---- Private delegate/helper methods ----
 
     private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) {
diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
index 44ce731..fcd63ea 100644
--- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java
@@ -25,7 +25,7 @@
 /**
  * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
  * <p/>
- * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
+ * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
  * by calls to methods of the same name in this delegate class.
  */
 public class Hyphenator_Delegate {
@@ -39,7 +39,8 @@
         return null;
     }
 
-    /*package*/ static long loadHyphenator(ByteBuffer buf, int offset) {
+    /*package*/ @SuppressWarnings("UnusedParameters")  // TODO implement this.
+    static long loadHyphenator(ByteBuffer buffer, int offset) {
         return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
     }
 }
diff --git a/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
new file mode 100644
index 0000000..d3af837
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/util/PathParser_Delegate.java
@@ -0,0 +1,757 @@
+/*
+ * 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.util;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.graphics.Path_Delegate;
+
+import java.awt.geom.Path2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Delegate that provides implementation for native methods in {@link android.util.PathParser}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of PathParser have been replaced by calls to
+ * methods of the same name in this delegate class.
+ *
+ * Most of the code has been taken from the implementation in
+ * {@code tools/base/sdk-common/src/main/java/com/android/ide/common/vectordrawable/PathParser.java}
+ * revision be6fe89a3b686db5a75e7e692a148699973957f3
+ */
+public class PathParser_Delegate {
+
+    private static final Logger LOGGER = Logger.getLogger("PathParser");
+
+    // ---- Builder delegate manager ----
+    private static final DelegateManager<PathParser_Delegate> sManager =
+            new DelegateManager<PathParser_Delegate>(PathParser_Delegate.class);
+
+    // ---- delegate data ----
+    @NonNull
+    private PathDataNode[] mPathDataNodes;
+
+    private PathParser_Delegate(@NonNull PathDataNode[] nodes) {
+        mPathDataNodes = nodes;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nParseStringForPath(long pathPtr, @NonNull String pathString, int
+            stringLength) {
+        Path_Delegate path_delegate = Path_Delegate.getDelegate(pathPtr);
+        if (path_delegate == null) {
+            return false;
+        }
+        assert pathString.length() == stringLength;
+        PathDataNode.nodesToPath(createNodesFromPathData(pathString), path_delegate.getJavaShape());
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCreatePathFromPathData(long outPathPtr, long pathData) {
+        Path_Delegate path_delegate = Path_Delegate.getDelegate(outPathPtr);
+        PathParser_Delegate source = sManager.getDelegate(outPathPtr);
+        if (source == null || path_delegate == null) {
+            return;
+        }
+        PathDataNode.nodesToPath(source.mPathDataNodes, path_delegate.getJavaShape());
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreateEmptyPathData() {
+        PathParser_Delegate newDelegate = new PathParser_Delegate(new PathDataNode[0]);
+        return sManager.addNewDelegate(newDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreatePathData(long nativePtr) {
+        PathParser_Delegate source = sManager.getDelegate(nativePtr);
+        if (source == null) {
+            return 0;
+        }
+        PathParser_Delegate dest = new PathParser_Delegate(deepCopyNodes(source.mPathDataNodes));
+        return sManager.addNewDelegate(dest);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreatePathDataFromString(@NonNull String pathString,
+            int stringLength) {
+        assert pathString.length() == stringLength : "Inconsistent path string length.";
+        PathDataNode[] nodes = createNodesFromPathData(pathString);
+        PathParser_Delegate delegate = new PathParser_Delegate(nodes);
+        return sManager.addNewDelegate(delegate);
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+            long toDataPtr, float fraction) {
+        PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+        PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+        PathParser_Delegate to = sManager.getDelegate(toDataPtr);
+        if (out == null || from == null || to == null) {
+            return false;
+        }
+        int length = from.mPathDataNodes.length;
+        if (length != to.mPathDataNodes.length) {
+            Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                    "Cannot interpolate path data with different lengths (from " + length + " to " +
+                            to.mPathDataNodes.length + ").", null);
+            return false;
+        }
+        if (out.mPathDataNodes.length != length) {
+            out.mPathDataNodes = new PathDataNode[length];
+        }
+        for (int i = 0; i < length; i++) {
+            out.mPathDataNodes[i].interpolatePathDataNode(from.mPathDataNodes[i],
+                    to.mPathDataNodes[i], fraction);
+        }
+        return true;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nFinalize(long nativePtr) {
+        sManager.removeJavaReferenceFor(nativePtr);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nCanMorph(long fromDataPtr, long toDataPtr) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "morphing path data isn't " +
+                "supported", null, null);
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetPathData(long outDataPtr, long fromDataPtr) {
+        PathParser_Delegate out = sManager.getDelegate(outDataPtr);
+        PathParser_Delegate from = sManager.getDelegate(fromDataPtr);
+        if (from == null || out == null) {
+            return;
+        }
+        out.mPathDataNodes = deepCopyNodes(from.mPathDataNodes);
+    }
+
+    /**
+     * @param pathData The string representing a path, the same as "d" string in svg file.
+     *
+     * @return an array of the PathDataNode.
+     */
+    @NonNull
+    private static PathDataNode[] createNodesFromPathData(@NonNull String pathData) {
+        int start = 0;
+        int end = 1;
+
+        ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
+        while (end < pathData.length()) {
+            end = nextStart(pathData, end);
+            String s = pathData.substring(start, end).trim();
+            if (s.length() > 0) {
+                float[] val = getFloats(s);
+                addNode(list, s.charAt(0), val);
+            }
+
+            start = end;
+            end++;
+        }
+        if ((end - start) == 1 && start < pathData.length()) {
+            addNode(list, pathData.charAt(start), new float[0]);
+        }
+        return list.toArray(new PathDataNode[list.size()]);
+    }
+
+    /**
+     * @param source The array of PathDataNode to be duplicated.
+     *
+     * @return a deep copy of the <code>source</code>.
+     */
+    @NonNull
+    private static PathDataNode[] deepCopyNodes(@NonNull PathDataNode[] source) {
+        PathDataNode[] copy = new PathDataNode[source.length];
+        for (int i = 0; i < source.length; i++) {
+            copy[i] = new PathDataNode(source[i]);
+        }
+        return copy;
+    }
+
+    private static int nextStart(@NonNull String s, int end) {
+        char c;
+
+        while (end < s.length()) {
+            c = s.charAt(end);
+            // Note that 'e' or 'E' are not valid path commands, but could be
+            // used for floating point numbers' scientific notation.
+            // Therefore, when searching for next command, we should ignore 'e'
+            // and 'E'.
+            if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
+                    && c != 'e' && c != 'E') {
+                return end;
+            }
+            end++;
+        }
+        return end;
+    }
+
+    /**
+     * Calculate the position of the next comma or space or negative sign
+     *
+     * @param s the string to search
+     * @param start the position to start searching
+     * @param result the result of the extraction, including the position of the the starting
+     * position of next number, whether it is ending with a '-'.
+     */
+    private static void extract(@NonNull String s, int start, @NonNull ExtractFloatResult result) {
+        // Now looking for ' ', ',', '.' or '-' from the start.
+        int currentIndex = start;
+        boolean foundSeparator = false;
+        result.mEndWithNegOrDot = false;
+        boolean secondDot = false;
+        boolean isExponential = false;
+        for (; currentIndex < s.length(); currentIndex++) {
+            boolean isPrevExponential = isExponential;
+            isExponential = false;
+            char currentChar = s.charAt(currentIndex);
+            switch (currentChar) {
+                case ' ':
+                case ',':
+                    foundSeparator = true;
+                    break;
+                case '-':
+                    // The negative sign following a 'e' or 'E' is not a separator.
+                    if (currentIndex != start && !isPrevExponential) {
+                        foundSeparator = true;
+                        result.mEndWithNegOrDot = true;
+                    }
+                    break;
+                case '.':
+                    if (!secondDot) {
+                        secondDot = true;
+                    } else {
+                        // This is the second dot, and it is considered as a separator.
+                        foundSeparator = true;
+                        result.mEndWithNegOrDot = true;
+                    }
+                    break;
+                case 'e':
+                case 'E':
+                    isExponential = true;
+                    break;
+            }
+            if (foundSeparator) {
+                break;
+            }
+        }
+        // When there is nothing found, then we put the end position to the end
+        // of the string.
+        result.mEndPosition = currentIndex;
+    }
+
+    /**
+     * Parse the floats in the string. This is an optimized version of
+     * parseFloat(s.split(",|\\s"));
+     *
+     * @param s the string containing a command and list of floats
+     *
+     * @return array of floats
+     */
+    @NonNull
+    private static float[] getFloats(@NonNull String s) {
+        if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
+            return new float[0];
+        }
+        try {
+            float[] results = new float[s.length()];
+            int count = 0;
+            int startPosition = 1;
+            int endPosition;
+
+            ExtractFloatResult result = new ExtractFloatResult();
+            int totalLength = s.length();
+
+            // The startPosition should always be the first character of the
+            // current number, and endPosition is the character after the current
+            // number.
+            while (startPosition < totalLength) {
+                extract(s, startPosition, result);
+                endPosition = result.mEndPosition;
+
+                if (startPosition < endPosition) {
+                    results[count++] = Float.parseFloat(
+                            s.substring(startPosition, endPosition));
+                }
+
+                if (result.mEndWithNegOrDot) {
+                    // Keep the '-' or '.' sign with next number.
+                    startPosition = endPosition;
+                } else {
+                    startPosition = endPosition + 1;
+                }
+            }
+            return Arrays.copyOf(results, count);
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("error in parsing \"" + s + "\"", e);
+        }
+    }
+
+
+    private static void addNode(@NonNull ArrayList<PathDataNode> list, char cmd,
+            @NonNull float[] val) {
+        list.add(new PathDataNode(cmd, val));
+    }
+
+    private static class ExtractFloatResult {
+        // We need to return the position of the next separator and whether the
+        // next float starts with a '-' or a '.'.
+        private int mEndPosition;
+        private boolean mEndWithNegOrDot;
+    }
+
+    /**
+     * Each PathDataNode represents one command in the "d" attribute of the svg file. An array of
+     * PathDataNode can represent the whole "d" attribute.
+     */
+    private static class PathDataNode {
+        private char mType;
+        @NonNull
+        private float[] mParams;
+
+        private PathDataNode(char type, @NonNull float[] params) {
+            mType = type;
+            mParams = params;
+        }
+
+        public char getType() {
+            return mType;
+        }
+
+        @NonNull
+        public float[] getParams() {
+            return mParams;
+        }
+
+        private PathDataNode(@NonNull PathDataNode n) {
+            mType = n.mType;
+            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
+        }
+
+        /**
+         * Convert an array of PathDataNode to Path.
+         *
+         * @param node The source array of PathDataNode.
+         * @param path The target Path object.
+         */
+        private static void nodesToPath(@NonNull PathDataNode[] node, @NonNull Path2D path) {
+            float[] current = new float[6];
+            char previousCommand = 'm';
+            //noinspection ForLoopReplaceableByForEach
+            for (int i = 0; i < node.length; i++) {
+                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
+                previousCommand = node[i].mType;
+            }
+        }
+
+        /**
+         * The current PathDataNode will be interpolated between the <code>nodeFrom</code> and
+         * <code>nodeTo</code> according to the <code>fraction</code>.
+         *
+         * @param nodeFrom The start value as a PathDataNode.
+         * @param nodeTo The end value as a PathDataNode
+         * @param fraction The fraction to interpolate.
+         */
+        private void interpolatePathDataNode(@NonNull PathDataNode nodeFrom,
+                @NonNull PathDataNode nodeTo, float fraction) {
+            for (int i = 0; i < nodeFrom.mParams.length; i++) {
+                mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
+                        + nodeTo.mParams[i] * fraction;
+            }
+        }
+
+        @SuppressWarnings("PointlessArithmeticExpression")
+        private static void addCommand(@NonNull Path2D path, float[] current, char cmd,
+                char lastCmd, @NonNull float[] val) {
+
+            int incr = 2;
+
+            float cx = current[0];
+            float cy = current[1];
+            float cpx = current[2];
+            float cpy = current[3];
+            float loopX = current[4];
+            float loopY = current[5];
+
+            switch (cmd) {
+                case 'z':
+                case 'Z':
+                    path.closePath();
+                    cx = loopX;
+                    cy = loopY;
+                case 'm':
+                case 'M':
+                case 'l':
+                case 'L':
+                case 't':
+                case 'T':
+                    incr = 2;
+                    break;
+                case 'h':
+                case 'H':
+                case 'v':
+                case 'V':
+                    incr = 1;
+                    break;
+                case 'c':
+                case 'C':
+                    incr = 6;
+                    break;
+                case 's':
+                case 'S':
+                case 'q':
+                case 'Q':
+                    incr = 4;
+                    break;
+                case 'a':
+                case 'A':
+                    incr = 7;
+            }
+
+            for (int k = 0; k < val.length; k += incr) {
+                boolean reflectCtrl;
+                float tempReflectedX, tempReflectedY;
+
+                switch (cmd) {
+                    case 'm':
+                        cx += val[k + 0];
+                        cy += val[k + 1];
+                        if (k > 0) {
+                            // According to the spec, if a moveto is followed by multiple
+                            // pairs of coordinates, the subsequent pairs are treated as
+                            // implicit lineto commands.
+                            path.lineTo(cx, cy);
+                        } else {
+                            path.moveTo(cx, cy);
+                            loopX = cx;
+                            loopY = cy;
+                        }
+                        break;
+                    case 'M':
+                        cx = val[k + 0];
+                        cy = val[k + 1];
+                        if (k > 0) {
+                            // According to the spec, if a moveto is followed by multiple
+                            // pairs of coordinates, the subsequent pairs are treated as
+                            // implicit lineto commands.
+                            path.lineTo(cx, cy);
+                        } else {
+                            path.moveTo(cx, cy);
+                            loopX = cx;
+                            loopY = cy;
+                        }
+                        break;
+                    case 'l':
+                        cx += val[k + 0];
+                        cy += val[k + 1];
+                        path.lineTo(cx, cy);
+                        break;
+                    case 'L':
+                        cx = val[k + 0];
+                        cy = val[k + 1];
+                        path.lineTo(cx, cy);
+                        break;
+                    case 'z':
+                    case 'Z':
+                        path.closePath();
+                        cx = loopX;
+                        cy = loopY;
+                        break;
+                    case 'h':
+                        cx += val[k + 0];
+                        path.lineTo(cx, cy);
+                        break;
+                    case 'H':
+                        path.lineTo(val[k + 0], cy);
+                        cx = val[k + 0];
+                        break;
+                    case 'v':
+                        cy += val[k + 0];
+                        path.lineTo(cx, cy);
+                        break;
+                    case 'V':
+                        path.lineTo(cx, val[k + 0]);
+                        cy = val[k + 0];
+                        break;
+                    case 'c':
+                        path.curveTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
+                                cy + val[k + 3], cx + val[k + 4], cy + val[k + 5]);
+                        cpx = cx + val[k + 2];
+                        cpy = cy + val[k + 3];
+                        cx += val[k + 4];
+                        cy += val[k + 5];
+                        break;
+                    case 'C':
+                        path.curveTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
+                                val[k + 4], val[k + 5]);
+                        cx = val[k + 4];
+                        cy = val[k + 5];
+                        cpx = val[k + 2];
+                        cpy = val[k + 3];
+                        break;
+                    case 's':
+                        reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' ||
+                                lastCmd == 'S');
+                        path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
+                                * cy - cpy : cy, cx + val[k + 0], cy + val[k + 1], cx
+                                + val[k + 2], cy + val[k + 3]);
+
+                        cpx = cx + val[k + 0];
+                        cpy = cy + val[k + 1];
+                        cx += val[k + 2];
+                        cy += val[k + 3];
+                        break;
+                    case 'S':
+                        reflectCtrl = (lastCmd == 'c' || lastCmd == 's' || lastCmd == 'C' ||
+                                lastCmd == 'S');
+                        path.curveTo(reflectCtrl ? 2 * cx - cpx : cx, reflectCtrl ? 2
+                                        * cy - cpy : cy, val[k + 0], val[k + 1], val[k + 2],
+                                val[k + 3]);
+                        cpx = (val[k + 0]);
+                        cpy = (val[k + 1]);
+                        cx = val[k + 2];
+                        cy = val[k + 3];
+                        break;
+                    case 'q':
+                        path.quadTo(cx + val[k + 0], cy + val[k + 1], cx + val[k + 2],
+                                cy + val[k + 3]);
+                        cpx = cx + val[k + 0];
+                        cpy = cy + val[k + 1];
+                        // Note that we have to update cpx first, since cx will be updated here.
+                        cx += val[k + 2];
+                        cy += val[k + 3];
+                        break;
+                    case 'Q':
+                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
+                        cx = val[k + 2];
+                        cy = val[k + 3];
+                        cpx = val[k + 0];
+                        cpy = val[k + 1];
+                        break;
+                    case 't':
+                        reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' ||
+                                lastCmd == 'T');
+                        tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
+                        tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
+                        path.quadTo(tempReflectedX, tempReflectedY, cx + val[k + 0],
+                                cy + val[k + 1]);
+                        cpx = tempReflectedX;
+                        cpy = tempReflectedY;
+                        cx += val[k + 0];
+                        cy += val[k + 1];
+                        break;
+                    case 'T':
+                        reflectCtrl = (lastCmd == 'q' || lastCmd == 't' || lastCmd == 'Q' ||
+                                lastCmd == 'T');
+                        tempReflectedX = reflectCtrl ? 2 * cx - cpx : cx;
+                        tempReflectedY = reflectCtrl ? 2 * cy - cpy : cy;
+                        path.quadTo(tempReflectedX, tempReflectedY, val[k + 0], val[k + 1]);
+                        cx = val[k + 0];
+                        cy = val[k + 1];
+                        cpx = tempReflectedX;
+                        cpy = tempReflectedY;
+                        break;
+                    case 'a':
+                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+                        drawArc(path, cx, cy, val[k + 5] + cx, val[k + 6] + cy,
+                                val[k + 0], val[k + 1], val[k + 2], val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        cx += val[k + 5];
+                        cy += val[k + 6];
+                        cpx = cx;
+                        cpy = cy;
+
+                        break;
+                    case 'A':
+                        drawArc(path, cx, cy, val[k + 5], val[k + 6], val[k + 0],
+                                val[k + 1], val[k + 2], val[k + 3] != 0,
+                                val[k + 4] != 0);
+                        cx = val[k + 5];
+                        cy = val[k + 6];
+                        cpx = cx;
+                        cpy = cy;
+                        break;
+
+                }
+                lastCmd = cmd;
+            }
+            current[0] = cx;
+            current[1] = cy;
+            current[2] = cpx;
+            current[3] = cpy;
+            current[4] = loopX;
+            current[5] = loopY;
+
+        }
+
+        private static void drawArc(@NonNull Path2D p, float x0, float y0, float x1,
+                float y1, float a, float b, float theta, boolean isMoreThanHalf,
+                boolean isPositiveArc) {
+
+            LOGGER.log(Level.FINE, "(" + x0 + "," + y0 + ")-(" + x1 + "," + y1
+                    + ") {" + a + " " + b + "}");
+        /* Convert rotation angle from degrees to radians */
+            double thetaD = theta * Math.PI / 180.0f;
+        /* Pre-compute rotation matrix entries */
+            double cosTheta = Math.cos(thetaD);
+            double sinTheta = Math.sin(thetaD);
+        /* Transform (x0, y0) and (x1, y1) into unit space */
+        /* using (inverse) rotation, followed by (inverse) scale */
+            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+            LOGGER.log(Level.FINE, "unit space (" + x0p + "," + y0p + ")-(" + x1p
+                    + "," + y1p + ")");
+        /* Compute differences and averages */
+            double dx = x0p - x1p;
+            double dy = y0p - y1p;
+            double xm = (x0p + x1p) / 2;
+            double ym = (y0p + y1p) / 2;
+        /* Solve for intersecting unit circles */
+            double dsq = dx * dx + dy * dy;
+            if (dsq == 0.0) {
+                LOGGER.log(Level.FINE, " Points are coincident");
+                return; /* Points are coincident */
+            }
+            double disc = 1.0 / dsq - 1.0 / 4.0;
+            if (disc < 0.0) {
+                LOGGER.log(Level.FINE, "Points are too far apart " + dsq);
+                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+                drawArc(p, x0, y0, x1, y1, a * adjust, b * adjust, theta,
+                        isMoreThanHalf, isPositiveArc);
+                return; /* Points are too far apart */
+            }
+            double s = Math.sqrt(disc);
+            double sdx = s * dx;
+            double sdy = s * dy;
+            double cx;
+            double cy;
+            if (isMoreThanHalf == isPositiveArc) {
+                cx = xm - sdy;
+                cy = ym + sdx;
+            } else {
+                cx = xm + sdy;
+                cy = ym - sdx;
+            }
+
+            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+            LOGGER.log(Level.FINE, "eta0 = Math.atan2( " + (y0p - cy) + " , "
+                    + (x0p - cx) + ") = " + Math.toDegrees(eta0));
+
+            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+            LOGGER.log(Level.FINE, "eta1 = Math.atan2( " + (y1p - cy) + " , "
+                    + (x1p - cx) + ") = " + Math.toDegrees(eta1));
+            double sweep = (eta1 - eta0);
+            if (isPositiveArc != (sweep >= 0)) {
+                if (sweep > 0) {
+                    sweep -= 2 * Math.PI;
+                } else {
+                    sweep += 2 * Math.PI;
+                }
+            }
+
+            cx *= a;
+            cy *= b;
+            double tcx = cx;
+            cx = cx * cosTheta - cy * sinTheta;
+            cy = tcx * sinTheta + cy * cosTheta;
+            LOGGER.log(
+                    Level.FINE,
+                    "cx, cy, a, b, x0, y0, thetaD, eta0, sweep = " + cx + " , "
+                            + cy + " , " + a + " , " + b + " , " + x0 + " , " + y0
+                            + " , " + Math.toDegrees(thetaD) + " , "
+                            + Math.toDegrees(eta0) + " , " + Math.toDegrees(sweep));
+
+            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+        }
+
+        /**
+         * Converts an arc to cubic Bezier segments and records them in p.
+         *
+         * @param p The target for the cubic Bezier segments
+         * @param cx The x coordinate center of the ellipse
+         * @param cy The y coordinate center of the ellipse
+         * @param a The radius of the ellipse in the horizontal direction
+         * @param b The radius of the ellipse in the vertical direction
+         * @param e1x E(eta1) x coordinate of the starting point of the arc
+         * @param e1y E(eta2) y coordinate of the starting point of the arc
+         * @param theta The angle that the ellipse bounding rectangle makes with the horizontal
+         * plane
+         * @param start The start angle of the arc on the ellipse
+         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+         */
+        private static void arcToBezier(@NonNull Path2D p, double cx, double cy, double a,
+                double b, double e1x, double e1y, double theta, double start,
+                double sweep) {
+            // Taken from equations at:
+            // http://spaceroots.org/documents/ellipse/node8.html
+            // and http://www.spaceroots.org/documents/ellipse/node22.html
+            // Maximum of 45 degrees per cubic Bezier segment
+            int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+
+
+            double eta1 = start;
+            double cosTheta = Math.cos(theta);
+            double sinTheta = Math.sin(theta);
+            double cosEta1 = Math.cos(eta1);
+            double sinEta1 = Math.sin(eta1);
+            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+            double anglePerSegment = sweep / numSegments;
+            for (int i = 0; i < numSegments; i++) {
+                double eta2 = eta1 + anglePerSegment;
+                double sinEta2 = Math.sin(eta2);
+                double cosEta2 = Math.cos(eta2);
+                double e2x = cx + (a * cosTheta * cosEta2)
+                        - (b * sinTheta * sinEta2);
+                double e2y = cy + (a * sinTheta * cosEta2)
+                        + (b * cosTheta * sinEta2);
+                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+                double alpha = Math.sin(eta2 - eta1)
+                        * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+                double q1x = e1x + alpha * ep1x;
+                double q1y = e1y + alpha * ep1y;
+                double q2x = e2x - alpha * ep2x;
+                double q2y = e2y - alpha * ep2y;
+
+                p.curveTo((float) q1x, (float) q1y, (float) q2x, (float) q2y,
+                        (float) e2x, (float) e2y);
+                eta1 = eta2;
+                e1x = e2x;
+                e1y = e2y;
+                ep1x = ep2x;
+                ep1y = ep2y;
+            }
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java b/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
deleted file mode 100644
index 1bd9830..0000000
--- a/tools/layoutlib/bridge/src/android/widget/TimePickerClockDelegate_Delegate.java
+++ /dev/null
@@ -1,44 +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 android.widget;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.view.KeyEvent;
-
-/**
- * Delegate used to provide new implementation of few methods in {@link TimePickerClockDelegate}.
- */
-public class TimePickerClockDelegate_Delegate {
-
-    // Copied from TimePickerClockDelegate.
-    private static final int AM = 0;
-    private static final int PM = 1;
-
-    @LayoutlibDelegate
-    static int getAmOrPmKeyCode(TimePickerClockDelegate tpcd, int amOrPm) {
-        // We don't care about locales here.
-        if (amOrPm == AM) {
-            return KeyEvent.KEYCODE_A;
-        } else if (amOrPm == PM) {
-            return KeyEvent.KEYCODE_P;
-        } else {
-            assert false : "amOrPm value in TimePickerSpinnerDelegate can only be 0 or 1";
-            return -1;
-        }
-    }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 4b89217..17ab2ff5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1382,6 +1382,12 @@
     }
 
     @Override
+    public File getDataDir() {
+        // pass
+        return null;
+    }
+
+    @Override
     public File getFilesDir() {
         // pass
         return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
index 8c60bae..c0ca1d5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
@@ -36,9 +36,9 @@
 /**
  * A Helper class to do fake data binding in {@link AdapterView} objects.
  */
-@SuppressWarnings("deprecation")
 public class AdapterHelper {
 
+    @SuppressWarnings("deprecation")
     static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent,
             LayoutlibCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) {
         // we don't care about recycling here because we never scroll.
diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
new file mode 100644
index 0000000..6246ec1b8
--- /dev/null
+++ b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 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 libcore.util;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of {@link NativeAllocationRegistry}
+ *
+ * Through the layoutlib_create tool, the original native methods of NativeAllocationRegistry have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original NativeAllocationRegistry class.
+ *
+ * @see DelegateManager
+ */
+public class NativeAllocationRegistry_Delegate {
+
+    // ---- delegate manager ----
+    private static final DelegateManager<NativeAllocationRegistry_Delegate> sManager =
+            new DelegateManager<>(NativeAllocationRegistry_Delegate.class);
+
+    private final FreeFunction mFinalizer;
+
+    private NativeAllocationRegistry_Delegate(FreeFunction finalizer) {
+        mFinalizer = finalizer;
+    }
+
+    /**
+     * The result of this method should be cached by the class and reused.
+     */
+    public static long createFinalizer(FreeFunction finalizer) {
+        return sManager.addNewDelegate(new NativeAllocationRegistry_Delegate(finalizer));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) {
+        NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
+        if (delegate != null) {
+            delegate.mFinalizer.free(nativePtr);
+        }
+    }
+
+    public interface FreeFunction {
+        void free(long nativePtr);
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 5eef24a..5c062d0 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -16,10 +16,11 @@
 
 include $(CLEAR_VARS)
 
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+
 # Only compile source java files in this lib.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_JAVACFLAGS := -source 6 -target 6
 
 LOCAL_MODULE := layoutlib-tests
 LOCAL_MODULE_TAGS := optional
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
index 16911bd..8f9fa8a2 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
@@ -75,8 +75,12 @@
             }
         }
         else {
-            BufferedImage goldenImage = ImageIO.read(is);
-            assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
+            try {
+                BufferedImage goldenImage = ImageIO.read(is);
+                assertImageSimilar(relativePath, goldenImage, thumbnail, MAX_PERCENT_DIFFERENCE);
+            } finally {
+                is.close();
+            }
         }
     }
 
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
index c7f2c41..47377ae 100644
--- a/tools/layoutlib/create/Android.mk
+++ b/tools/layoutlib/create/Android.mk
@@ -16,6 +16,8 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 
 LOCAL_JAR_MANIFEST := manifest.txt
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
index 758bd48..01c940a 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java
@@ -176,17 +176,6 @@
             }
         }
 
-        /* Java 7 verifies the StackMapTable of a class if its version number is greater than 50.0.
-         * However, the check is disabled if the class version number is 50.0 or less. Generation
-         * of the StackMapTable requires a rewrite using the tree API of ASM. As a workaround,
-         * we rewrite the version number of the class to be 50.0
-         *
-         * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6693236
-         */
-        if (version > 50) {
-            version = 50;
-        }
-
         super.visit(version, access, name, signature, superName, interfaces);
     }
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index d106592..9e390f6 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -144,11 +144,8 @@
             InjectMethodRunnable.class,
             InjectMethodRunnables.class,
             /* Java package classes */
-            AutoCloseable.class,
-            Objects.class,
             IntegralToString.class,
             UnsafeByteSequence.class,
-            Charsets.class,
             System_Delegate.class,
             LinkedHashMap_Delegate.class,
         };
@@ -169,6 +166,7 @@
         "android.graphics.BitmapFactory#setDensityFromOptions",
         "android.graphics.drawable.GradientDrawable#buildRing",
         "android.graphics.Typeface#getSystemFontConfigLocation",
+        "android.graphics.Typeface#makeFamilyFromParsed",
         "android.os.Handler#sendMessageAtTime",
         "android.os.HandlerThread#run",
         "android.preference.Preference#getView",
@@ -188,6 +186,7 @@
         "android.view.WindowManagerGlobal#getWindowManagerService",
         "android.view.inputmethod.InputMethodManager#getInstance",
         "android.view.MenuInflater#registerMenu",
+        "android.view.RenderNode#getMatrix",
         "android.view.RenderNode#nCreate",
         "android.view.RenderNode#nDestroyRenderNode",
         "android.view.RenderNode#nSetElevation",
@@ -200,7 +199,6 @@
         "android.view.RenderNode#nGetTranslationZ",
         "android.view.RenderNode#nSetRotation",
         "android.view.RenderNode#nGetRotation",
-        "android.view.RenderNode#getMatrix",
         "android.view.RenderNode#nSetLeft",
         "android.view.RenderNode#nSetTop",
         "android.view.RenderNode#nSetRight",
@@ -216,7 +214,6 @@
         "android.view.RenderNode#nGetScaleY",
         "android.view.RenderNode#nIsPivotExplicitlySet",
         "android.view.ViewGroup#drawChild",
-        "android.widget.TimePickerClockDelegate#getAmOrPmKeyCode",
         "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
         "com.android.internal.util.XmlUtils#convertValueToInt",
         "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
@@ -224,6 +221,7 @@
         "libcore.io.MemoryMappedFile#mmapRO",
         "libcore.io.MemoryMappedFile#close",
         "libcore.io.MemoryMappedFile#bigEndianIterator",
+        "libcore.util.NativeAllocationRegistry#applyFreeFunction",
     };
 
     /**
@@ -274,6 +272,7 @@
         "android.os.SystemProperties",
         "android.text.AndroidBidi",
         "android.text.StaticLayout",
+        "android.util.PathParser",
         "android.view.Display",
         "libcore.icu.ICU",
     };
@@ -306,12 +305,13 @@
      */
     private final static String[] JAVA_PKG_CLASSES =
         new String[] {
-            "java.lang.AutoCloseable",                         "com.android.tools.layoutlib.java.AutoCloseable",
-            "java.util.Objects",                               "com.android.tools.layoutlib.java.Objects",
-            "java.nio.charset.Charsets",                       "com.android.tools.layoutlib.java.Charsets",
+            "java.nio.charset.Charsets",                       "java.nio.charset.StandardCharsets",
             "java.lang.IntegralToString",                      "com.android.tools.layoutlib.java.IntegralToString",
             "java.lang.UnsafeByteSequence",                    "com.android.tools.layoutlib.java.UnsafeByteSequence",
-            "java.nio.charset.StandardCharsets",               "com.android.tools.layoutlib.java.Charsets",
+            // Use android.icu.text versions of DateFormat and SimpleDateFormat since the
+            // original ones do not match the Android implementation
+            "java.text.DateFormat",                            "android.icu.text.DateFormat",
+            "java.text.SimpleDateFormat",                      "android.icu.text.SimpleDateFormat"
         };
 
     private final static String[] EXCLUDED_CLASSES =
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
index b5ab738..4ba7237 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -103,6 +103,7 @@
                 mParentVisitor.visitInsn(Opcodes.ICONST_1);
                 mParentVisitor.visitInsn(Opcodes.IRETURN);
                 mParentVisitor.visitLabel(l1);
+                mParentVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
                 mParentVisitor.visitInsn(Opcodes.ICONST_0);
                 break;
             case Type.CHAR:
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index dafb9c6..c59528e8 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -15,6 +15,8 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+
 # Only compile source java files in this lib.
 LOCAL_SRC_FILES := $(call all-java-files-under, com)
 
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 44be671..2373754 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -155,10 +155,6 @@
      * Do not place scans in the chip's scan history buffer
      */
     public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
-    /**
-     * report full scan results and completion event to the context hub
-     */
-    public static final int REPORT_EVENT_CONTEXT_HUB = (1 << 3);
 
     /**
      * scan configuration parameters to be sent to {@link #startBackgroundScan}