Merge "Move framework-specific portions of pathmap.mk into frameworks/base"
diff --git a/api/current.txt b/api/current.txt
index 6e054fc..693d512 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -298,6 +298,7 @@
     field public static final int authorities = 16842776; // 0x1010018
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
     field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+    field public static final int autoFillHint = 16844121; // 0x1010559
     field public static final int autoFillMode = 16844116; // 0x1010554
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -5519,12 +5520,14 @@
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
     ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+    ctor public NotificationChannelGroup(java.lang.String, int);
     ctor protected NotificationChannelGroup(android.os.Parcel);
     method public android.app.NotificationChannelGroup clone();
     method public int describeContents();
     method public java.util.List<android.app.NotificationChannel> getChannels();
     method public java.lang.String getId();
     method public java.lang.CharSequence getName();
+    method public int getNameResId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
   }
@@ -6554,6 +6557,7 @@
 
   public static class AssistStructure.ViewNode {
     method public float getAlpha();
+    method public int getAutoFillHint();
     method public android.view.autofill.AutoFillId getAutoFillId();
     method public java.lang.String[] getAutoFillOptions();
     method public android.view.autofill.AutoFillType getAutoFillType();
@@ -8451,8 +8455,10 @@
     field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
     field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
     field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+    field public static final java.lang.String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
     field public static final java.lang.String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
     field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+    field public static final java.lang.String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
     field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
     field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
     field public static final java.lang.String QUERY_ARG_LIMIT = "android:query-page-limit";
@@ -8463,7 +8469,6 @@
     field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
     field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
     field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
-    field public static final java.lang.String QUERY_RESULT_SIZE = "android:query-result-size";
     field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
     field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
     field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
@@ -44822,6 +44827,7 @@
     method public float getAlpha();
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
+    method public int getAutoFillHint();
     method public int getAutoFillMode();
     method public android.view.autofill.AutoFillType getAutoFillType();
     method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -45139,6 +45145,7 @@
     method public void setActivated(boolean);
     method public void setAlpha(float);
     method public void setAnimation(android.view.animation.Animation);
+    method public void setAutoFillHint(int);
     method public void setAutoFillMode(int);
     method public void setBackground(android.graphics.drawable.Drawable);
     method public void setBackgroundColor(int);
@@ -45280,6 +45287,20 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+    field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+    field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+    field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+    field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+    field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+    field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+    field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+    field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
     field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
     field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
     field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -45932,6 +45953,7 @@
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
+    method public abstract void setAutoFillHint(int);
     method public abstract void setAutoFillOptions(java.lang.String[]);
     method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
     method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -47203,9 +47225,8 @@
     method public int describeContents();
     method public static android.view.autofill.AutoFillType forDate();
     method public static android.view.autofill.AutoFillType forList();
-    method public static android.view.autofill.AutoFillType forText(int);
+    method public static android.view.autofill.AutoFillType forText();
     method public static android.view.autofill.AutoFillType forToggle();
-    method public int getSubType();
     method public boolean isDate();
     method public boolean isList();
     method public boolean isText();
diff --git a/api/system-current.txt b/api/system-current.txt
index cfcc40a..f095936 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -410,6 +410,7 @@
     field public static final int authorities = 16842776; // 0x1010018
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
     field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+    field public static final int autoFillHint = 16844121; // 0x1010559
     field public static final int autoFillMode = 16844116; // 0x1010554
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -5712,6 +5713,7 @@
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
     ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+    ctor public NotificationChannelGroup(java.lang.String, int);
     ctor protected NotificationChannelGroup(android.os.Parcel);
     method public void addChannel(android.app.NotificationChannel);
     method public android.app.NotificationChannelGroup clone();
@@ -5719,6 +5721,7 @@
     method public java.util.List<android.app.NotificationChannel> getChannels();
     method public java.lang.String getId();
     method public java.lang.CharSequence getName();
+    method public int getNameResId();
     method public org.json.JSONObject toJson() throws org.json.JSONException;
     method public void writeToParcel(android.os.Parcel, int);
     method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
@@ -6793,6 +6796,7 @@
 
   public static class AssistStructure.ViewNode {
     method public float getAlpha();
+    method public int getAutoFillHint();
     method public android.view.autofill.AutoFillId getAutoFillId();
     method public java.lang.String[] getAutoFillOptions();
     method public android.view.autofill.AutoFillType getAutoFillType();
@@ -8941,8 +8945,10 @@
     field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
     field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
     field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+    field public static final java.lang.String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
     field public static final java.lang.String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
     field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+    field public static final java.lang.String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
     field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
     field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
     field public static final java.lang.String QUERY_ARG_LIMIT = "android:query-page-limit";
@@ -8953,7 +8959,6 @@
     field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
     field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
     field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
-    field public static final java.lang.String QUERY_RESULT_SIZE = "android:query-result-size";
     field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
     field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
     field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
@@ -48208,6 +48213,7 @@
     method public float getAlpha();
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
+    method public int getAutoFillHint();
     method public int getAutoFillMode();
     method public android.view.autofill.AutoFillType getAutoFillType();
     method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -48525,6 +48531,7 @@
     method public void setActivated(boolean);
     method public void setAlpha(float);
     method public void setAnimation(android.view.animation.Animation);
+    method public void setAutoFillHint(int);
     method public void setAutoFillMode(int);
     method public void setBackground(android.graphics.drawable.Drawable);
     method public void setBackgroundColor(int);
@@ -48666,6 +48673,20 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+    field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+    field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+    field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+    field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+    field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+    field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+    field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+    field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
     field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
     field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
     field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -49318,6 +49339,7 @@
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
+    method public abstract void setAutoFillHint(int);
     method public abstract void setAutoFillOptions(java.lang.String[]);
     method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
     method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -50592,9 +50614,8 @@
     method public int describeContents();
     method public static android.view.autofill.AutoFillType forDate();
     method public static android.view.autofill.AutoFillType forList();
-    method public static android.view.autofill.AutoFillType forText(int);
+    method public static android.view.autofill.AutoFillType forText();
     method public static android.view.autofill.AutoFillType forToggle();
-    method public int getSubType();
     method public boolean isDate();
     method public boolean isList();
     method public boolean isText();
diff --git a/api/test-current.txt b/api/test-current.txt
index 478a3ed..4585b8e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -298,6 +298,7 @@
     field public static final int authorities = 16842776; // 0x1010018
     field public static final int autoAdvanceViewId = 16843535; // 0x101030f
     field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+    field public static final int autoFillHint = 16844121; // 0x1010559
     field public static final int autoFillMode = 16844116; // 0x1010554
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -5529,12 +5530,14 @@
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
     ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+    ctor public NotificationChannelGroup(java.lang.String, int);
     ctor protected NotificationChannelGroup(android.os.Parcel);
     method public android.app.NotificationChannelGroup clone();
     method public int describeContents();
     method public java.util.List<android.app.NotificationChannel> getChannels();
     method public java.lang.String getId();
     method public java.lang.CharSequence getName();
+    method public int getNameResId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
   }
@@ -6264,6 +6267,7 @@
     method public long getMaximumTimeToLock(android.content.ComponentName);
     method public int getOrganizationColor(android.content.ComponentName);
     method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
+    method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(android.os.UserHandle);
     method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
     method public long getPasswordExpiration(android.content.ComponentName);
     method public long getPasswordExpirationTimeout(android.content.ComponentName);
@@ -6580,6 +6584,7 @@
 
   public static class AssistStructure.ViewNode {
     method public float getAlpha();
+    method public int getAutoFillHint();
     method public android.view.autofill.AutoFillId getAutoFillId();
     method public java.lang.String[] getAutoFillOptions();
     method public android.view.autofill.AutoFillType getAutoFillType();
@@ -8478,8 +8483,10 @@
     field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
     field public static final java.lang.String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
     field public static final java.lang.String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
+    field public static final java.lang.String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
     field public static final java.lang.String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
     field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
+    field public static final java.lang.String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
     field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
     field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
     field public static final java.lang.String QUERY_ARG_LIMIT = "android:query-page-limit";
@@ -8490,7 +8497,6 @@
     field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
     field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
     field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
-    field public static final java.lang.String QUERY_RESULT_SIZE = "android:query-result-size";
     field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
     field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
     field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
@@ -45177,6 +45183,7 @@
     method public float getAlpha();
     method public android.view.animation.Animation getAnimation();
     method public android.os.IBinder getApplicationWindowToken();
+    method public int getAutoFillHint();
     method public int getAutoFillMode();
     method public android.view.autofill.AutoFillType getAutoFillType();
     method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -45497,6 +45504,7 @@
     method public void setActivated(boolean);
     method public void setAlpha(float);
     method public void setAnimation(android.view.animation.Animation);
+    method public void setAutoFillHint(int);
     method public void setAutoFillMode(int);
     method public void setBackground(android.graphics.drawable.Drawable);
     method public void setBackgroundColor(int);
@@ -45638,6 +45646,20 @@
     field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
     field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
     field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+    field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+    field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+    field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+    field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+    field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+    field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+    field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+    field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+    field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
     field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
     field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
     field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -46294,6 +46316,7 @@
     method public abstract void setAccessibilityFocused(boolean);
     method public abstract void setActivated(boolean);
     method public abstract void setAlpha(float);
+    method public abstract void setAutoFillHint(int);
     method public abstract void setAutoFillOptions(java.lang.String[]);
     method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
     method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -47567,9 +47590,8 @@
     method public int describeContents();
     method public static android.view.autofill.AutoFillType forDate();
     method public static android.view.autofill.AutoFillType forList();
-    method public static android.view.autofill.AutoFillType forText(int);
+    method public static android.view.autofill.AutoFillType forText();
     method public static android.view.autofill.AutoFillType forToggle();
-    method public int getSubType();
     method public boolean isDate();
     method public boolean isList();
     method public boolean isText();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8538330..dad2061 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -522,6 +522,7 @@
     void startLocalVoiceInteraction(in IBinder token, in Bundle options);
     void stopLocalVoiceInteraction(in IBinder token);
     boolean supportsLocalVoiceInteraction();
+    void notifyPinnedStackAnimationStarted();
     void notifyPinnedStackAnimationEnded();
     void removeStack(int stackId);
     void makePackageIdle(String packageName, int userId);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 9834350..d8d4bb9 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -38,6 +38,11 @@
     void onPinnedActivityRestartAttempt(String launchedFromPackage);
 
     /**
+     * Called whenever the pinned stack is starting animating a resize.
+     */
+    void onPinnedStackAnimationStarted();
+
+    /**
      * Called whenever the pinned stack is done animating a resize.
      */
     void onPinnedStackAnimationEnded();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 1c33e38..7cdd45f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -286,7 +286,7 @@
                 final String apkName = path.substring(path.lastIndexOf(File.separator));
                 boolean match = false;
                 for (String oldPath : oldPaths) {
-                    final String oldApkName = oldPath.substring(path.lastIndexOf(File.separator));
+                    final String oldApkName = oldPath.substring(oldPath.lastIndexOf(File.separator));
                     if (apkName.equals(oldApkName)) {
                         match = true;
                         break;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 34a45dd6..3fc459e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4177,7 +4177,7 @@
             }
 
             RemoteViews header = makeNotificationHeader();
-
+            header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
             if (summary != null) {
                 mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
             } else {
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 8854adc..288d39a 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -15,6 +15,7 @@
  */
 package android.app;
 
+import android.annotation.StringRes;
 import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Parcel;
@@ -40,23 +41,38 @@
 
     private static final String TAG_GROUP = "channelGroup";
     private static final String ATT_NAME = "name";
+    private static final String ATT_NAME_RES_ID = "name_res_id";
     private static final String ATT_ID = "id";
 
     private final String mId;
     private CharSequence mName;
+    private int mNameResId = 0;
     private List<NotificationChannel> mChannels = new ArrayList<>();
 
     /**
      * Creates a notification channel.
      *
      * @param id The id of the group. Must be unique per package.
-     * @param name The user visible name of the group.
+     * @param name The user visible name of the group. Unchangeable once created; use this
+     *             constructor if the group represents something user-defined that does not
+     *             need to be translated.
      */
     public NotificationChannelGroup(String id, CharSequence name) {
         this.mId = id;
         this.mName = name;
     }
 
+    /**
+     * Creates a notification channel.
+     *
+     * @param id The id of the group. Must be unique per package.
+     * @param nameResId String resource id of the user visible name of the group.
+     */
+    public NotificationChannelGroup(String id, @StringRes int nameResId) {
+        this.mId = id;
+        this.mNameResId = nameResId;
+    }
+
     protected NotificationChannelGroup(Parcel in) {
         if (in.readByte() != 0) {
             mId = in.readString();
@@ -64,6 +80,7 @@
             mId = null;
         }
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mNameResId = in.readInt();
         in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
     }
 
@@ -76,6 +93,7 @@
             dest.writeByte((byte) 0);
         }
         TextUtils.writeToParcel(mName, dest, flags);
+        dest.writeInt(mNameResId);
         dest.writeParcelableList(mChannels, flags);
     }
 
@@ -93,6 +111,13 @@
         return mName;
     }
 
+    /**
+     * Returns the resource id of the user visible name of this group.
+     */
+    public @StringRes int getNameResId() {
+        return mNameResId;
+    }
+
     /*
      * Returns the list of channels that belong to this group
      *
@@ -119,7 +144,12 @@
         out.startTag(null, TAG_GROUP);
 
         out.attribute(null, ATT_ID, getId());
-        out.attribute(null, ATT_NAME, getName().toString());
+        if (getName() != null) {
+            out.attribute(null, ATT_NAME, getName().toString());
+        }
+        if (getNameResId() != 0) {
+            out.attribute(null, ATT_NAME_RES_ID, Integer.toString(getNameResId()));
+        }
 
         out.endTag(null, TAG_GROUP);
     }
@@ -132,6 +162,7 @@
         JSONObject record = new JSONObject();
         record.put(ATT_ID, getId());
         record.put(ATT_NAME, getName());
+        record.put(ATT_NAME_RES_ID, getNameResId());
         return record;
     }
 
@@ -160,6 +191,7 @@
 
         NotificationChannelGroup that = (NotificationChannelGroup) o;
 
+        if (getNameResId() != that.getNameResId()) return false;
         if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
         if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
             return false;
@@ -171,13 +203,18 @@
 
     @Override
     public NotificationChannelGroup clone() {
-        return new NotificationChannelGroup(getId(), getName());
+        if (getName() != null) {
+            return new NotificationChannelGroup(getId(), getName());
+        } else {
+            return new NotificationChannelGroup(getId(), getNameResId());
+        }
     }
 
     @Override
     public int hashCode() {
         int result = getId() != null ? getId().hashCode() : 0;
         result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+        result = 31 * result + getNameResId();
         result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0);
         return result;
     }
@@ -187,6 +224,8 @@
         return "NotificationChannelGroup{" +
                 "mId='" + mId + '\'' +
                 ", mName=" + mName +
+                ", mNameResId=" + mNameResId +
+                ", mChannels=" + mChannels +
                 '}';
     }
 }
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 5a0845f..7a569fc 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -39,6 +39,10 @@
     }
 
     @Override
+    public void onPinnedStackAnimationStarted() throws RemoteException {
+    }
+
+    @Override
     public void onPinnedStackAnimationEnded() throws RemoteException {
     }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6585793..449cca3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7958,4 +7958,28 @@
             throw re.rethrowFromSystemServer();
         }
     }
+
+
+    /**
+     * Called by the system to get a list of CA certificates that were installed by the device or
+     * profile owner.
+     *
+     * <p> The caller must be the target user's Device Owner/Profile owner or hold the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+     *
+     * @param user The user for whom to retrieve information.
+     * @return list of aliases identifying CA certificates installed by the device or profile owner
+     * @throws SecurityException if the caller does not have permission to retrieve information
+     *         about the given user's CA certificates.
+     *
+     * @hide
+     */
+    @TestApi
+    public List<String> getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+        try {
+            return mService.getOwnerInstalledCaCerts(user).getList();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ec97c2c..97a4678 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.StringParceledListSlice;
 import android.graphics.Bitmap;
 import android.net.ProxyInfo;
 import android.net.Uri;
@@ -349,4 +350,5 @@
     boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags);
 
     boolean isDefaultInputMethodSetByOwner(in UserHandle user);
+    StringParceledListSlice getOwnerInstalledCaCerts(in UserHandle user);
 }
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index d1b2a15..a1ef4a6 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -539,6 +539,7 @@
         // fields (viewId and childId) of the field.
         AutoFillId mAutoFillId;
         AutoFillType mAutoFillType;
+        @View.AutoFillHint int mAutoFillHint;
         AutoFillValue mAutoFillValue;
         String[] mAutoFillOptions;
         boolean mSanitized;
@@ -623,6 +624,7 @@
                 mSanitized = in.readInt() == 1;
                 mAutoFillId = in.readParcelable(null);
                 mAutoFillType = in.readParcelable(null);
+                mAutoFillHint = in.readInt();
                 mAutoFillValue = in.readParcelable(null);
                 mAutoFillOptions = in.readStringArray();
             }
@@ -756,6 +758,7 @@
                 out.writeInt(mSanitized ? 1 : 0);
                 out.writeParcelable(mAutoFillId, 0);
                 out.writeParcelable(mAutoFillType,  0);
+                out.writeInt(mAutoFillHint);
                 final AutoFillValue sanitizedValue = writeSensitive ? mAutoFillValue : null;
                 out.writeParcelable(sanitizedValue,  0);
                 out.writeStringArray(mAutoFillOptions);
@@ -859,6 +862,20 @@
         }
 
         /**
+         * Describes the content of a view so that a auto-fill service can fill in the appropriate
+         * data.
+         *
+         * <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
+         * for assist.</p>
+         *
+         * @return The hint for this view
+         */
+        // TODO(b/35364993): add CTS/unit test
+        @View.AutoFillHint public int getAutoFillHint() {
+            return mAutoFillHint;
+        }
+
+        /**
          * Gets the the value of this view.
          *
          * <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
@@ -1549,6 +1566,11 @@
         }
 
         @Override
+        public void setAutoFillHint(@View.AutoFillHint int hint) {
+            mNode.mAutoFillHint = hint;
+        }
+
+        @Override
         public void setAutoFillValue(AutoFillValue value) {
             mNode.mAutoFillValue = value;
         }
@@ -1695,6 +1717,7 @@
                     + ", type=" + node.getAutoFillType()
                     + ", options=" + Arrays.toString(node.getAutoFillOptions())
                     + ", inputType=" + node.getInputType()
+                    + ", hint=" + Integer.toHexString(node.getAutoFillHint())
                     + ", value=" + node.getAutoFillValue()
                     + ", sanitized=" + node.isSanitized());
         }
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index 4b6f99b..1d5c2b0 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -24,8 +24,10 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Objects;
+
 /**
- * CacheQuotaRequest represents a triplet of a uid, the volume UUID it is stored upon, and
+ * CacheQuotaHint represents a triplet of a uid, the volume UUID it is stored upon, and
  * its usage stats. When processed, it obtains a cache quota as defined by the system which
  * allows apps to understand how much cache to use.
  * {@hide}
@@ -78,6 +80,23 @@
         return 0;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof CacheQuotaHint) {
+            final CacheQuotaHint other = (CacheQuotaHint) o;
+            return Objects.equals(mUuid, other.mUuid)
+                    && Objects.equals(mUsageStats, other.mUsageStats)
+                    && mUid == other.mUid && mQuota == other.mQuota;
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.mUuid, this.mUid, this.mUsageStats, this.mQuota);
+    }
+
     public static final class Builder {
         private String mUuid;
         private int mUid;
@@ -100,7 +119,7 @@
         }
 
         public @NonNull Builder setUid(int uid) {
-            Preconditions.checkArgumentPositive(uid, "Proposed uid was not positive.");
+            Preconditions.checkArgumentNonnegative(uid, "Proposed uid was negative.");
             mUid = uid;
             return this;
         }
@@ -117,7 +136,6 @@
         }
 
         public @NonNull CacheQuotaHint build() {
-            Preconditions.checkNotNull(mUsageStats);
             return new CacheQuotaHint(this);
         }
     }
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 4252482..7d8459c 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -50,6 +50,12 @@
     public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
             "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
 
+    /* Extras used in ACTION_MESSAGE_RECEIVED intent */
+    public static final String EXTRA_SENDER_CONTACT_URI =
+            "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
+    public static final String EXTRA_SENDER_CONTACT_NAME =
+            "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
+
     private IBluetoothMapClient mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 9958a79..5579c9a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -307,6 +307,18 @@
      */
     public static final String QUERY_ARG_SORT_COLLATION = "android:query-sort-collation";
 
+    /**
+     * Allows provider to report back to client which keys were honored.
+     *
+     * Key identifying a {@code String[]} containing all QUERY_ARG_SORT* arguments
+     * honored by the provider. Include this in {@link Cursor} extras {@link Bundle}
+     * when any QUERY_ARG_SORT* value was honored during the preparation of the
+     * results {@link Cursor}.
+     *
+     * @see #QUERY_ARG_SORT_COLUMNS, #QUERY_ARG_SORT_DIRECTION, #QUERY_ARG_SORT_COLLATION.
+     */
+    public static final String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
+
     /** @hide */
     @IntDef(flag = false, value = {
             QUERY_SORT_DIRECTION_ASCENDING,
@@ -354,8 +366,9 @@
     public static final String QUERY_ARG_LIMIT = "android:query-page-limit";
 
     /**
-     * Added to {@link Cursor} extras {@link Bundle} to indicate size of the
-     * full, un-offset, un-limited recordset.
+     * Added to {@link Cursor} extras {@link Bundle} to indicate total size of
+     * recordset when paging is active. Providers must include this when
+     * implementing paging support.
      *
      * <p>When full size of the recordset is unknown a provider may return -1
      * to indicate this.
@@ -364,7 +377,7 @@
      * send content change notification once (if) full recordset size becomes
      * known.
      */
-    public static final String QUERY_RESULT_SIZE = "android:query-result-size";
+    public static final String EXTRA_TOTAL_SIZE = "android.content.extra.TOTAL_SIZE";
 
     /**
      * This is the Android platform's base MIME type for a content: URI
@@ -704,6 +717,13 @@
      * <li>Provide an explicit projection, to prevent reading data from storage
      * that aren't going to be used.
      *
+     * Provider must identify which QUERY_ARG_SORT* arguments were honored during
+     * the preparation of the result set by including the respective argument keys
+     * in the {@link Cursor} extras {@link Bundle}. See {@link #EXTRA_HONORED_ARGS}
+     * for details.
+     *
+     * @see #QUERY_ARG_SORT_COLUMNS, #QUERY_ARG_SORT_DIRECTION, #QUERY_ARG_SORT_COLLATION.
+     *
      * @param uri The URI, using the content:// scheme, for the content to
      *         retrieve.
      * @param projection A list of which columns to return. Passing null will
@@ -3037,17 +3057,19 @@
             query += " COLLATE NOCASE";
         }
 
-        switch (queryArgs.getInt(
-                QUERY_ARG_SORT_DIRECTION, Integer.MIN_VALUE)) {
-            case QUERY_SORT_DIRECTION_ASCENDING:
-                query += " ASC";
-                break;
-            case QUERY_SORT_DIRECTION_DESCENDING:
-                query += " DESC";
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported sort direction value."
-                        + " See ContentResolver documentation for details.");
+        int sortDir = queryArgs.getInt(QUERY_ARG_SORT_DIRECTION, Integer.MIN_VALUE);
+        if (sortDir != Integer.MIN_VALUE) {
+            switch (sortDir) {
+                case QUERY_SORT_DIRECTION_ASCENDING:
+                    query += " ASC";
+                    break;
+                case QUERY_SORT_DIRECTION_DESCENDING:
+                    query += " DESC";
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported sort direction value."
+                            + " See ContentResolver documentation for details.");
+            }
         }
         return query;
     }
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 71a0349..33b5903 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -119,6 +119,9 @@
      * <p>Location should specify a document URI or a tree URI with document ID. If
      * this URI identifies a non-directory, document navigator will attempt to use the parent
      * of the document as the initial location.
+     *
+     * <p>The initial location is system specific if this extra is missing or document navigator
+     * failed to locate the desired initial location.
      */
     public static final String EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI";
 
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index c3d3f39..16d4666 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -94,6 +94,7 @@
             }
         }
     };
+    private boolean mAcceptAllTouches;
 
     public NotificationHeaderView(Context context) {
         this(context, null);
@@ -374,6 +375,8 @@
                 case MotionEvent.ACTION_DOWN:
                     mTrackGesture = false;
                     if (isInside(x, y)) {
+                        mDownX = x;
+                        mDownY = y;
                         mTrackGesture = true;
                         return true;
                     }
@@ -396,11 +399,12 @@
         }
 
         private boolean isInside(float x, float y) {
+            if (mAcceptAllTouches) {
+                return true;
+            }
             for (int i = 0; i < mTouchRects.size(); i++) {
                 Rect r = mTouchRects.get(i);
                 if (r.contains((int) x, (int) y)) {
-                    mDownX = x;
-                    mDownY = y;
                     return true;
                 }
             }
@@ -433,4 +437,9 @@
         }
         return mTouchListener.isInside(x, y);
     }
+
+    @RemotableViewMethod
+    public void setAcceptAllTouches(boolean acceptAllTouches) {
+        mAcceptAllTouches = acceptAllTouches;
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e349170..99051a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -974,6 +974,143 @@
      */
     public static final int AUTO_FILL_MODE_MANUAL = 2;
 
+    /** @hide */
+    @IntDef({
+            AUTO_FILL_HINT_NONE,
+            AUTO_FILL_HINT_EMAIL_ADDRESS,
+            AUTO_FILL_HINT_NAME,
+            AUTO_FILL_HINT_POSTAL_ADDRESS,
+            AUTO_FILL_HINT_PASSWORD,
+            AUTO_FILL_HINT_PHONE,
+            AUTO_FILL_HINT_USERNAME,
+            AUTO_FILL_HINT_POSTAL_CODE,
+            AUTO_FILL_HINT_CREDIT_CARD_NUMBER,
+            AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE,
+            AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE,
+            AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH,
+            AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR,
+            AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AutoFillHint {}
+
+    /**
+     * No auto-fill hint is set.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_NONE = 0;
+
+    /**
+     * This view contains an email address.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 0x1;
+
+    /**
+     * The view contains a real name.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_NAME = 0x2;
+
+    /**
+     * The view contains a user name.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_USERNAME = 0x4;
+
+    /**
+     * The view contains a password.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_PASSWORD = 0x8;
+
+    /**
+     * The view contains a phone number.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_PHONE = 0x10;
+
+    /**
+     * The view contains a postal address.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 0x20;
+
+    /**
+     * The view contains a postal code.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_POSTAL_CODE = 0x40;
+
+    /**
+     * The view contains a credit card number.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 0x80;
+
+    /**
+     * The view contains a credit card security code.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 0x100;
+
+    /**
+     * The view contains a credit card expiration date.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 0x200;
+
+    /**
+     * The view contains the month a credit card expires.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 0x400;
+
+    /**
+     * The view contains the year a credit card expires.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 0x800;
+
+    /**
+     * The view contains the day a credit card expires.
+     *
+     * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+     * {@code android:autoFillHint}.
+     */
+    public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 0x1000;
+
+    /**
+     * Hint for the auto-fill services that describes the content of the view.
+     */
+    @AutoFillHint private int mAutoFillHint;
+
     /**
      * This view is enabled. Interpretation varies by subclass.
      * Use with ENABLED_MASK when calling setFlags.
@@ -4799,11 +4936,16 @@
                         setFocusedByDefault(a.getBoolean(attr, true));
                     }
                     break;
-                case com.android.internal.R.styleable.View_autoFillMode:
+                case R.styleable.View_autoFillMode:
                     if (a.peekValue(attr) != null) {
                         setAutoFillMode(a.getInt(attr, AUTO_FILL_MODE_INHERIT));
                     }
                     break;
+                case R.styleable.View_autoFillHint:
+                    if (a.peekValue(attr) != null) {
+                        setAutoFillHint(a.getInt(attr, AUTO_FILL_HINT_NONE));
+                    }
+                    break;
             }
         }
 
@@ -7035,6 +7177,7 @@
                 // to reuse the accessibility id to save space.
                 structure.setAutoFillId(getAccessibilityViewId());
                 structure.setAutoFillType(autoFillType);
+                structure.setAutoFillHint(getAutoFillHint());
                 structure.setAutoFillValue(getAutoFillValue());
             }
         }
@@ -7178,7 +7321,7 @@
     /**
      * Describes the auto-fill type that should be used on calls to
      * {@link #autoFill(AutoFillValue)} and {@link #autoFillVirtual(int, AutoFillValue)}.
-
+     *
      * <p>By default returns {@code null}, but views should override it (and
      * {@link #autoFill(AutoFillValue)} to support the AutoFill Framework.
      */
@@ -7188,9 +7331,21 @@
     }
 
     /**
+     * Describes the content of a view so that a auto-fill service can fill in the appropriate data.
+     *
+     * @return The hint set via the attribute
+     *
+     * @attr ref android.R.styleable#View_autoFillHint
+     */
+    @ViewDebug.ExportedProperty()
+    @AutoFillHint public int getAutoFillHint() {
+        return mAutoFillHint;
+    }
+
+    /**
      * Gets the {@link View}'s current auto-fill value.
      *
-     * <p>By default returns {@code null}, but views should override it,
+     * <p>By default returns {@code null}, but views should override it (and
      * {@link #autoFill(AutoFillValue)}, and {@link #getAutoFillType()} to support the AutoFill
      * Framework.
      */
@@ -8698,6 +8853,17 @@
     }
 
     /**
+     * Sets the a hint that helps the auto-fill service to select the appropriate data to fill the
+     * view.
+     *
+     * @param autoFillHint The auto-fill hint to set
+     * @attr ref android.R.styleable#View_autoFillHint
+     */
+    public void setAutoFillHint(@AutoFillHint int autoFillHint) {
+        mAutoFillHint = autoFillHint;
+    }
+
+    /**
      * Set whether this view should have sound effects enabled for events such as
      * clicking and touching.
      *
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 43560f0..bc0960c 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -301,6 +301,13 @@
     public abstract void setAutoFillType(AutoFillType info);
 
     /**
+     * Sets the a hint that helps the auto-fill service to select the appropriate data to fill the
+     * view.
+     */
+    // TODO(b/35364993): add CTS/unit test
+    public abstract void setAutoFillHint(@View.AutoFillHint int hint);
+
+    /**
      * Sets the {@link AutoFillValue} representing the current value of this node.
      */
     // TODO(b/33197203, b/33802548): add CTS/unit test
diff --git a/core/java/android/view/autofill/AutoFillType.java b/core/java/android/view/autofill/AutoFillType.java
index e974705..62913d8 100644
--- a/core/java/android/view/autofill/AutoFillType.java
+++ b/core/java/android/view/autofill/AutoFillType.java
@@ -21,16 +21,11 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.View;
-import android.widget.TextView;
 
 /**
  * Defines the type of a object that can be used to auto-fill a {@link View} so the
  * {@link android.service.autofill.AutoFillService} can use the proper {@link AutoFillValue} to
  * fill it.
- *
- * <p>Some {@link AutoFillType}s can have an optional {@code sub-type}: the
- * main {@code type} defines the view's UI control category (like a text field), while the optional
- * {@code sub-type} define its semantics (like a postal address).
  */
 public final class AutoFillType implements Parcelable {
 
@@ -38,9 +33,10 @@
     // class idiom" (Effective Java, Item 71) to avoid memory utilization when auto-fill is not
     // enabled.
     private static class DefaultTypesHolder {
-        static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE, 0);
-        static final AutoFillType LIST = new AutoFillType(TYPE_LIST, 0);
-        static final AutoFillType DATE = new AutoFillType(TYPE_DATE, 0);
+        static final AutoFillType TEXT = new AutoFillType(TYPE_TEXT);
+        static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE);
+        static final AutoFillType LIST = new AutoFillType(TYPE_LIST);
+        static final AutoFillType DATE = new AutoFillType(TYPE_DATE);
     }
 
     private static final int TYPE_TEXT = 1;
@@ -49,11 +45,9 @@
     private static final int TYPE_DATE = 4;
 
     private final int mType;
-    private final int mSubType;
 
-    private AutoFillType(int type, int subType) {
+    private AutoFillType(int type) {
         mType = type;
-        mSubType = subType;
     }
 
     /**
@@ -62,8 +56,6 @@
      * <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
      * {@link AutoFillValue#forText(CharSequence)}, and the value passed to auto-fill a
      * {@link View} can be fetched through {@link AutoFillValue#getTextValue()}.
-     *
-     * <p>Sub-type for this type is the value defined by {@link TextView#getInputType()}.
      */
     public boolean isText() {
         return mType == TYPE_TEXT;
@@ -75,8 +67,6 @@
      * <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
      * {@link AutoFillValue#forToggle(boolean)}, and the value passed to auto-fill a
      * {@link View} can be fetched through {@link AutoFillValue#getToggleValue()}.
-     *
-     * <p>This type has no sub-types.
      */
     public boolean isToggle() {
         return mType == TYPE_TOGGLE;
@@ -89,8 +79,6 @@
      * <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
      * {@link AutoFillValue#forList(int)}, and the value passed to auto-fill a
      * {@link View} can be fetched through {@link AutoFillValue#getListValue()}.
-     *
-     * <p>This type has no sub-types.
      */
     public boolean isList() {
         return mType == TYPE_LIST;
@@ -111,15 +99,6 @@
         return mType == TYPE_DATE;
     }
 
-    /**
-     * Gets the optional sub-type, representing the {@link View}'s semantic.
-     *
-     * @return {@code 0} if type does not support sub-types.
-     */
-    public int getSubType() {
-        return mSubType;
-    }
-
     /////////////////////////////////////
     //  Object "contract" methods. //
     /////////////////////////////////////
@@ -128,14 +107,13 @@
     public String toString() {
         if (!DEBUG) return super.toString();
 
-        return "AutoFillType [type=" + mType + ", subType=" + mSubType + "]";
+        return "AutoFillType [type=" + mType + "]";
     }
 
     @Override
     public int hashCode() {
         final int prime = 31;
         int result = 1;
-        result = prime * result + mSubType;
         result = prime * result + mType;
         return result;
     }
@@ -146,7 +124,6 @@
         if (obj == null) return false;
         if (getClass() != obj.getClass()) return false;
         final AutoFillType other = (AutoFillType) obj;
-        if (mSubType != other.mSubType) return false;
         if (mType != other.mType) return false;
         return true;
     }
@@ -163,12 +140,10 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mType);
-        parcel.writeInt(mSubType);
     }
 
     private AutoFillType(Parcel parcel) {
         mType = parcel.readInt();
-        mSubType = parcel.readInt();
     }
 
     public static final Parcelable.Creator<AutoFillType> CREATOR =
@@ -193,8 +168,8 @@
      *
      * <p>See {@link #isText()} for more info.
      */
-    public static AutoFillType forText(int inputType) {
-        return new AutoFillType(TYPE_TEXT, inputType);
+    public static AutoFillType forText() {
+        return DefaultTypesHolder.TEXT;
     }
 
     /**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3d10917..97fc835 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10019,7 +10019,7 @@
     @Override
     @Nullable
     public AutoFillType getAutoFillType() {
-        return isTextEditable() ? AutoFillType.forText(getInputType()) : null;
+        return isTextEditable() ? AutoFillType.forText() : null;
     }
 
     @Override
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c137ae2..55b8884 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2304,6 +2304,39 @@
             <enum name="manual" value="2" />
         </attr>
 
+        <!-- Describes the content of a view so that a auto-fill service can fill in the appropriate
+             data. Multiple flags can be combined to mean e.g. emailAddress or postalAddress. -->
+        <attr name="autoFillHint">
+            <!-- No hint. -->
+            <flag name="none" value="0" />
+            <!-- The view contains an email address. -->
+            <flag name="emailAddress" value="0x1" />
+            <!-- The view contains a real name. -->
+            <flag name="name" value="0x2" />
+            <!-- The view contains a user name. -->
+            <flag name="username" value="0x4" />
+            <!-- The view contains a password. -->
+            <flag name="password" value="0x8" />
+            <!-- The view contains a phone number. -->
+            <flag name="phone" value="0x10" />
+            <!-- The view contains a postal address. -->
+            <flag name="postalAddress" value="0x20" />
+            <!-- The view contains a postal code. -->
+            <flag name="postalCode" value="0x40" />
+            <!-- The view contains a credit card number. -->
+            <flag name="creditCardNumber" value="0x80" />
+            <!-- The view contains a credit card security code -->
+            <flag name="creditCardSecurityCode" value="0x100" />
+            <!-- The view contains a credit card expiration date -->
+            <flag name="creditCardExpirationDate" value="0x200" />
+            <!-- The view contains the month a credit card expires -->
+            <flag name="creditCardExpirationMonth" value="0x400" />
+            <!-- The view contains the year a credit card expires -->
+            <flag name="creditCardExpirationYear" value="0x800" />
+            <!-- The view contains the day a credit card expires -->
+            <flag name="creditCardExpirationDay" value="0x1000" />
+        </attr>
+
         <!-- Boolean that controls whether a view can take focus while in touch mode.
              If this is true for a view, that view can gain focus when clicked on, and can keep
              focus if another view is clicked on that doesn't have this attribute set to true. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 15764a9..df3962c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2801,6 +2801,7 @@
         <public name="secondaryContentAlpha" />
         <public name="requiredFeature" />
         <public name="requiredNotFeature" />
+        <public name="autoFillHint" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index b685431..c4bb72c 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -29,8 +29,8 @@
     byte[] getCertificate(String alias);
     byte[] getCaCertificates(String alias);
 
-    // APIs used by CertInstaller
-    void installCaCertificate(in byte[] caCertificate);
+    // APIs used by CertInstaller and DevicePolicyManager
+    String installCaCertificate(in byte[] caCertificate);
 
     // APIs used by DevicePolicyManager
     boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 2e33609..374c1b1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -268,7 +268,7 @@
 
 // Geometry
 void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
-    if (CC_UNLIKELY(floatCount < 2 || PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(floatCount < 2 || paint.nothingToDraw())) return;
     floatCount &= ~0x1; // round down to nearest two
 
     addOp(alloc().create_trivial<PointsOp>(
@@ -279,7 +279,7 @@
 }
 
 void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
-    if (CC_UNLIKELY(floatCount < 4 || PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(floatCount < 4 || paint.nothingToDraw())) return;
     floatCount &= ~0x3; // round down to nearest four
 
     addOp(alloc().create_trivial<LinesOp>(
@@ -290,7 +290,7 @@
 }
 
 void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
 
     addOp(alloc().create_trivial<RectOp>(
             Rect(left, top, right, bottom),
@@ -333,7 +333,7 @@
 }
 
 void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
 
     if (paint.getStyle() == SkPaint::kFill_Style
             && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
@@ -362,7 +362,7 @@
 
 void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
             float rx, float ry, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
 
     if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) {
         addOp(alloc().create_trivial<RoundRectOp>(
@@ -398,7 +398,7 @@
 
 void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
     // TODO: move to Canvas.h
-    if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
 
     drawOval(x - radius, y - radius, x + radius, y + radius, paint);
 }
@@ -419,7 +419,7 @@
 }
 
 void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
 
     addOp(alloc().create_trivial<OvalOp>(
             Rect(left, top, right, bottom),
@@ -430,7 +430,7 @@
 
 void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
         float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
 
     if (fabs(sweepAngle) >= 360.0f) {
         drawOval(left, top, right, bottom, paint);
@@ -445,7 +445,7 @@
 }
 
 void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
 
     addOp(alloc().create_trivial<PathOp>(
             Rect(path.getBounds()),
@@ -543,7 +543,7 @@
 void RecordingCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
             const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
             float boundsRight, float boundsBottom, float totalAdvance) {
-    if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+    if (!glyphs || !positions || glyphCount <= 0 || paint.nothingToDraw()) return;
     glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
     positions = refBuffer<float>(positions, glyphCount * 2);
 
@@ -563,7 +563,7 @@
         glyphs[0] = layout.getGlyphId(i);
         float x = hOffset + layout.getX(i);
         float y = vOffset + layout.getY(i);
-        if (PaintUtils::paintWillNotDrawText(paint)) return;
+        if (paint.nothingToDraw()) return;
         const uint16_t* tempGlyphs = refBuffer<glyph_t>(glyphs, 1);
         addOp(alloc().create_trivial<TextOnPathOp>(
                 *(mState.currentSnapshot()->transform),
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index c57b1b3..51a7d00 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -443,7 +443,7 @@
 
 void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint,
                             SkCanvas::PointMode mode) {
-    if (CC_UNLIKELY(count < 2 || PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(count < 2 || paint.nothingToDraw())) return;
     // convert the floats into SkPoints
     count >>= 1;    // now it is the number of points
     std::unique_ptr<SkPoint[]> pts(new SkPoint[count]);
@@ -469,49 +469,49 @@
 }
 
 void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
-    if (CC_UNLIKELY(count < 4 || PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(count < 4 || paint.nothingToDraw())) return;
     this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
 }
 
 void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
         const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
     mCanvas->drawRectCoords(left, top, right, bottom, paint);
 
 }
 
 void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
     mCanvas->drawRegion(region, paint);
 }
 
 void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom,
         float rx, float ry, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
     mCanvas->drawRoundRect(rect, rx, ry, paint);
 }
 
 void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
-    if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
     mCanvas->drawCircle(x, y, radius, paint);
 }
 
 void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
     mCanvas->drawOval(oval, paint);
 }
 
 void SkiaCanvas::drawArc(float left, float top, float right, float bottom,
         float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
     mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
 }
 
 void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
-    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+    if (CC_UNLIKELY(paint.nothingToDraw())) return;
     SkRect rect;
     SkRRect roundRect;
     if (path.isOval(&rect)) {
@@ -698,7 +698,7 @@
         const SkPaint& paint, float x, float y,
         float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
         float totalAdvance) {
-     if (!text || !positions || count <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+     if (!text || !positions || count <= 0 || paint.nothingToDraw()) return;
     // Set align to left for drawing, as we don't want individual
     // glyphs centered or right-aligned; the offset above takes
     // care of all alignment.
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 0ac09ac..7fb75dc 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -18,6 +18,7 @@
 
 #include <gtest/gtest.h>
 #include <RecordingCanvas.h>
+#include <SkBlurDrawLooper.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
 
@@ -59,3 +60,21 @@
     EXPECT_EQ(directOp->unmappedBounds, pictureOp->unmappedBounds);
     EXPECT_EQ(directOp->localMatrix, pictureOp->localMatrix);
 }
+
+TEST(SkiaCanvas, drawShadowLayer) {
+    auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+    SkiaCanvas canvas(surface->getCanvas());
+
+    // clear to white
+    canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
+
+    SkPaint paint;
+    // it is transparent to ensure that we still draw the rect since it has a looper
+    paint.setColor(SK_ColorTRANSPARENT);
+    // this is how view's shadow layers are implemented
+    paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10));
+    canvas.drawRect(3, 3, 7, 7, paint);
+
+    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
+    ASSERT_NE(TestUtils::getColor(surface, 5, 5), SK_ColorWHITE);
+}
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 845a3ea..2673be1c 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -39,21 +39,6 @@
         return GL_NEAREST;
     }
 
-    // TODO: move to a method on android:Paint? replace with SkPaint::nothingToDraw()?
-    static inline bool paintWillNotDraw(const SkPaint& paint) {
-        return paint.getAlpha() == 0
-                && !paint.getColorFilter()
-                && paint.getBlendMode() == SkBlendMode::kSrcOver;
-    }
-
-    // TODO: move to a method on android:Paint? replace with SkPaint::nothingToDraw()?
-    static inline bool paintWillNotDrawText(const SkPaint& paint) {
-        return paint.getAlpha() == 0
-                && paint.getLooper() == nullptr
-                && !paint.getColorFilter()
-                && paint.getBlendMode() == SkBlendMode::kSrcOver;
-    }
-
     static bool isOpaquePaint(const SkPaint* paint) {
         if (!paint) return true; // default (paintless) behavior is SrcOver, black
 
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index f79cd06..4b43260 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -35,6 +35,10 @@
  * Instances of this class are created by {@link MidiManager#openDevice}.
  */
 public final class MidiDevice implements Closeable {
+    static {
+        System.loadLibrary("media_jni");
+    }
+
     private static final String TAG = "MidiDevice";
 
     private final MidiDeviceInfo mDeviceInfo;
@@ -43,6 +47,7 @@
     private final IBinder mClientToken;
     private final IBinder mDeviceToken;
     private boolean mIsDeviceClosed;
+    private boolean mIsMirroredToNative;
 
     private final CloseGuard mGuard = CloseGuard.get();
 
@@ -209,10 +214,45 @@
         }
     }
 
+    /**
+     * Makes Midi Device available to the Native API
+     * @hide
+     */
+    public void mirrorToNative() throws IOException {
+        if (mIsDeviceClosed || mIsMirroredToNative) {
+            return;
+        }
+
+        int result = mirrorToNative(mDeviceServer.asBinder(), mDeviceInfo.getId());
+        if (result != 0) {
+            throw new IOException("Failed mirroring to native: " + result);
+        }
+
+        mIsMirroredToNative = true;
+    }
+
+    /**
+     * Makes Midi Device no longer available to the Native API
+     * @hide
+     */
+    public void removeFromNative() throws IOException {
+        if (!mIsMirroredToNative) {
+            return;
+        }
+
+        int result = removeFromNative(mDeviceInfo.getId());
+        if (result != 0) {
+            throw new IOException("Failed removing from native: " + result);
+        }
+
+        mIsMirroredToNative = false;
+    }
+
     @Override
     public void close() throws IOException {
         synchronized (mGuard) {
             if (!mIsDeviceClosed) {
+                removeFromNative();
                 mGuard.close();
                 mIsDeviceClosed = true;
                 try {
@@ -238,4 +278,7 @@
     public String toString() {
         return ("MidiDevice: " + mDeviceInfo.toString());
     }
+
+    private native int mirrorToNative(IBinder deviceServerBinder, int uid);
+    private native int removeFromNative(int uid);
 }
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 861ed0a..23bf3d6 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -26,6 +26,7 @@
     android_mtp_MtpDatabase.cpp \
     android_mtp_MtpDevice.cpp \
     android_mtp_MtpServer.cpp \
+    midi/android_media_midi_MidiDevice.cpp \
 
 LOCAL_SHARED_LIBRARIES := \
     libandroid_runtime \
@@ -34,6 +35,7 @@
     libbinder \
     libmedia \
     libmediadrm \
+    libmidi \
     libskia \
     libui \
     liblog \
@@ -55,6 +57,7 @@
     external/tremor/Tremor \
     frameworks/base/core/jni \
     frameworks/base/libs/hwui \
+    frameworks/base/media/native \
     frameworks/av/media/libmedia \
     frameworks/av/media/libstagefright \
     frameworks/av/media/mtp \
diff --git a/media/jni/midi/android_media_midi_MidiDevice.cpp b/media/jni/midi/android_media_midi_MidiDevice.cpp
new file mode 100644
index 0000000..1e54bac
--- /dev/null
+++ b/media/jni/midi/android_media_midi_MidiDevice.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Midi-JNI"
+
+#include <android_util_Binder.h>
+#include <midi/MidiDeviceRegistry.h>
+#include <nativehelper/jni.h>
+#include <utils/Log.h>
+
+using namespace android;
+using namespace android::media::midi;
+
+extern "C" jint Java_android_media_midi_MidiDevice_mirrorToNative(
+        JNIEnv *env, jobject thiz, jobject midiDeviceServer, jint id)
+{
+    (void)thiz;
+    sp<IBinder> serverBinder = ibinderForJavaObject(env, midiDeviceServer);
+    if (serverBinder.get() == NULL) {
+        ALOGE("Could not obtain IBinder from passed jobject");
+        return -EINVAL;
+    }
+    // return MidiDeviceManager::getInstance().addDevice(serverBinder, uid);
+    return MidiDeviceRegistry::getInstance().addDevice(
+               new BpMidiDeviceServer(serverBinder), id);
+}
+
+extern "C" jint Java_android_media_midi_MidiDevice_removeFromNative(
+        JNIEnv *env, jobject thiz, jint uid)
+{
+    (void)env;
+    (void)thiz;
+    // return MidiDeviceManager::getInstance().removeDevice(uid);
+    return MidiDeviceRegistry::getInstance().removeDevice(uid);
+}
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
new file mode 100644
index 0000000..3500805
--- /dev/null
+++ b/media/native/midi/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 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.
+
+// The headers module is in frameworks/media/native/midi/Android.bp.
+ndk_library {
+    name: "libmidi.ndk",
+    symbol_file: "libmidi.map.txt",
+    first_version: "26",
+//    unversioned_until: "current",
+}
diff --git a/media/native/midi/Android.mk b/media/native/midi/Android.mk
new file mode 100644
index 0000000..b91c430
--- /dev/null
+++ b/media/native/midi/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	../../java/android/media/midi/IMidiDeviceServer.aidl \
+	midi.cpp \
+	MidiDeviceRegistry.cpp \
+	MidiPortRegistry.cpp
+
+LOCAL_AIDL_INCLUDES := \
+	$(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
+	frameworks/native/aidl/binder
+
+LOCAL_CFLAGS += -Wall -Werror -O0
+
+LOCAL_MODULE := libmidi
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := liblog libbinder libutils libmedia
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/native/midi/MidiDeviceRegistry.cpp b/media/native/midi/MidiDeviceRegistry.cpp
new file mode 100644
index 0000000..8854a08
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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 "MidiDeviceRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiDeviceRegistry);
+
+namespace media {
+namespace midi {
+
+MidiDeviceRegistry::MidiDeviceRegistry() : mNextDeviceToken(1) {
+}
+
+status_t MidiDeviceRegistry::addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId) {
+    if (server.get() == nullptr) {
+        return -EINVAL;
+    }
+
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    mServers[deviceId] = server;
+    return OK;
+}
+
+status_t MidiDeviceRegistry::removeDevice(int32_t deviceId) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    mServers.erase(deviceId);
+    const auto& iter = mUidToToken.find(deviceId);
+    if (iter != mUidToToken.end()) {
+        mTokenToUid.erase(iter->second);
+        mUidToToken.erase(iter);
+    }
+    return OK;
+}
+
+//NOTE: This creates an entry if not found, or returns an existing one.
+status_t MidiDeviceRegistry::obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    const auto& serversIter = mServers.find(deviceId);
+    if (serversIter == mServers.end()) {
+        // Not found.
+        return -EINVAL;
+    }
+
+    const auto& iter = mUidToToken.find(deviceId);
+    if (iter != mUidToToken.end()) {
+        *deviceTokenPtr = iter->second;
+    } else {
+        *deviceTokenPtr = mNextDeviceToken++;
+        mTokenToUid[*deviceTokenPtr] = deviceId;
+        mUidToToken[deviceId] = *deviceTokenPtr;
+    }
+    return OK;
+}
+
+status_t MidiDeviceRegistry::releaseDevice(AMIDI_Device deviceToken) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    const auto& iter = mTokenToUid.find(deviceToken);
+    if (iter == mTokenToUid.end()) {
+        // Not found
+        return -EINVAL;
+    }
+
+    mServers.erase(iter->second);
+    mUidToToken.erase(iter->second);
+    mTokenToUid.erase(iter);
+    return OK;
+}
+
+status_t MidiDeviceRegistry::getDeviceByToken(
+        AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    int32_t id = -1;
+    {
+        const auto& iter = mTokenToUid.find(deviceToken);
+        if (iter == mTokenToUid.end()) {
+            return -EINVAL;
+        }
+        id = iter->second;
+    }
+    const auto& iter = mServers.find(id);
+    if (iter == mServers.end()) {
+        return -EINVAL;
+    }
+
+    *devicePtr = iter->second;
+    return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiDeviceRegistry.h b/media/native/midi/MidiDeviceRegistry.h
new file mode 100644
index 0000000..93be733
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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 ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+
+#include <map>
+#include <mutex>
+
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains a thread-safe, (singleton) list of MIDI devices with associated Binder interfaces,
+ * which are exposed to the Native API via (Java) MidiDevice.mirrorToNative() &
+ * MidiDevice.removeFromNative().
+ * (Called via MidiDeviceManager::addDevice() MidiManager::removeDevice()).
+ */
+class MidiDeviceRegistry : public Singleton<MidiDeviceRegistry> {
+  public:
+    /* Add a MIDI Device to the registry.
+     *
+     * server       The Binder interface to the MIDI device server.
+     * deviceUId    The unique ID of the device obtained from
+     *              the Java API via MidiDeviceInfo.getId().
+     */
+    status_t addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId);
+
+    /* Remove the device (and associated server) from the Device registry.
+     *
+     * deviceUid    The ID of the device which was used in the call to addDevice().
+     */
+    status_t removeDevice(int32_t deviceId);
+
+    /* Gets a device token associated with the device ID. This is used by the
+     * native API to identify/access the device.
+     * Multiple calls without releasing the token will return the same value.
+     *
+     * deviceUid    The ID of the device.
+     * deviceTokenPtr Receives the device (native) token associated with the device ID.
+     * returns: OK on success, error code otherwise.
+     */
+    status_t obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr);
+
+    /*
+     * Releases the native API device token associated with a MIDI device.
+     *
+     * deviceToken The device (native) token associated with the device ID.
+     */
+    status_t releaseDevice(AMIDI_Device deviceToken);
+
+    /*
+     * Gets the Device server binder interface associated with the device token.
+     *
+     * deviceToken The device (native) token associated with the device ID.
+     */
+    status_t getDeviceByToken(AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr);
+
+  private:
+    friend class Singleton<MidiDeviceRegistry>;
+    MidiDeviceRegistry();
+
+    // Access Mutex
+    std::mutex                              mMapsLock;
+
+    // maps device IDs to servers
+    std::map<int32_t, sp<BpMidiDeviceServer>>   mServers;
+
+    // maps device tokens to device ID
+    std::map<AMIDI_Device, int32_t>         mTokenToUid;
+
+    // maps device IDs to device tokens
+    std::map<int32_t, AMIDI_Device>         mUidToToken;
+
+    // Value of next device token to dole out.
+    AMIDI_Device                            mNextDeviceToken;
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
diff --git a/media/native/midi/MidiPortRegistry.cpp b/media/native/midi/MidiPortRegistry.cpp
new file mode 100644
index 0000000..fa70af8
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 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 "MidiPortRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiPortRegistry);
+
+namespace media {
+namespace midi {
+
+//TODO Note that these 2 are identical
+struct MidiPortRegistry::OutputPort {
+    AMIDI_Device device;
+    sp<IBinder> binderToken;
+    base::unique_fd ufd;
+};
+
+struct MidiPortRegistry::InputPort {
+    AMIDI_Device device;
+    sp<IBinder> binderToken;
+    base::unique_fd ufd;
+};
+
+MidiPortRegistry::MidiPortRegistry() : mNextOutputPortToken(0), mNextInputPortToken(0) {
+}
+
+status_t MidiPortRegistry::addOutputPort(
+        AMIDI_Device device,
+        sp<IBinder> portToken,
+        base::unique_fd &&ufd,
+        AMIDI_OutputPort *portPtr) {
+    *portPtr = mNextOutputPortToken++;
+
+    OutputPortEntry* portEntry = new OutputPortEntry;
+    portEntry->port = new OutputPort;
+    portEntry->state = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+    portEntry->port = new OutputPort;
+    portEntry->port->device = device;
+    portEntry->port->binderToken = portToken;
+    portEntry->port->ufd = std::move(ufd);
+
+    mOutputPortMap[*portPtr] = portEntry;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::removeOutputPort(
+        AMIDI_OutputPort port,
+        AMIDI_Device *devicePtr,
+        sp<IBinder> *portTokenPtr) {
+    OutputPortMap::iterator itr = mOutputPortMap.find(port);
+    if (itr == mOutputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    OutputPortEntry *entry = mOutputPortMap[port];
+    int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+    while (!entry->state.compare_exchange_weak(portState, MIDI_OUTPUT_PORT_STATE_CLOSED)) {
+        if (portState == MIDI_OUTPUT_PORT_STATE_CLOSED) {
+            return -EINVAL; // Already closed
+        }
+    }
+    *devicePtr = entry->port->device;
+    *portTokenPtr = entry->port->binderToken;
+    delete entry->port;
+    entry->port = nullptr;
+
+    mOutputPortMap.erase(itr);
+
+    return OK;
+}
+
+status_t MidiPortRegistry::getOutputPortFdAndLock(
+        AMIDI_OutputPort port, base::unique_fd **ufdPtr) {
+    if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    OutputPortEntry *entry = mOutputPortMap[port];
+    int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+    if (!entry->state.compare_exchange_strong(portState, MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE)) {
+        // The port has been closed.
+        return -EPIPE;
+    }
+    *ufdPtr = &entry->port->ufd;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::unlockOutputPort(AMIDI_OutputPort port) {
+    if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    OutputPortEntry *entry = mOutputPortMap[port];
+    entry->state.store(MIDI_OUTPUT_PORT_STATE_OPEN_IDLE);
+    return OK;
+}
+
+status_t MidiPortRegistry::addInputPort(
+        AMIDI_Device device,
+        sp<IBinder> portToken,
+        base::unique_fd &&ufd,
+        AMIDI_InputPort *portPtr) {
+    *portPtr = mNextInputPortToken++;
+
+    InputPortEntry *entry = new InputPortEntry;
+
+    entry->state = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+    entry->port = new InputPort;
+    entry->port->device = device;
+    entry->port->binderToken = portToken;
+    entry->port->ufd = std::move(ufd);
+
+    mInputPortMap[*portPtr] = entry;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::removeInputPort(
+        AMIDI_InputPort port,
+        AMIDI_Device *devicePtr,
+        sp<IBinder> *portTokenPtr) {
+    InputPortMap::iterator itr = mInputPortMap.find(port);
+    if (itr == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+    int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+    while (!entry->state.compare_exchange_weak(portState, MIDI_INPUT_PORT_STATE_CLOSED)) {
+        if (portState == MIDI_INPUT_PORT_STATE_CLOSED) return -EINVAL; // Already closed
+    }
+
+    *devicePtr = entry->port->device;
+    *portTokenPtr = entry->port->binderToken;
+    delete entry->port;
+    entry->port = nullptr;
+
+    mInputPortMap.erase(itr);
+
+    return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+    if (mInputPortMap.find(port) == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+
+    *ufdPtr = &entry->port->ufd;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+    if (mInputPortMap.find(port) == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+
+    int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+    if (!entry->state.compare_exchange_strong(portState, MIDI_INPUT_PORT_STATE_OPEN_ACTIVE)) {
+        // The port has been closed.
+        return -EPIPE;
+    }
+    *ufdPtr = &entry->port->ufd;
+    return OK;
+}
+
+status_t MidiPortRegistry::MidiPortRegistry::unlockInputPort(AMIDI_InputPort port) {
+    if (mInputPortMap.find(port) == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+    entry->state.store(MIDI_INPUT_PORT_STATE_OPEN_IDLE);
+    return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiPortRegistry.h b/media/native/midi/MidiPortRegistry.h
new file mode 100644
index 0000000..f1ffb78
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 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 ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+
+#include <atomic>
+#include <map>
+
+#include <android-base/unique_fd.h>
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains lists of all active input and output MIDI ports and controls access to them. Provides
+ * exclusive access to specific MIDI ports.
+ */
+class MidiPortRegistry : public Singleton<MidiPortRegistry> {
+  public:
+    /*
+     * Creates an output port entry and associates it with the specified MIDI device.
+     * Called by AMIDI_openOutputPort();
+     *
+     * device       The native API device ID.
+     * portToken    The port token (returned from the device server).
+     * udf          File descriptor for the data port associated with the MIDI output port.
+     * portPtr      Receives the native API port ID of the port being opened.
+     */
+    status_t addOutputPort(
+            AMIDI_Device device,
+            sp<IBinder> portToken,
+            base::unique_fd &&ufd,
+            AMIDI_OutputPort *portPtr);
+
+    /*
+     * Removes for the output port list a previously added output port.
+     * Called by AMIDI_closeOutputPort();
+     *
+     * port         The native API port ID of the port being closed.
+     * devicePtr    Receives the native API device ID associated with the port.
+     * portTokenPtr Receives the binder token associated with the port.
+     */
+    status_t removeOutputPort(
+            AMIDI_OutputPort port,
+            AMIDI_Device *devicePtr,
+            sp<IBinder> *portTokenPtr);
+
+    /*
+     * Creates an input port entry and associates it with the specified MIDI device.
+     * Called by AMIDI_openInputPort();
+     *
+     * device       The native API device ID.
+     * portToken    The port token (returned from the device server).
+     * udf          File descriptor for the data port associated with the MIDI input port.
+     * portPtr      Receives the native API port ID of the port being opened.
+     */
+    status_t addInputPort(
+            AMIDI_Device device,
+            sp<IBinder> portToken,
+            base::unique_fd &&ufd,
+            AMIDI_InputPort *portPtr);
+
+    /*
+     * Removes for the input port list a previously added input port.
+     * Called by AMIDI_closeINputPort();
+     *
+     * port         The native API port ID of the port being closed.
+     * devicePtr    Receives the native API device ID associated with the port.
+     * portTokenPtr Receives the binder token associated with the port.
+     */
+    status_t removeInputPort(
+            AMIDI_InputPort port,
+            AMIDI_Device *devicePtr,
+            sp<IBinder> *portTokenPtr);
+
+    /*
+     * Retrieves an exclusive-access file descriptor for an output port.
+     * Called from AMIDI_receive().
+     *
+     * port     The native API id of the output port.
+     * ufdPtr   Receives the exclusive-access file descriptor for the output port.
+     */
+    status_t getOutputPortFdAndLock(AMIDI_OutputPort port, base::unique_fd **ufdPtr);
+
+    /*
+     * Releases exclusive-access to the port and invalidates the previously received file
+     * descriptor.
+     * Called from AMIDI_receive().
+     *
+     * port The native API id of the output port.
+     */
+    status_t unlockOutputPort(AMIDI_OutputPort port);
+
+    /*
+     * Retrieves an exclusive-access file descriptor for an input port.
+     * (Not being used as (perhaps) AMIDI_sendWithTimestamp() doesn't need exclusive access
+     * to the port).
+     *
+     * port     The native API id of the input port.
+     * ufdPtr   Receives the exclusive-access file descriptor for the input port.
+     */
+    status_t getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+    /*
+     * Releases exclusive-access to the port and invalidates the previously received file
+     * descriptor.
+     * (Not used. See above).
+     *
+     * port The native API id of the input port.
+     */
+    status_t unlockInputPort(AMIDI_InputPort port);
+
+    /*
+     * Retrieves an unlocked (multi-access) file descriptor for an input port.
+     * Used by AMIDI_sendWith(), AMIDI_sendWithTimestamp & AMIDI_flush.
+     *
+     * port     The native API id of the input port.
+     * ufdPtr   Receives the multi-access file descriptor for the input port.
+     */
+    status_t getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+  private:
+    friend class Singleton<MidiPortRegistry>;
+    MidiPortRegistry();
+
+    /*
+     * Output (data receiving) ports.
+     */
+    struct OutputPort;
+    enum {
+        MIDI_OUTPUT_PORT_STATE_CLOSED = 0,
+        MIDI_OUTPUT_PORT_STATE_OPEN_IDLE,
+        MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE
+    };
+
+    struct OutputPortEntry {
+        std::atomic_int state;
+        OutputPort *port;
+    };
+
+    typedef std::map<AMIDI_OutputPort, OutputPortEntry*> OutputPortMap;
+    // Access is synchronized per record via 'state' field.
+    std::atomic<AMIDI_OutputPort> mNextOutputPortToken;
+    OutputPortMap  mOutputPortMap;
+
+    /*
+     * Input (data sending) ports.
+     */
+    struct InputPort;
+    enum {
+        MIDI_INPUT_PORT_STATE_CLOSED = 0,
+        MIDI_INPUT_PORT_STATE_OPEN_IDLE,
+        MIDI_INPUT_PORT_STATE_OPEN_ACTIVE
+    };
+
+    struct InputPortEntry {
+        std::atomic_int state;
+        InputPort *port;
+    };
+
+    typedef std::map<AMIDI_OutputPort, InputPortEntry*> InputPortMap;
+    // Access is synchronized per record via 'state' field.
+    std::atomic<AMIDI_InputPort> mNextInputPortToken;
+    InputPortMap  mInputPortMap;
+
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
diff --git a/media/native/midi/midi.cpp b/media/native/midi/midi.cpp
new file mode 100644
index 0000000..1bf0bd0
--- /dev/null
+++ b/media/native/midi/midi.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define LOG_TAG "NativeMIDI"
+
+#include <poll.h>
+#include <unistd.h>
+
+#include <binder/Binder.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "media/MidiDeviceInfo.h"
+#include "MidiDeviceRegistry.h"
+#include "MidiPortRegistry.h"
+
+#include "midi.h"
+
+using android::IBinder;
+using android::BBinder;
+using android::OK;
+using android::sp;
+using android::status_t;
+using android::base::unique_fd;
+using android::binder::Status;
+using android::media::midi::BpMidiDeviceServer;
+using android::media::midi::MidiDeviceInfo;
+using android::media::midi::MidiDeviceRegistry;
+using android::media::midi::MidiPortRegistry;
+
+#define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE
+
+/* TRANSFER PACKET FORMAT (as defined in MidiPortImpl.java)
+ *
+ * Transfer packet format is as follows (see MidiOutputPort.mThread.run() to see decomposition):
+ * |oc|md|md| ......... |md|ts|ts|ts|ts|ts|ts|ts|ts|
+ *  ^ +--------------------+-----------------------+
+ *  |  ^                    ^
+ *  |  |                    |
+ *  |  |                    + timestamp (8 bytes)
+ *  |  |
+ *  |  + MIDI data bytes (numBytes bytes)
+ *  |
+ *  + OpCode (AMIDI_OPCODE_DATA)
+ *
+ *  NOTE: The socket pair is configured to use SOCK_SEQPACKET mode.
+ *  SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message
+ *  boundaries, and delivers messages in the order that they were sent.
+ *  So 'read()' always returns a whole message.
+ */
+
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr) {
+    return MidiDeviceRegistry::getInstance().obtainDeviceToken(id, devicePtr);
+}
+
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr) {
+    sp<BpMidiDeviceServer> deviceServer;
+    status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_getDeviceInfo bad device token %d: %d", device, result);
+        return result;
+    }
+
+    MidiDeviceInfo deviceInfo;
+    Status txResult = deviceServer->getDeviceInfo(&deviceInfo);
+    if (!txResult.isOk()) {
+        ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError());
+        return txResult.transactionError();
+    }
+
+    deviceInfoPtr->type = deviceInfo.getType();
+    deviceInfoPtr->uid = deviceInfo.getUid();
+    deviceInfoPtr->isPrivate = deviceInfo.isPrivate();
+    deviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
+    deviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
+    return OK;
+}
+
+/*
+ * Output (receiving) API
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr) {
+    sp<BpMidiDeviceServer> deviceServer;
+    status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_openOutputPort bad device token %d: %d", device, result);
+        return result;
+    }
+
+    sp<BBinder> portToken(new BBinder());
+    unique_fd ufd;
+    Status txResult = deviceServer->openOutputPort(portToken, portNumber, &ufd);
+    if (!txResult.isOk()) {
+        ALOGE("AMIDI_openOutputPort transaction error: %d", txResult.transactionError());
+        return txResult.transactionError();
+    }
+
+    result = MidiPortRegistry::getInstance().addOutputPort(
+            device, portToken, std::move(ufd), outputPortPtr);
+    if (result != OK) {
+        ALOGE("AMIDI_openOutputPort port registration error: %d", result);
+        // Close port
+        return result;
+    }
+    return OK;
+}
+
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages) {
+    unique_fd *ufd;
+    // TODO: May return a nicer self-unlocking object
+    status_t result = MidiPortRegistry::getInstance().getOutputPortFdAndLock(outputPort, &ufd);
+    if (result != OK) {
+        return result;
+    }
+
+    ssize_t messagesRead = 0;
+    while (messagesRead < maxMessages) {
+        struct pollfd checkFds[1] = { { *ufd, POLLIN, 0 } };
+        int pollResult = poll(checkFds, 1, 0);
+        if (pollResult < 1) {
+            result = android::INVALID_OPERATION;
+            break;
+        }
+
+        AMIDI_Message *message = &messages[messagesRead];
+        uint8_t readBuffer[AMIDI_PACKET_SIZE];
+        memset(readBuffer, 0, sizeof(readBuffer));
+        ssize_t readCount = read(*ufd, readBuffer, sizeof(readBuffer));
+        if (readCount == EINTR) {
+            continue;
+        }
+        if (readCount < 1) {
+            result = android::NOT_ENOUGH_DATA;
+            break;
+        }
+
+        // set Packet Format definition at the top of this file.
+        size_t dataSize = 0;
+        message->opcode = readBuffer[0];
+        message->timestamp = 0;
+        if (message->opcode == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
+            dataSize = readCount - AMIDI_PACKET_OVERHEAD;
+            if (dataSize) {
+                memcpy(message->buffer, readBuffer + 1, dataSize);
+            }
+            message->timestamp = *(uint64_t*) (readBuffer + readCount - sizeof(uint64_t));
+        }
+        message->len = dataSize;
+        ++messagesRead;
+    }
+
+    MidiPortRegistry::getInstance().unlockOutputPort(outputPort);
+    return result == OK ? messagesRead : result;
+}
+
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort) {
+    AMIDI_Device device;
+    sp<IBinder> portToken;
+    status_t result =
+        MidiPortRegistry::getInstance().removeOutputPort(outputPort, &device, &portToken);
+    if (result != OK) {
+        return result;
+    }
+
+    sp<BpMidiDeviceServer> deviceServer;
+    result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        return result;
+    }
+
+    Status txResult = deviceServer->closePort(portToken);
+    if (!txResult.isOk()) {
+        return txResult.transactionError();
+    }
+    return OK;
+}
+
+/*
+ * Input (sending) API
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr) {
+    sp<BpMidiDeviceServer> deviceServer;
+    status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_openInputPort bad device token %d: %d", device, result);
+        return result;
+    }
+
+    sp<BBinder> portToken(new BBinder());
+    unique_fd ufd; // this is the file descriptor of the "receive" port s
+    Status txResult = deviceServer->openInputPort(portToken, portNumber, &ufd);
+    if (!txResult.isOk()) {
+        ALOGE("AMIDI_openInputPort transaction error: %d", txResult.transactionError());
+        return txResult.transactionError();
+    }
+
+    result = MidiPortRegistry::getInstance().addInputPort(
+            device, portToken, std::move(ufd), inputPortPtr);
+    if (result != OK) {
+        ALOGE("AMIDI_openInputPort port registration error: %d", result);
+        // Close port
+        return result;
+    }
+
+    return OK;
+}
+
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort) {
+    AMIDI_Device device;
+    sp<IBinder> portToken;
+    status_t result = MidiPortRegistry::getInstance().removeInputPort(
+            inputPort, &device, &portToken);
+    if (result != OK) {
+        ALOGE("AMIDI_closeInputPort remove port error: %d", result);
+        return result;
+    }
+
+    sp<BpMidiDeviceServer> deviceServer;
+    result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_closeInputPort can't find device error: %d", result);
+        return result;
+    }
+
+    Status txResult = deviceServer->closePort(portToken);
+    if (!txResult.isOk()) {
+        result = txResult.transactionError();
+        ALOGE("AMIDI_closeInputPort transaction error: %d", result);
+        return result;
+    }
+
+    return OK;
+}
+
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort /*inputPort*/) {
+    return SIZE_MIDIRECEIVEBUFFER;
+}
+
+static ssize_t AMIDI_makeSendBuffer(uint8_t *buffer, uint8_t *data, ssize_t numBytes, uint64_t timestamp) {
+    buffer[0] = AMIDI_OPCODE_DATA;
+    memcpy(buffer + 1, data, numBytes);
+    memcpy(buffer + 1 + numBytes, &timestamp, sizeof(timestamp));
+    return numBytes + AMIDI_PACKET_OVERHEAD;
+}
+
+// Handy debugging function.
+//static void AMIDI_logBuffer(uint8_t *data, size_t numBytes) {
+//    for (size_t index = 0; index < numBytes; index++) {
+//      ALOGI("  data @%zu [0x%X]", index, data[index]);
+//    }
+//}
+
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes) {
+    return AMIDI_sendWithTimestamp(inputPort, buffer, numBytes, 0);
+}
+
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *data,
+        ssize_t numBytes, int64_t timestamp) {
+
+    if (numBytes > SIZE_MIDIRECEIVEBUFFER) {
+        return android::BAD_VALUE;
+    }
+
+    // AMIDI_logBuffer(data, numBytes);
+
+    unique_fd *ufd = NULL;
+    status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+    if (result != OK) {
+        return result;
+    }
+
+    uint8_t writeBuffer[SIZE_MIDIRECEIVEBUFFER + AMIDI_PACKET_OVERHEAD];
+    ssize_t numTransferBytes = AMIDI_makeSendBuffer(writeBuffer, data, numBytes, timestamp);
+    ssize_t numWritten = write(*ufd, writeBuffer, numTransferBytes);
+
+    if (numWritten < numTransferBytes) {
+        ALOGE("AMIDI_sendWithTimestamp Couldn't write MIDI data buffer. requested:%zu, written%zu",
+                numTransferBytes, numWritten);
+    }
+
+    return numWritten - AMIDI_PACKET_OVERHEAD;
+}
+
+status_t AMIDI_flush(AMIDI_InputPort inputPort) {
+    unique_fd *ufd = NULL;
+    status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+    if (result != OK) {
+        return result;
+    }
+
+    uint8_t opCode = AMIDI_OPCODE_FLUSH;
+    ssize_t numTransferBytes = 1;
+    ssize_t numWritten = write(*ufd, &opCode, numTransferBytes);
+
+    if (numWritten < numTransferBytes) {
+        ALOGE("AMIDI_flush Couldn't write MIDI flush. requested:%zu, written%zu",
+                numTransferBytes, numWritten);
+        return android::INVALID_OPERATION;
+    }
+
+    return OK;
+}
+
diff --git a/media/native/midi/midi.h b/media/native/midi/midi.h
new file mode 100644
index 0000000..717bc66
--- /dev/null
+++ b/media/native/midi/midi.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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 ANDROID_MEDIA_MIDI_H_
+#define ANDROID_MEDIA_MIDI_H_
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+using android::status_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//typedef struct _AMIDI_Device;
+//typedef struct _AMIDI_InputPort;
+//typedef struct _AMIDI_OutputPort;
+//typedef _AMIDI_Device*      AMIDI_Device;
+//typedef _AMIDI_InputPort*   AMIDI_InputPort;
+//typedef _AMIDI_OutputPort*  AMIDI_OutputPort;
+
+typedef int32_t AMIDI_Device;
+typedef int32_t AMIDI_InputPort;
+typedef int32_t  AMIDI_OutputPort;
+
+//TODO - Do we want to wrap this stuff in namespace android { namespace media { namespace midi {?
+
+enum {
+    AMIDI_INVALID_HANDLE = -1
+};
+
+enum {
+    AMIDI_OPCODE_DATA = 1,
+    AMIDI_OPCODE_FLUSH = 2,
+    AMIDI_PACKET_SIZE = 1024,  /* !!! Currently MidiPortImpl.MAX_PACKET_SIZE !!! */
+    AMIDI_PACKET_OVERHEAD = 9,
+    AMIDI_BUFFER_SIZE = AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD
+            /* !!! TBD, currently MidiPortImpl.MAX_PACKET_DATA_SIZE !!! */
+};
+
+typedef struct {
+    uint32_t opcode;
+    uint8_t buffer[AMIDI_BUFFER_SIZE];
+    size_t len;
+    int64_t timestamp;
+} AMIDI_Message;
+
+enum {
+    AMIDI_DEVICE_TYPE_USB = 1,
+    AMIDI_DEVICE_TYPE_VIRTUAL = 2,
+    AMIDI_DEVICE_TYPE_BLUETOOTH = 3
+};
+
+typedef struct {
+    int32_t type;
+    int32_t uid;
+    int32_t isPrivate;
+    int32_t inputPortCount;
+    int32_t outputPortCount;
+} AMIDI_DeviceInfo;
+
+/*
+ * Device API
+ */
+/*
+ * Retrieves the native API device token for the specified Java API device ID.
+ *
+ * uid          The Java API id of the device.
+ * devicePtr    Receives the associated native API token for the device.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr);
+
+/*
+ * Retrieves information for the native MIDI device.
+ *
+ * device           The Native API token for the device.
+ * deviceInfoPtr    Receives the associated device info.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr);
+
+/*
+ * API for receiving data from the Output port of a device.
+ */
+/*
+ * Opens the output port.
+ *
+ * device           Identifies the device.
+ * portNumber       Specifies the zero-based port index on the device to open.
+ * outputPortPtr    Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr);
+
+/*
+ * Receives any pending MIDI messages (up to the specified maximum number of messages).
+ *
+ * outputPort   Identifies the port to receive messages from.
+ * messages     Points to an array (size maxMessages) to receive the MIDI messages.
+ * maxMessages  The number of messages allocated in the messages array.
+ *
+ * Returns the number of messages received, or a (negative) error code.
+ */
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages);
+
+/*
+ * Closes the output port.
+ *
+ * outputPort   The native API port identifier of the port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort);
+
+/*
+ * API for sending data to the Input port of a device.
+ */
+/*
+ * Opens the input port.
+ *
+ * device           Identifies the device.
+ * portNumber       Specifies the zero-based port index on the device to open.
+ * inputPortPtr     Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr);
+
+/*
+ * Returns the maximum number of bytes that can be received in a single MIDI message.
+ */
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort inputPort);
+
+/*
+ * Sends data to the specified input port.
+ *
+ * inputPort    The native API identifier of the port to send data to.
+ * buffer       Points to the array of bytes containing the data to send.
+ * numBytes     Specifies the number of bytes to write.
+ *
+ * Returns  The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes);
+
+/*
+ * Sends data to the specified input port with a timestamp.
+ *
+ * inputPort    The native API identifier of the port to send data to.
+ * buffer       Points to the array of bytes containing the data to send.
+ * numBytes     Specifies the number of bytes to write.
+ * timestamp    The time stamp to associate with the sent data.
+ *
+ * Returns  The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *buffer,
+        ssize_t numBytes, int64_t timestamp);
+
+/*
+ * Sends a message with a 'MIDI flush command code' to the specified port.
+ *
+ * inputPort    The native API identifier of the port to send the flush message to.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_flush(AMIDI_InputPort inputPort);
+
+/*
+ * Closes the input port.
+ *
+ * inputPort   The native API port identifier of the port.
+ *
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ANDROID_MEDIA_MIDI_H_ */
diff --git a/media/tests/NativeMidiDemo/Android.mk b/media/tests/NativeMidiDemo/Android.mk
new file mode 100644
index 0000000..6b08f6b
--- /dev/null
+++ b/media/tests/NativeMidiDemo/Android.mk
@@ -0,0 +1,30 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := NativeMidiDemo
+
+#LOCAL_SDK_VERSION := current
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativemidi_jni
+
+include $(BUILD_PACKAGE)
+
+# Include packages in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/tests/NativeMidiDemo/AndroidManifest.xml b/media/tests/NativeMidiDemo/AndroidManifest.xml
new file mode 100644
index 0000000..322873f
--- /dev/null
+++ b/media/tests/NativeMidiDemo/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.android.nativemididemo"
+          android:versionCode="1"
+          android:versionName="1.0">
+  <application
+      android:allowBackup="false"
+      android:fullBackupContent="false"
+      android:icon="@mipmap/ic_launcher"
+      android:label="@string/app_name">
+    <uses-feature android:name="android.software.midi" android:required="true"/>
+    <activity android:name=".NativeMidi"
+              android:label="@string/app_name">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+  </application>
+</manifest>
diff --git a/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java
new file mode 100644
index 0000000..0969b10
--- /dev/null
+++ b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java
@@ -0,0 +1,354 @@
+/*
+ * 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.example.android.nativemididemo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiReceiver;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import java.io.IOException;
+
+public class NativeMidi extends Activity
+{
+    private TextView mCallbackStatusTextView;
+    private TextView mJavaMidiStatusTextView;
+    private TextView mMessagesTextView;
+    private RadioGroup mMidiDevicesRadioGroup;
+    private Handler mTimerHandler = new Handler();
+    private boolean mAudioWorks;
+    private final int mMinFramesPerBuffer = 32;   // See {min|max}PlaySamples in nativemidi-jni.cpp
+    private final int mMaxFramesPerBuffer = 1000;
+    private int mFramesPerBuffer;
+
+    private TouchableScrollView mMessagesContainer;
+    private MidiManager mMidiManager;
+    private MidiOutputPortSelector mActivePortSelector;
+
+    private Runnable mTimerRunnable = new Runnable() {
+        private long mLastTime;
+        private long mLastPlaybackCounter;
+        private int mLastCallbackRate;
+        private long mLastUntouchedTime;
+
+        @Override
+        public void run() {
+            final long checkIntervalMs = 1000;
+            long currentTime = System.currentTimeMillis();
+            long currentPlaybackCounter = getPlaybackCounter();
+            if (currentTime - mLastTime >= checkIntervalMs) {
+                int callbackRate = Math.round(
+                        (float)(currentPlaybackCounter - mLastPlaybackCounter) /
+                        ((float)(currentTime - mLastTime) / (float)1000));
+                if (mLastCallbackRate != callbackRate) {
+                    mCallbackStatusTextView.setText(
+                           "CB: " + callbackRate + " Hz");
+                    mLastCallbackRate = callbackRate;
+                }
+                mLastTime = currentTime;
+                mLastPlaybackCounter = currentPlaybackCounter;
+            }
+
+            String[] newMessages = getRecentMessages();
+            if (newMessages != null) {
+                for (String message : newMessages) {
+                    mMessagesTextView.append(message);
+                    mMessagesTextView.append("\n");
+                }
+                if (!mMessagesContainer.isTouched) {
+                    if (mLastUntouchedTime == 0) mLastUntouchedTime = currentTime;
+                    if (currentTime - mLastUntouchedTime > 3000) {
+                        mMessagesContainer.fullScroll(View.FOCUS_DOWN);
+                    }
+                } else {
+                    mLastUntouchedTime = 0;
+                }
+            }
+
+            mTimerHandler.postDelayed(this, checkIntervalMs / 4);
+        }
+    };
+
+    private void addMessage(final String message) {
+        mTimerHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                mMessagesTextView.append(message);
+            }
+        }, 0);
+    }
+
+    private class MidiOutputPortSelector implements View.OnClickListener {
+        private final MidiDeviceInfo mDeviceInfo;
+        private final int mPortNumber;
+        private MidiDevice mDevice;
+        private MidiOutputPort mOutputPort;
+
+        MidiOutputPortSelector() {
+            mDeviceInfo = null;
+            mPortNumber = -1;
+        }
+
+        MidiOutputPortSelector(MidiDeviceInfo info, int portNumber) {
+            mDeviceInfo = info;
+            mPortNumber = portNumber;
+        }
+
+        MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; }
+
+        @Override
+        public void onClick(View v) {
+            if (mActivePortSelector != null) {
+                mActivePortSelector.close();
+                mActivePortSelector = null;
+            }
+            if (mDeviceInfo == null) {
+                mActivePortSelector = this;
+                return;
+            }
+            mMidiManager.openDevice(mDeviceInfo, new MidiManager.OnDeviceOpenedListener() {
+                @Override
+                public void onDeviceOpened(MidiDevice device) {
+                    if (device == null) {
+                        addMessage("! Failed to open MIDI device !\n");
+                    } else {
+                        mDevice = device;
+                        try {
+                            mDevice.mirrorToNative();
+                            startReadingMidi(mDevice.getInfo().getId(), mPortNumber);
+                        } catch (IOException e) {
+                            addMessage("! Failed to mirror to native !\n" + e.getMessage() + "\n");
+                        }
+
+                        mActivePortSelector = MidiOutputPortSelector.this;
+
+                        mOutputPort = device.openOutputPort(mPortNumber);
+                        mOutputPort.connect(mMidiReceiver);
+                    }
+                }
+            }, null);
+        }
+
+        void closePortOnly() {
+            stopReadingMidi();
+        }
+
+        void close() {
+            closePortOnly();
+            try {
+                if (mOutputPort != null) {
+                    mOutputPort.close();
+                }
+            } catch (IOException e) {
+                mMessagesTextView.append("! Port close error: " + e + "\n");
+            } finally {
+                mOutputPort = null;
+            }
+            try {
+                if (mDevice != null) {
+                    mDevice.close();
+                }
+            } catch (IOException e) {
+                mMessagesTextView.append("! Device close error: " + e + "\n");
+            } finally {
+                mDevice = null;
+            }
+        }
+    }
+
+    private MidiManager.DeviceCallback mMidiDeviceCallback = new MidiManager.DeviceCallback() {
+        @Override
+        public void onDeviceAdded(MidiDeviceInfo info) {
+            Bundle deviceProps = info.getProperties();
+            String deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_NAME);
+            if (deviceName == null) {
+                deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER);
+            }
+
+            for (MidiDeviceInfo.PortInfo port : info.getPorts()) {
+                if (port.getType() != MidiDeviceInfo.PortInfo.TYPE_OUTPUT) continue;
+                String portName = port.getName();
+                int portNumber = port.getPortNumber();
+                if (portName.length() == 0) portName = "[" + portNumber + "]";
+                portName += "@" + deviceName;
+                RadioButton outputDevice = new RadioButton(NativeMidi.this);
+                outputDevice.setText(portName);
+                outputDevice.setTag(info);
+                outputDevice.setOnClickListener(new MidiOutputPortSelector(info, portNumber));
+                mMidiDevicesRadioGroup.addView(outputDevice);
+            }
+
+            NativeMidi.this.updateKeepScreenOn();
+        }
+
+        @Override
+        public void onDeviceRemoved(MidiDeviceInfo info) {
+            if (mActivePortSelector != null && info.equals(mActivePortSelector.getDeviceInfo())) {
+                mActivePortSelector.close();
+                mActivePortSelector = null;
+            }
+            int removeButtonStart = -1, removeButtonCount = 0;
+            final int buttonCount = mMidiDevicesRadioGroup.getChildCount();
+            boolean checked = false;
+            for (int i = 0; i < buttonCount; ++i) {
+                RadioButton button = (RadioButton) mMidiDevicesRadioGroup.getChildAt(i);
+                if (!info.equals(button.getTag())) continue;
+                if (removeButtonStart == -1) removeButtonStart = i;
+                ++removeButtonCount;
+                if (button.isChecked()) checked = true;
+            }
+            if (removeButtonStart != -1) {
+                mMidiDevicesRadioGroup.removeViews(removeButtonStart, removeButtonCount);
+                if (checked) {
+                    mMidiDevicesRadioGroup.check(R.id.device_none);
+                }
+            }
+
+            NativeMidi.this.updateKeepScreenOn();
+        }
+    };
+
+    private class JavaMidiReceiver extends MidiReceiver implements Runnable {
+        @Override
+        public void onSend(byte[] data, int offset,
+                int count, long timestamp) throws IOException {
+            mTimerHandler.removeCallbacks(this);
+            mTimerHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    mJavaMidiStatusTextView.setText("Java: MSG");
+                }
+            }, 0);
+            mTimerHandler.postDelayed(this, 100);
+        }
+
+        @Override
+        public void run() {
+            mJavaMidiStatusTextView.setText("Java: ---");
+        }
+    }
+
+    private JavaMidiReceiver mMidiReceiver = new JavaMidiReceiver();
+
+    private void updateKeepScreenOn() {
+        if (mMidiDevicesRadioGroup.getChildCount() > 1) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        } else {
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        mCallbackStatusTextView = (TextView) findViewById(R.id.callback_status);
+        mJavaMidiStatusTextView = (TextView) findViewById(R.id.java_midi_status);
+        mMessagesTextView = (TextView) findViewById(R.id.messages);
+        mMessagesContainer = (TouchableScrollView) findViewById(R.id.messages_scroll);
+        mMidiDevicesRadioGroup = (RadioGroup) findViewById(R.id.devices);
+        RadioButton deviceNone = (RadioButton) findViewById(R.id.device_none);
+        deviceNone.setOnClickListener(new MidiOutputPortSelector());
+
+        AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+        if (sampleRate == null) sampleRate = "48000";
+        String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+        if (framesPerBuffer == null) framesPerBuffer = Integer.toString(mMaxFramesPerBuffer);
+        mFramesPerBuffer = Integer.parseInt(framesPerBuffer);
+        String audioInitResult = initAudio(Integer.parseInt(sampleRate), mFramesPerBuffer);
+        mMessagesTextView.append("Open SL ES init: " + audioInitResult + "\n");
+
+        if (audioInitResult.startsWith("Success")) {
+            mAudioWorks = true;
+            mTimerHandler.postDelayed(mTimerRunnable, 0);
+            mTimerHandler.postDelayed(mMidiReceiver, 0);
+        }
+
+        mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
+        mMidiManager.registerDeviceCallback(mMidiDeviceCallback, new Handler());
+        for (MidiDeviceInfo info : mMidiManager.getDevices()) {
+            mMidiDeviceCallback.onDeviceAdded(info);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mAudioWorks) {
+            mTimerHandler.removeCallbacks(mTimerRunnable);
+            pauseAudio();
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mAudioWorks) {
+            mTimerHandler.postDelayed(mTimerRunnable, 0);
+            resumeAudio();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (mActivePortSelector != null) {
+            mActivePortSelector.close();
+            mActivePortSelector = null;
+        }
+        shutdownAudio();
+        super.onDestroy();
+    }
+
+    public void onClearMessages(View v) {
+        mMessagesTextView.setText("");
+    }
+
+    public void onClosePort(View v) {
+        if (mActivePortSelector != null) {
+            mActivePortSelector.closePortOnly();
+        }
+    }
+
+    private native String initAudio(int sampleRate, int playSamples);
+    private native void pauseAudio();
+    private native void resumeAudio();
+    private native void shutdownAudio();
+
+    private native long getPlaybackCounter();
+    private native String[] getRecentMessages();
+
+    private native void startReadingMidi(int deviceId, int portNumber);
+    private native void stopReadingMidi();
+
+    static {
+        System.loadLibrary("nativemidi_jni");
+    }
+}
diff --git a/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java
new file mode 100644
index 0000000..645aafa
--- /dev/null
+++ b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java
@@ -0,0 +1,32 @@
+package com.example.android.nativemididemo;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+public class TouchableScrollView extends ScrollView {
+    public boolean isTouched;
+
+    public TouchableScrollView(Context context) {
+        super(context);
+    }
+
+    public TouchableScrollView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                isTouched = true;
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                isTouched = false;
+                break;
+        }
+        return super.onTouchEvent(event);
+    }
+}
diff --git a/media/tests/NativeMidiDemo/jni/Android.mk b/media/tests/NativeMidiDemo/jni/Android.mk
new file mode 100644
index 0000000..69a64bd
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/Android.mk
@@ -0,0 +1,35 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := libnativemidi_jni
+
+LOCAL_SRC_FILES := \
+	nativemidi-jni.cpp \
+	messagequeue.cpp
+
+LOCAL_CFLAGS += -Wall -Wextra -Werror -O0
+
+LOCAL_C_INCLUDES += \
+    frameworks/base/media/native
+
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := libOpenSLES libmidi
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/tests/NativeMidiDemo/jni/messagequeue.cpp b/media/tests/NativeMidiDemo/jni/messagequeue.cpp
new file mode 100644
index 0000000..ffaef38
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/messagequeue.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 <atomic>
+#include <stdio.h>
+#include <string.h>
+
+#include "messagequeue.h"
+
+namespace nativemididemo {
+
+static const int messageBufferSize = 64 * 1024;
+static char messageBuffer[messageBufferSize];
+static std::atomic_ullong messagesLastWritePosition;
+
+void writeMessage(const char* message)
+{
+    static unsigned long long lastWritePos = 0;
+    size_t messageLen = strlen(message);
+    if (messageLen == 0) return;
+
+    messageLen += 1; // Also count in the null terminator.
+    char buffer[1024];
+    if (messageLen >= messageBufferSize) {
+        snprintf(buffer, sizeof(buffer), "!!! Message too long: %zu bytes !!!", messageLen);
+        message = buffer;
+        messageLen = strlen(message);
+    }
+
+    size_t wrappedWritePos = lastWritePos % messageBufferSize;
+    if (wrappedWritePos + messageLen >= messageBufferSize) {
+        size_t tailLen = messageBufferSize - wrappedWritePos;
+        memset(messageBuffer + wrappedWritePos, 0, tailLen);
+        lastWritePos += tailLen;
+        wrappedWritePos = 0;
+    }
+
+    memcpy(messageBuffer + wrappedWritePos, message, messageLen);
+    lastWritePos += messageLen;
+    messagesLastWritePosition.store(lastWritePos);
+}
+
+static char messageBufferCopy[messageBufferSize];
+
+jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject)
+{
+    static unsigned long long lastReadPos = 0;
+    const char* overrunMessage = "";
+    size_t messagesCount = 0;
+    jobjectArray result = NULL;
+
+    // First we copy the portion of the message buffer into messageBufferCopy.  If after finishing
+    // the copy we notice that the writer has mutated the portion of the buffer that we were
+    // copying, we report an overrun. Afterwards we can safely read messages from the copy.
+    memset(messageBufferCopy, 0, sizeof(messageBufferCopy));
+    unsigned long long lastWritePos = messagesLastWritePosition.load();
+    if (lastWritePos - lastReadPos > messageBufferSize) {
+        overrunMessage = "!!! Message buffer overrun !!!";
+        messagesCount = 1;
+        lastReadPos = lastWritePos;
+        goto create_array;
+    }
+    if (lastWritePos == lastReadPos) return result;
+    if (lastWritePos / messageBufferSize == lastReadPos / messageBufferSize) {
+        size_t wrappedReadPos = lastReadPos % messageBufferSize;
+        memcpy(messageBufferCopy + wrappedReadPos,
+                messageBuffer + wrappedReadPos,
+                lastWritePos % messageBufferSize - wrappedReadPos);
+    } else {
+        size_t wrappedReadPos = lastReadPos % messageBufferSize;
+        memcpy(messageBufferCopy, messageBuffer, lastWritePos % messageBufferSize);
+        memcpy(messageBufferCopy + wrappedReadPos,
+                messageBuffer + wrappedReadPos,
+                messageBufferSize - wrappedReadPos);
+    }
+    {
+    unsigned long long newLastWritePos = messagesLastWritePosition.load();
+    if (newLastWritePos - lastReadPos > messageBufferSize) {
+        overrunMessage = "!!! Message buffer overrun !!!";
+        messagesCount = 1;
+        lastReadPos = lastWritePos = newLastWritePos;
+        goto create_array;
+    }
+    }
+    // Otherwise we ignore newLastWritePos, since we only have a copy of the buffer
+    // up to lastWritePos.
+
+    for (unsigned long long readPos = lastReadPos; readPos < lastWritePos; ) {
+        size_t messageLen = strlen(messageBufferCopy + (readPos % messageBufferSize));
+        if (messageLen != 0) {
+            readPos += messageLen + 1;
+            messagesCount++;
+        } else {
+            // Skip to the beginning of the buffer.
+            readPos = (readPos / messageBufferSize + 1) * messageBufferSize;
+        }
+    }
+    if (messagesCount == 0) {
+        lastReadPos = lastWritePos;
+        return result;
+    }
+
+create_array:
+    result = env->NewObjectArray(
+            messagesCount, env->FindClass("java/lang/String"), env->NewStringUTF(overrunMessage));
+    if (lastWritePos == lastReadPos) return result;
+
+    jsize arrayIndex = 0;
+    while (lastReadPos < lastWritePos) {
+        size_t wrappedReadPos = lastReadPos % messageBufferSize;
+        if (messageBufferCopy[wrappedReadPos] != '\0') {
+            jstring message = env->NewStringUTF(messageBufferCopy + wrappedReadPos);
+            env->SetObjectArrayElement(result, arrayIndex++, message);
+            lastReadPos += env->GetStringLength(message) + 1;
+            env->DeleteLocalRef(message);
+        } else {
+            // Skip to the beginning of the buffer.
+            lastReadPos = (lastReadPos / messageBufferSize + 1) * messageBufferSize;
+        }
+    }
+    return result;
+}
+
+} // namespace nativemididemo
diff --git a/media/tests/NativeMidiDemo/jni/messagequeue.h b/media/tests/NativeMidiDemo/jni/messagequeue.h
new file mode 100644
index 0000000..20aa9e8
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/messagequeue.h
@@ -0,0 +1,30 @@
+/*
+ * 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 NATIVEMIDIDEMO_MESSAGEQUEUE_H
+#define NATIVEMIDIDEMO_MESSAGEQUEUE_H
+
+#include <jni.h>
+
+namespace nativemididemo {
+
+void writeMessage(const char* message);
+jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject thiz);
+
+} // namespace nativemididemo
+
+#endif // NATIVEMIDIDEMO_MESSAGEQUEUE_H
diff --git a/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp b/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
new file mode 100644
index 0000000..8aa874a
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
@@ -0,0 +1,285 @@
+#include <atomic>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <jni.h>
+
+#include <midi/midi.h>
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include "messagequeue.h"
+
+extern "C" {
+JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio(
+        JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
+        JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
+        JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
+        JNIEnv* env, jobject thiz);
+JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(
+        JNIEnv* env, jobject thiz);
+JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
+        JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
+        JNIEnv* env, jobject thiz, jint deviceId, jint portNumber);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
+        JNIEnv* env, jobject thiz);
+}
+
+static const char* errStrings[] = {
+    "SL_RESULT_SUCCESS",                    // 0
+    "SL_RESULT_PRECONDITIONS_VIOLATED",     // 1
+    "SL_RESULT_PARAMETER_INVALID",          // 2
+    "SL_RESULT_MEMORY_FAILURE",             // 3
+    "SL_RESULT_RESOURCE_ERROR",             // 4
+    "SL_RESULT_RESOURCE_LOST",              // 5
+    "SL_RESULT_IO_ERROR",                   // 6
+    "SL_RESULT_BUFFER_INSUFFICIENT",        // 7
+    "SL_RESULT_CONTENT_CORRUPTED",          // 8
+    "SL_RESULT_CONTENT_UNSUPPORTED",        // 9
+    "SL_RESULT_CONTENT_NOT_FOUND",          // 10
+    "SL_RESULT_PERMISSION_DENIED",          // 11
+    "SL_RESULT_FEATURE_UNSUPPORTED",        // 12
+    "SL_RESULT_INTERNAL_ERROR",             // 13
+    "SL_RESULT_UNKNOWN_ERROR",              // 14
+    "SL_RESULT_OPERATION_ABORTED",          // 15
+    "SL_RESULT_CONTROL_LOST" };             // 16
+static const char* getSLErrStr(int code) {
+    return errStrings[code];
+}
+
+static SLObjectItf engineObject;
+static SLEngineItf engineEngine;
+static SLObjectItf outputMixObject;
+static SLObjectItf playerObject;
+static SLPlayItf playerPlay;
+static SLAndroidSimpleBufferQueueItf playerBufferQueue;
+
+static const int minPlaySamples = 32;
+static const int maxPlaySamples = 1000;
+static std::atomic_int playSamples(maxPlaySamples);
+static short playBuffer[maxPlaySamples];
+
+static std::atomic_ullong sharedCounter;
+
+static AMIDI_Device midiDevice = AMIDI_INVALID_HANDLE;
+static std::atomic<AMIDI_OutputPort> midiOutputPort(AMIDI_INVALID_HANDLE);
+
+static int setPlaySamples(int newPlaySamples)
+{
+    if (newPlaySamples < minPlaySamples) newPlaySamples = minPlaySamples;
+    if (newPlaySamples > maxPlaySamples) newPlaySamples = maxPlaySamples;
+    playSamples.store(newPlaySamples);
+    return newPlaySamples;
+}
+
+// Amount of messages we are ready to handle during one callback cycle.
+static const size_t MAX_INCOMING_MIDI_MESSAGES = 20;
+// Static allocation to save time in the callback.
+static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES];
+
+static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/)
+{
+    sharedCounter++;
+
+    AMIDI_OutputPort outputPort = midiOutputPort.load();
+    if (outputPort != AMIDI_INVALID_HANDLE) {
+        char midiDumpBuffer[1024];
+        ssize_t midiReceived = AMIDI_receive(
+                outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES);
+        if (midiReceived >= 0) {
+            for (ssize_t i = 0; i < midiReceived; ++i) {
+                AMIDI_Message* msg = &incomingMidiMessages[i];
+                if (msg->opcode == AMIDI_OPCODE_DATA) {
+                    memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
+                    int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+                            "%" PRIx64 " ", msg->timestamp);
+                    for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) {
+                        pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
+                                "%02x ", *b);
+                    }
+                    nativemididemo::writeMessage(midiDumpBuffer);
+                } else if (msg->opcode == AMIDI_OPCODE_FLUSH) {
+                    nativemididemo::writeMessage("MIDI flush");
+                }
+            }
+        } else {
+            snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+                    "! MIDI Receive error: %s !", strerror(-midiReceived));
+            nativemididemo::writeMessage(midiDumpBuffer);
+        }
+    }
+
+    size_t usedBufferSize = playSamples.load() * sizeof(playBuffer[0]);
+    if (usedBufferSize > sizeof(playBuffer)) {
+        usedBufferSize = sizeof(playBuffer);
+    }
+    (*bq)->Enqueue(bq, playBuffer, usedBufferSize);
+}
+
+jstring Java_com_example_android_nativemididemo_NativeMidi_initAudio(
+        JNIEnv* env, jobject, jint sampleRate, jint playSamples) {
+    const char* stage;
+    SLresult result;
+    char printBuffer[1024];
+
+    playSamples = setPlaySamples(playSamples);
+
+    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
+    if (SL_RESULT_SUCCESS != result) { stage = "slCreateEngine"; goto handle_error; }
+
+    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
+    if (SL_RESULT_SUCCESS != result) { stage = "realize Engine object"; goto handle_error; }
+
+    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
+    if (SL_RESULT_SUCCESS != result) { stage = "get Engine interface"; goto handle_error; }
+
+    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
+    if (SL_RESULT_SUCCESS != result) { stage = "CreateOutputMix"; goto handle_error; }
+
+    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
+    if (SL_RESULT_SUCCESS != result) { stage = "realize OutputMix object"; goto handle_error; }
+
+    {
+    SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, (SLuint32)sampleRate * 1000,
+                                    SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+                                    SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN };
+    SLDataLocator_AndroidSimpleBufferQueue loc_bufq =
+            { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
+    SLDataSource audioSrc = { &loc_bufq, &format_pcm };
+    SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject };
+    SLDataSink audioSnk = { &loc_outmix, NULL };
+    const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE };
+    const SLboolean req[1] = { SL_BOOLEAN_TRUE };
+    result = (*engineEngine)->CreateAudioPlayer(
+            engineEngine, &playerObject, &audioSrc, &audioSnk, 1, ids, req);
+    if (SL_RESULT_SUCCESS != result) { stage = "CreateAudioPlayer"; goto handle_error; }
+
+    result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
+    if (SL_RESULT_SUCCESS != result) { stage = "realize Player object"; goto handle_error; }
+    }
+
+    result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
+    if (SL_RESULT_SUCCESS != result) { stage = "get Play interface"; goto handle_error; }
+
+    result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
+    if (SL_RESULT_SUCCESS != result) { stage = "get BufferQueue interface"; goto handle_error; }
+
+    result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, NULL);
+    if (SL_RESULT_SUCCESS != result) { stage = "register BufferQueue callback"; goto handle_error; }
+
+    result = (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
+    if (SL_RESULT_SUCCESS != result) {
+        stage = "enqueue into PlayerBufferQueue"; goto handle_error; }
+
+    result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
+    if (SL_RESULT_SUCCESS != result) {
+        stage = "SetPlayState(SL_PLAYSTATE_PLAYING)"; goto handle_error; }
+
+    snprintf(printBuffer, sizeof(printBuffer),
+            "Success, sample rate %d, buffer samples %d", sampleRate, playSamples);
+    return env->NewStringUTF(printBuffer);
+
+handle_error:
+    snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result));
+    return env->NewStringUTF(printBuffer);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
+        JNIEnv*, jobject) {
+    if (playerPlay != NULL) {
+        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED);
+    }
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
+        JNIEnv*, jobject) {
+    if (playerBufferQueue != NULL && playerPlay != NULL) {
+        (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
+        (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
+    }
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
+        JNIEnv*, jobject) {
+    if (playerObject != NULL) {
+        (*playerObject)->Destroy(playerObject);
+        playerObject = NULL;
+        playerPlay = NULL;
+        playerBufferQueue = NULL;
+    }
+
+    if (outputMixObject != NULL) {
+        (*outputMixObject)->Destroy(outputMixObject);
+        outputMixObject = NULL;
+    }
+
+    if (engineObject != NULL) {
+        (*engineObject)->Destroy(engineObject);
+        engineObject = NULL;
+        engineEngine = NULL;
+    }
+}
+
+jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) {
+    return sharedCounter.load();
+}
+
+jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
+        JNIEnv* env, jobject thiz) {
+    return nativemididemo::getRecentMessagesForJava(env, thiz);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
+        JNIEnv*, jobject, jint deviceId, jint portNumber) {
+    char buffer[1024];
+
+    int result = AMIDI_getDeviceById(deviceId, &midiDevice);
+    if (result == 0) {
+        snprintf(buffer, sizeof(buffer), "Obtained device token for uid %d: token %d", deviceId, midiDevice);
+    } else {
+        snprintf(buffer, sizeof(buffer), "Could not obtain device token for uid %d: %d", deviceId, result);
+    }
+    nativemididemo::writeMessage(buffer);
+    if (result) return;
+
+    AMIDI_DeviceInfo deviceInfo;
+    result = AMIDI_getDeviceInfo(midiDevice, &deviceInfo);
+    if (result == 0) {
+        snprintf(buffer, sizeof(buffer), "Device info: uid %d, type %d, priv %d, ports %d I / %d O",
+                deviceInfo.uid, deviceInfo.type, deviceInfo.isPrivate,
+                (int)deviceInfo.inputPortCount, (int)deviceInfo.outputPortCount);
+    } else {
+        snprintf(buffer, sizeof(buffer), "Could not obtain device info %d", result);
+    }
+    nativemididemo::writeMessage(buffer);
+    if (result) return;
+
+    AMIDI_OutputPort outputPort;
+    result = AMIDI_openOutputPort(midiDevice, portNumber, &outputPort);
+    if (result == 0) {
+        snprintf(buffer, sizeof(buffer), "Opened port %d: token %d", portNumber, outputPort);
+        midiOutputPort.store(outputPort);
+    } else {
+        snprintf(buffer, sizeof(buffer), "Could not open port %d: %d", deviceId, result);
+    }
+    nativemididemo::writeMessage(buffer);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
+        JNIEnv*, jobject) {
+    AMIDI_OutputPort outputPort = midiOutputPort.exchange(AMIDI_INVALID_HANDLE);
+    if (outputPort == AMIDI_INVALID_HANDLE) return;
+    int result = AMIDI_closeOutputPort(outputPort);
+    char buffer[1024];
+    if (result == 0) {
+        snprintf(buffer, sizeof(buffer), "Closed port by token %d", outputPort);
+    } else {
+        snprintf(buffer, sizeof(buffer), "Could not close port by token %d: %d", outputPort, result);
+    }
+    nativemididemo::writeMessage(buffer);
+}
diff --git a/media/tests/NativeMidiDemo/res/layout/main.xml b/media/tests/NativeMidiDemo/res/layout/main.xml
new file mode 100644
index 0000000..465d471
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/layout/main.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+  <LinearLayout
+      android:orientation="horizontal"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      >
+    <TextView
+        android:id="@+id/callback_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="13sp"
+        android:typeface="monospace"
+        />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="13sp"
+        android:typeface="monospace"
+        android:text=" | "
+        />
+    <TextView
+        android:id="@+id/java_midi_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="13sp"
+        android:typeface="monospace"
+        />
+    <TextView
+        android:layout_width="0px"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textSize="13sp"
+        android:typeface="monospace"
+        android:text=" "
+        />
+  </LinearLayout>
+  <HorizontalScrollView
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:paddingTop="6sp"
+      android:paddingBottom="6sp"
+      >
+    <RadioGroup
+        android:id="@+id/devices"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:checkedButton="@+id/device_none"
+        >
+      <RadioButton
+          android:id="@+id/device_none"
+          android:text="None" 
+          />
+    </RadioGroup>
+  </HorizontalScrollView>
+  <com.example.android.nativemididemo.TouchableScrollView android:id="@+id/messages_scroll"
+              android:layout_width="match_parent"
+              android:layout_height="0px"
+              android:layout_weight="1"
+              >
+    <TextView android:id="@+id/messages"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:textSize="13sp"
+              android:typeface="monospace"
+              />
+  </com.example.android.nativemididemo.TouchableScrollView>
+  <LinearLayout
+      android:orientation="horizontal"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      >
+    <Button android:id="@+id/clear_messages"
+          android:layout_width="0px"
+          android:layout_height="wrap_content"
+          android:layout_weight="1"
+          android:text="@string/clear_messages"
+          android:onClick="onClearMessages"
+          />
+    <Button android:id="@+id/close_port"
+          android:layout_width="0px"
+          android:layout_height="wrap_content"
+          android:layout_weight="1"
+          android:text="@string/close_port"
+          android:onClick="onClosePort"
+          />
+  </LinearLayout>
+</LinearLayout>
diff --git a/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/values/strings.xml b/media/tests/NativeMidiDemo/res/values/strings.xml
new file mode 100644
index 0000000..5b69b52
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="app_name">NativeMidiDemo</string>
+  <string name="clear_messages">Clear Messages</string>
+  <string name="close_port">Close Port</string>
+</resources>
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 3ede2ee..1b1f28c 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -139,16 +139,27 @@
     ANativeActivity_setWindowFlags;
     ANativeActivity_setWindowFormat;
     ANativeActivity_showSoftInput;
-    ANativeWindow_acquire; # removed=26
-    ANativeWindow_fromSurface; # removed=26
-    ANativeWindow_fromSurfaceTexture; # removed=26
-    ANativeWindow_getFormat; # removed=26
-    ANativeWindow_getHeight; # removed=26
-    ANativeWindow_getWidth; # removed=26
-    ANativeWindow_lock; # removed=26
-    ANativeWindow_release; # removed=26
-    ANativeWindow_setBuffersGeometry; # removed=26
-    ANativeWindow_unlockAndPost; # removed=26
+    AHardwareBuffer_acquire; # introduced=26
+    AHardwareBuffer_allocate; # introduced=26
+    AHardwareBuffer_describe; # introduced=26
+    AHardwareBuffer_fromHardwareBuffer; # introduced=26
+    AHardwareBuffer_getNativeHandle; # introduced=26
+    AHardwareBuffer_lock; # introduced=26
+    AHardwareBuffer_recvHandleFromUnixSocket; # introduced=26
+    AHardwareBuffer_release; # introduced=26
+    AHardwareBuffer_sendHandleToUnixSocket; # introduced=26
+    AHardwareBuffer_toHardwareBuffer; # introduced=26
+    AHardwareBuffer_unlock; # introduced=26
+    ANativeWindow_acquire;
+    ANativeWindow_fromSurface;
+    ANativeWindow_fromSurfaceTexture; # introduced-arm=13 introduced-mips=13 introduced-x86=13
+    ANativeWindow_getFormat;
+    ANativeWindow_getHeight;
+    ANativeWindow_getWidth;
+    ANativeWindow_lock;
+    ANativeWindow_release;
+    ANativeWindow_setBuffersGeometry;
+    ANativeWindow_unlockAndPost;
     AObbInfo_delete;
     AObbInfo_getFlags;
     AObbInfo_getPackageName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index d4623d6..82da9a3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -31,7 +31,8 @@
 
     public InterestingConfigChanges(int extraFlags) {
         mFlags = extraFlags | ActivityInfo.CONFIG_LOCALE
-                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT;
+                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT
+                | ActivityInfo.CONFIG_ASSETS_PATHS;
     }
 
     public boolean applyNewConfig(Resources res) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
new file mode 100644
index 0000000..ec8e956
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 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.settingslib.applications;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.permission.RuntimePermissionPresentationInfo;
+import android.content.pm.permission.RuntimePermissionPresenter;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PermissionsSummaryHelper  {
+
+    public static void getPermissionSummary(Context context, String pkg,
+            final PermissionsResultCallback callback) {
+        final RuntimePermissionPresenter presenter =
+                RuntimePermissionPresenter.getInstance(context);
+        presenter.getAppPermissions(pkg, new RuntimePermissionPresenter.OnResultCallback() {
+            @Override
+            public void onGetAppPermissions(
+                    @NonNull List<RuntimePermissionPresentationInfo> permissions) {
+                final int permissionCount = permissions.size();
+
+                int grantedStandardCount = 0;
+                int grantedAdditionalCount = 0;
+                int requestedCount = 0;
+                List<CharSequence> grantedStandardLabels = new ArrayList<>();
+
+                for (int i = 0; i < permissionCount; i++) {
+                    RuntimePermissionPresentationInfo permission = permissions.get(i);
+                    requestedCount++;
+                    if (permission.isGranted()) {
+                        if (permission.isStandard()) {
+                            grantedStandardLabels.add(permission.getLabel());
+                            grantedStandardCount++;
+                        } else {
+                            grantedAdditionalCount++;
+                        }
+                    }
+                }
+
+                Collator collator = Collator.getInstance();
+                collator.setStrength(Collator.PRIMARY);
+                Collections.sort(grantedStandardLabels, collator);
+
+                callback.onPermissionSummaryResult(grantedStandardCount, requestedCount,
+                        grantedAdditionalCount, grantedStandardLabels);
+            }
+        }, null);
+    }
+
+    public static abstract class PermissionsResultCallback {
+        public void onAppWithPermissionsCountsResult(int standardGrantedPermissionAppCount,
+                int standardUsedPermissionAppCount) {
+            /* do nothing - stub */
+        }
+
+        public void onPermissionSummaryResult(int standardGrantedPermissionCount,
+                int requestedPermissionCount, int additionalGrantedPermissionCount,
+                List<CharSequence> grantedGroupLabels) {
+            /* do nothing - stub */
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemBars.java b/packages/SystemUI/src/com/android/systemui/SystemBars.java
index 6623cabe..b5093b3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemBars.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemBars.java
@@ -43,13 +43,6 @@
     }
 
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        if (mStatusBar != null) {
-            mStatusBar.onConfigurationChanged(newConfig);
-        }
-    }
-
-    @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mStatusBar != null) {
             mStatusBar.dump(fd, pw, args);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index ae402ef..e7256d1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -71,8 +71,15 @@
         }
 
         @Override
+        public void onPinnedStackAnimationStarted() {
+            // Disable touches while the animation is running
+            mTouchHandler.setTouchEnabled(false);
+        }
+
+        @Override
         public void onPinnedStackAnimationEnded() {
-            // TODO(winsonc): Disable touch interaction with the PiP until the animation ends
+            // Re-enable touches after the animation completes
+            mTouchHandler.setTouchEnabled(true);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index d832810..4100b66 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -181,6 +181,10 @@
         registerInputConsumer();
     }
 
+    public void setTouchEnabled(boolean enabled) {
+        mTouchState.setAllowTouches(enabled);
+    }
+
     public void onActivityPinned() {
         // Reset some states once we are pinned
         if (mIsTappingThrough) {
@@ -294,6 +298,7 @@
                 // Fall through to clean up
             }
             case MotionEvent.ACTION_CANCEL: {
+                mTouchState.reset();
                 break;
             }
         }
@@ -418,6 +423,10 @@
 
         @Override
         public void onDown(PipTouchState touchState) {
+            if (!touchState.isUserInteracting()) {
+                return;
+            }
+
             if (ENABLE_DRAG_TO_DISMISS) {
                 mDismissViewController.createDismissTarget();
                 mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
@@ -426,6 +435,10 @@
 
         @Override
         boolean onMove(PipTouchState touchState) {
+            if (!touchState.isUserInteracting()) {
+                return false;
+            }
+
             if (touchState.startedDragging()) {
                 mSavedSnapFraction = -1f;
             }
@@ -458,6 +471,10 @@
 
         @Override
         public boolean onUp(PipTouchState touchState) {
+            if (!touchState.isUserInteracting()) {
+                return false;
+            }
+
             try {
                 if (ENABLE_DRAG_TO_DISMISS) {
                     mHandler.removeCallbacks(mShowDismissAffordance);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 702ad0a..a317dc3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -37,6 +37,7 @@
     private final PointF mLastTouch = new PointF();
     private final PointF mLastDelta = new PointF();
     private final PointF mVelocity = new PointF();
+    private boolean mAllowTouches = true;
     private boolean mIsUserInteracting = false;
     private boolean mIsDragging = false;
     private boolean mStartedDragging = false;
@@ -48,23 +49,41 @@
     }
 
     /**
+     * Resets this state.
+     */
+    public void reset() {
+        mAllowDraggingOffscreen = false;
+        mIsDragging = false;
+        mStartedDragging = false;
+        mIsUserInteracting = false;
+    }
+
+    /**
      * Processess a given touch event and updates the state.
      */
     public void onTouchEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
+                if (!mAllowTouches) {
+                    return;
+                }
+
                 // Initialize the velocity tracker
                 initOrResetVelocityTracker();
+
                 mActivePointerId = ev.getPointerId(0);
                 mLastTouch.set(ev.getX(), ev.getY());
                 mDownTouch.set(mLastTouch);
-                mIsDragging = false;
-                mStartedDragging = false;
                 mAllowDraggingOffscreen = true;
                 mIsUserInteracting = true;
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
+                // Skip event if we did not start processing this touch gesture
+                if (!mIsUserInteracting) {
+                    break;
+                }
+
                 // Update the velocity tracker
                 mVelocityTracker.addMovement(ev);
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -86,6 +105,11 @@
                 break;
             }
             case MotionEvent.ACTION_POINTER_UP: {
+                // Skip event if we did not start processing this touch gesture
+                if (!mIsUserInteracting) {
+                    break;
+                }
+
                 // Update the velocity tracker
                 mVelocityTracker.addMovement(ev);
 
@@ -100,6 +124,11 @@
                 break;
             }
             case MotionEvent.ACTION_UP: {
+                // Skip event if we did not start processing this touch gesture
+                if (!mIsUserInteracting) {
+                    break;
+                }
+
                 // Update the velocity tracker
                 mVelocityTracker.addMovement(ev);
                 mVelocityTracker.computeCurrentVelocity(1000,
@@ -112,7 +141,6 @@
                 // Fall through to clean up
             }
             case MotionEvent.ACTION_CANCEL: {
-                mIsUserInteracting = false;
                 recycleVelocityTracker();
                 break;
             }
@@ -171,6 +199,19 @@
     }
 
     /**
+     * Sets whether touching is currently allowed.
+     */
+    public void setAllowTouches(boolean allowTouches) {
+        mAllowTouches = allowTouches;
+
+        // If the user happens to touch down before this is sent from the system during a transition
+        // then block any additional handling by resetting the state now
+        if (mIsUserInteracting) {
+            reset();
+        }
+    }
+
+    /**
      * Disallows dragging offscreen for the duration of the current gesture.
      */
     public void setDisallowDraggingOffscreen() {
@@ -202,6 +243,7 @@
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
         pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
         pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
         pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 428fe9b..7d13f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -84,7 +84,6 @@
         mLabel = (TextView) mLabelContainer.findViewById(R.id.tile_label);
         mPadLock = (ImageView) mLabelContainer.findViewById(R.id.restricted_padlock);
 
-        mLabelContainer.setBackground(newTileBackground());
         addView(mLabelContainer);
     }
 
@@ -102,6 +101,11 @@
             mLabel.setText(state.label);
         }
         mDivider.setVisibility(state.dualTarget ? View.VISIBLE : View.INVISIBLE);
+        if (state.dualTarget != mLabelContainer.isClickable()) {
+            mLabelContainer.setClickable(state.dualTarget);
+            mLabelContainer.setLongClickable(state.dualTarget);
+            mLabelContainer.setBackground(state.dualTarget ? newTileBackground() : null);
+        }
         mLabel.setEnabled(!state.disabledByPolicy);
         mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
     }
@@ -110,7 +114,9 @@
     public void init(OnClickListener click, OnClickListener secondaryClick,
             OnLongClickListener longClick) {
         super.init(click, secondaryClick, longClick);
-        mLabelContainer.setClickable(true);
         mLabelContainer.setOnClickListener(secondaryClick);
+        mLabelContainer.setOnLongClickListener(longClick);
+        mLabelContainer.setClickable(false);
+        mLabelContainer.setLongClickable(false);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index b28b0e7..a76299d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -288,7 +288,7 @@
             drawable = mTile.getIcon().loadDrawable(mContext);
         } catch (Exception e) {
             Log.w(TAG, "Invalid icon, forcing into unavailable state");
-            tileState = Tile.STATE_UNAVAILABLE;
+            state.state = Tile.STATE_UNAVAILABLE;
             drawable = mDefaultIcon.loadDrawable(mContext);
         }
         state.icon = new DrawableIcon(drawable);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index cda902b..1042356 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -154,6 +154,7 @@
         public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
         public void onActivityPinned() { }
         public void onPinnedActivityRestartAttempt(String launchedFromPackage) { }
+        public void onPinnedStackAnimationStarted() { }
         public void onPinnedStackAnimationEnded() { }
         public void onActivityForcedResizable(String packageName, int taskId) { }
         public void onActivityDismissingDockedStack() { }
@@ -206,6 +207,12 @@
         }
 
         @Override
+        public void onPinnedStackAnimationStarted() throws RemoteException {
+            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
+            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
+        }
+
+        @Override
         public void onPinnedStackAnimationEnded() throws RemoteException {
             mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
             mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
@@ -1219,6 +1226,7 @@
         private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
         private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
         private static final int ON_TASK_PROFILE_LOCKED = 8;
+        private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
 
         @Override
         public void handleMessage(Message msg) {
@@ -1248,6 +1256,12 @@
                     }
                     break;
                 }
+                case ON_PINNED_STACK_ANIMATION_STARTED: {
+                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                        mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
+                    }
+                    break;
+                }
                 case ON_PINNED_STACK_ANIMATION_ENDED: {
                     for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                         mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 995901b..09b7bec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -177,6 +177,10 @@
         disable(state1, state2, true);
     }
 
+    public void recomputeDisableFlags(boolean animate) {
+        disable(mDisable1, mDisable2, animate);
+    }
+
     public void animateExpandNotificationsPanel() {
         synchronized (mLock) {
             mHandler.removeMessages(MSG_EXPAND_NOTIFICATIONS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 807d902..af464c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -154,7 +154,12 @@
                         iNotificationManager.getNotificationChannelGroupForPackage(
                                 channel.getGroup(), pkg, appUid);
                 if (notificationChannelGroup != null) {
-                    groupName = notificationChannelGroup.getName();
+                    if (info != null && notificationChannelGroup.getNameResId() != 0) {
+                        groupName = pm.getText(pkg, notificationChannelGroup.getNameResId(), info);
+                    }
+                    if (notificationChannelGroup.getName() != null) {
+                        groupName = notificationChannelGroup.getName();
+                    }
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 36ed551..8da17fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -41,7 +41,8 @@
  * A notification shelf view that is placed inside the notification scroller. It manages the
  * overflow icons that don't fit into the regular list anymore.
  */
-public class NotificationShelf extends ActivatableNotificationView {
+public class NotificationShelf extends ActivatableNotificationView implements
+        View.OnLayoutChangeListener {
 
     public static final boolean SHOW_AMBIENT_ICONS = true;
     private static final boolean USE_ANIMATIONS_WHEN_OPENING =
@@ -494,6 +495,10 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
+        updateRelativeOffset();
+    }
+
+    private void updateRelativeOffset() {
         mCollapsedIcons.getLocationOnScreen(mTmp);
         mRelativeOffset = mTmp[0];
         getLocationOnScreen(mTmp);
@@ -560,6 +565,7 @@
 
     public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
         mCollapsedIcons = collapsedIcons;
+        mCollapsedIcons.addOnLayoutChangeListener(this);
     }
 
     public void setStatusBarState(int statusBarState) {
@@ -595,6 +601,12 @@
         }
     }
 
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        updateRelativeOffset();
+    }
+
     private class ShelfState extends ExpandableViewState {
         private float openedAmount;
         private boolean hasItemsInStableShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index aec9a4b..1101701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -612,8 +612,10 @@
     }
 
     public void setIconAppearAmount(float iconAppearAmount) {
-        mIconAppearAmount = iconAppearAmount;
-        invalidate();
+        if (mIconAppearAmount != iconAppearAmount) {
+            mIconAppearAmount = iconAppearAmount;
+            invalidate();
+        }
     }
 
     public float getIconAppearAmount() {
@@ -625,8 +627,10 @@
     }
 
     public void setDotAppearAmount(float dotAppearAmount) {
-        mDotAppearAmount = dotAppearAmount;
-        invalidate();
+        if (mDotAppearAmount != dotAppearAmount) {
+            mDotAppearAmount = dotAppearAmount;
+            invalidate();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 1f56c56..6cd3eae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -153,7 +153,7 @@
     }
 
     private boolean shouldHideNotificationIcons() {
-        return !mStatusBar.isClosed() && mStatusBarComponent.shouldHideNotificationIcons();
+        return !mStatusBar.isClosed() && mStatusBarComponent.hideStatusBarIconsWhenExpanded();
     }
 
     public void hideSystemIconArea(boolean animate) {
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 ad875f1..3f7e340 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -21,8 +21,8 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.app.ActivityManager;
 import android.annotation.DrawableRes;
+import android.app.ActivityManager;
 import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 707997d..4581204 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -79,7 +79,9 @@
         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
             View child = mNotificationIcons.getChildAt(i);
             child.setLayoutParams(params);
-            child = mShelfIcons.getChildAt(i);
+        }
+        for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
+            View child = mShelfIcons.getChildAt(i);
             child.setLayoutParams(params);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 468fb57..e6d3168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -47,7 +46,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -179,7 +177,7 @@
     private boolean mKeyguardStatusViewAnimating;
     private ValueAnimator mQsSizeChangeAnimator;
 
-    private boolean mShadeEmpty;
+    private boolean mShowEmptyShadeView;
 
     private boolean mQsScrimEnabled = true;
     private boolean mLastAnnouncementWasQuickSettings;
@@ -211,11 +209,12 @@
         }
     };
     private NotificationGroupManager mGroupManager;
-    private boolean mOpening;
+    private boolean mShowIconsWhenExpanded;
     private int mIndicationBottomPadding;
     private boolean mIsFullWidth;
     private boolean mDark;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private boolean mNoVisibleNotifications = true;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -1518,7 +1517,7 @@
         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
         // and expanding/collapsing the whole panel from/to quick settings.
         if (mNotificationStackScroller.getNotGoneChildCount() == 0
-                && mShadeEmpty) {
+                && mShowEmptyShadeView) {
             notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
         }
         int maxQsHeight = mQsMaxExpansionHeight;
@@ -2118,15 +2117,15 @@
         return mDozing;
     }
 
-    public void setShadeEmpty(boolean shadeEmpty) {
-        mShadeEmpty = shadeEmpty;
+    public void showEmptyShadeView(boolean emptyShadeViewVisible) {
+        mShowEmptyShadeView = emptyShadeViewVisible;
         updateEmptyShadeView();
     }
 
     private void updateEmptyShadeView() {
 
         // Hide "No notifications" in QS.
-        mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
+        mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
     }
 
     public void setQsScrimEnabled(boolean qsScrimEnabled) {
@@ -2306,7 +2305,7 @@
         }
         mNotificationStackScroller.setExpandedHeight(expandedHeight);
         updateKeyguardBottomAreaAlpha();
-        setOpening(isFullWidth() && expandedHeight < getOpeningHeight());
+        updateStatusBarIcons();
     }
 
     /**
@@ -2317,13 +2316,21 @@
         return mIsFullWidth;
     }
 
-    private void setOpening(boolean opening) {
-        if (opening != mOpening) {
-            mOpening = opening;
+    private void updateStatusBarIcons() {
+        boolean showIconsWhenExpanded = isFullWidth() && getExpandedHeight() < getOpeningHeight();
+        if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
+            showIconsWhenExpanded = false;
+        }
+        if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
+            mShowIconsWhenExpanded = showIconsWhenExpanded;
             mStatusBar.recomputeDisableFlags(false);
         }
     }
 
+    private boolean isOnKeyguard() {
+        return mStatusBar.getBarState() == StatusBarState.KEYGUARD;
+    }
+
     public void setPanelScrimMinFraction(float minFraction) {
         mBar.panelScrimMinFractionChanged(minFraction);
     }
@@ -2426,12 +2433,8 @@
         mGroupManager = groupManager;
     }
 
-    public boolean shouldHideNotificationIcons() {
-        return !isFullWidth() || (!mOpening && !isFullyCollapsed());
-    }
-
-    public boolean shouldAnimateIconHiding() {
-        return !isFullWidth();
+    public boolean hideStatusBarIconsWhenExpanded() {
+        return !isFullWidth() || !mShowIconsWhenExpanded;
     }
 
     private final FragmentListener mFragmentListener = new FragmentListener() {
@@ -2473,4 +2476,8 @@
         mKeyguardStatusView.setDark(dark);
         positionClockAndNotifications();
     }
+
+    public void setNoVisibleNotifications(boolean noNotifications) {
+        mNoVisibleNotifications = noNotifications;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 9e93ed3..bb6c8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.AlarmManager;
@@ -25,8 +24,6 @@
 import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.Notification.Action;
-import android.app.Notification.BigTextStyle;
-import android.app.Notification.Style;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.SynchronousUserSwitchObserver;
@@ -37,7 +34,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Icon;
 import android.media.AudioManager;
@@ -51,7 +47,6 @@
 import android.provider.Settings.Global;
 import android.service.notification.StatusBarNotification;
 import android.telecom.TelecomManager;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -61,11 +56,9 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.R.string;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
-import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
 import com.android.systemui.statusbar.CommandQueue;
@@ -87,9 +80,6 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.NotificationChannels;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * This class contains all of the policy about which icons are installed in the status
  * bar at boot time.  It goes through the normal API for icons, even though it probably
@@ -137,8 +127,6 @@
     private boolean mVolumeVisible;
     private boolean mCurrentUserSetup;
 
-    private int mZen;
-
     private boolean mManagedProfileFocused = false;
     private boolean mManagedProfileIconVisible = false;
     private boolean mManagedProfileInQuietMode = false;
@@ -275,14 +263,14 @@
 
     @Override
     public void onZenChanged(int zen) {
-        mZen = zen;
         updateVolumeZen();
     }
 
     private void updateAlarm() {
         final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
         final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
-        final boolean zenNone = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
+        int zen = mZenController.getZen();
+        final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
         mIconController.setIcon(mSlotAlarmClock, zenNone ? R.drawable.stat_sys_alarm_dim
                 : R.drawable.stat_sys_alarm, null);
         mIconController.setIconVisibility(mSlotAlarmClock, mCurrentUserSetup && hasAlarm);
@@ -323,17 +311,18 @@
         boolean volumeVisible = false;
         int volumeIconId = 0;
         String volumeDescription = null;
+        int zen = mZenController.getZen();
 
         if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
-            zenVisible = mZen != Global.ZEN_MODE_OFF;
-            zenIconId = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS
+            zenVisible = zen != Global.ZEN_MODE_OFF;
+            zenIconId = zen == Global.ZEN_MODE_NO_INTERRUPTIONS
                     ? R.drawable.stat_sys_dnd_total_silence : R.drawable.stat_sys_dnd;
             zenDescription = mContext.getString(R.string.quick_settings_dnd_label);
-        } else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+        } else if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
             zenVisible = true;
             zenIconId = R.drawable.stat_sys_zen_none;
             zenDescription = mContext.getString(R.string.interruption_level_none);
-        } else if (mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+        } else if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
             zenVisible = true;
             zenIconId = R.drawable.stat_sys_zen_important;
             zenDescription = mContext.getString(R.string.interruption_level_priority);
@@ -344,7 +333,7 @@
             volumeVisible = true;
             volumeIconId = R.drawable.stat_sys_ringer_silent;
             volumeDescription = mContext.getString(R.string.accessibility_ringer_silent);
-        } else if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && mZen != Global.ZEN_MODE_ALARMS &&
+        } else if (zen != Global.ZEN_MODE_NO_INTERRUPTIONS && zen != Global.ZEN_MODE_ALARMS &&
                 audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
             volumeVisible = true;
             volumeIconId = R.drawable.stat_sys_ringer_vibrate;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 53d2d73..816a39d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -172,7 +172,6 @@
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
@@ -717,7 +716,7 @@
     private LogMaker mStatusBarStateLog;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     private NotificationIconAreaController mNotificationIconAreaController;
-    private ConfigurationListener mDensityChangeListener;
+    private ConfigurationListener mConfigurationListener;
     private InflationExceptionHandler mInflationExceptionHandler = this::handleInflationException;
 
     private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
@@ -945,13 +944,18 @@
 
         Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
 
-        mDensityChangeListener = new ConfigurationListener() {
+        mConfigurationListener = new ConfigurationListener() {
+            @Override
+            public void onConfigChanged(Configuration newConfig) {
+                StatusBar.this.onConfigurationChanged(newConfig);
+            }
+
             @Override
             public void onDensityOrFontScaleChanged() {
                 StatusBar.this.onDensityOrFontScaleChanged();
             }
         };
-        Dependency.get(ConfigurationController.class).addCallback(mDensityChangeListener);
+        Dependency.get(ConfigurationController.class).addCallback(mConfigurationListener);
     }
 
     protected void createIconController() {
@@ -2034,10 +2038,10 @@
     }
 
     private void updateEmptyShadeView() {
-        boolean showEmptyShade =
+        boolean showEmptyShadeView =
                 mState != StatusBarState.KEYGUARD &&
                         mNotificationData.getActiveNotifications().size() == 0;
-        mNotificationPanel.setShadeEmpty(showEmptyShade);
+        mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
     }
 
     private void updateSpeedBumpIndex() {
@@ -2506,7 +2510,7 @@
      * This needs to be called if state used by {@link #adjustDisableFlags} changes.
      */
     public void recomputeDisableFlags(boolean animate) {
-        mCommandQueue.disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
+        mCommandQueue.recomputeDisableFlags(animate);
     }
 
     protected H createHandler() {
@@ -2720,8 +2724,8 @@
         return mLaunchTransitionFadingAway;
     }
 
-    public boolean shouldHideNotificationIcons() {
-        return mNotificationPanel.shouldHideNotificationIcons();
+    public boolean hideStatusBarIconsWhenExpanded() {
+        return mNotificationPanel.hideStatusBarIconsWhenExpanded();
     }
 
     /**
@@ -2963,7 +2967,7 @@
         runPostCollapseRunnables();
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         showBouncerIfKeyguard();
-        recomputeDisableFlags(shouldAnimatIconHiding() /* animate */);
+        recomputeDisableFlags(mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -2972,10 +2976,6 @@
         }
     }
 
-    private boolean shouldAnimatIconHiding() {
-        return mNotificationPanel.shouldAnimateIconHiding();
-    }
-
     public boolean interceptTouchEvent(MotionEvent event) {
         if (DEBUG_GESTURES) {
             if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -3910,7 +3910,7 @@
         }
         Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
         mDeviceProvisionedController.removeCallback(mUserSetupObserver);
-        Dependency.get(ConfigurationController.class).removeCallback(mDensityChangeListener);
+        Dependency.get(ConfigurationController.class).removeCallback(mConfigurationListener);
     }
 
     private boolean mDemoModeAllowed;
@@ -6570,6 +6570,7 @@
                 }
             }
         }
+        mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
 
         mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
         mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 9a3fabb..92f8c7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -100,10 +100,6 @@
         mNotificationChannel = new NotificationChannel(
                 TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
         when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
-                eq(R.string.notification_menu_accessibility), anyObject())).thenReturn(
-                        getContext().getString(R.string.notification_menu_accessibility));
-
         when(mMockINotificationManager.getNumNotificationChannelsForPackage(
                 eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(1);
     }
@@ -174,6 +170,28 @@
 
     @Test
     @UiThreadTest
+    public void testBindNotification_SetsGroupName_resId() throws Exception {
+        when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
+                eq(R.string.legacy_vpn_name), anyObject())).thenReturn(
+                getContext().getString(R.string.legacy_vpn_name));
+        mNotificationChannel.setGroup("test_group_id");
+        final NotificationChannelGroup notificationChannelGroup =
+                new NotificationChannelGroup("test_group_id", R.string.legacy_vpn_name);
+        when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+                eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt()))
+                .thenReturn(notificationChannelGroup);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+        final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
+        assertEquals(View.VISIBLE, groupNameView.getVisibility());
+        assertEquals(mContext.getString(R.string.legacy_vpn_name), groupNameView.getText());
+        final TextView groupDividerView =
+                (TextView) mNotificationInfo.findViewById(R.id.pkg_group_divider);
+        assertEquals(View.VISIBLE, groupDividerView.getVisibility());
+    }
+
+    @Test
+    @UiThreadTest
     public void testBindNotification_SetsTextChannelName() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 mMockStatusBarNotification, mNotificationChannel, null, null, null);
@@ -184,6 +202,9 @@
     @Test
     @UiThreadTest
     public void testBindNotification_SetsTextChannelName_resId() throws Exception {
+        when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
+                eq(R.string.notification_menu_accessibility), anyObject())).thenReturn(
+                getContext().getString(R.string.notification_menu_accessibility));
         NotificationChannel notificationChannelResId = new NotificationChannel(
                 TEST_CHANNEL, R.string.notification_menu_accessibility,
                 NotificationManager.IMPORTANCE_LOW);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e9e73cc..54ee5dc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12111,6 +12111,12 @@
         mRecentTasks.notifyTaskPersisterLocked(task, flush);
     }
 
+    /** Notifies all listeners when the pinned stack animation starts. */
+    @Override
+    public void notifyPinnedStackAnimationStarted() {
+        mTaskChangeNotificationController.notifyPinnedStackAnimationStarted();
+    }
+
     /** Notifies all listeners when the pinned stack animation ends. */
     @Override
     public void notifyPinnedStackAnimationEnded() {
@@ -19013,8 +19019,7 @@
         return ActivityManager.BROADCAST_SUCCESS;
     }
 
-    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
-            int skipCount, long dispatchTime) {
+    final void rotateBroadcastStatsIfNeededLocked() {
         final long now = SystemClock.elapsedRealtime();
         if (mCurBroadcastStats == null ||
                 (mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
@@ -19025,9 +19030,19 @@
             }
             mCurBroadcastStats = new BroadcastStats();
         }
+    }
+
+    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+            int skipCount, long dispatchTime) {
+        rotateBroadcastStatsIfNeededLocked();
         mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
     }
 
+    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+        rotateBroadcastStatsIfNeededLocked();
+        mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+    }
+
     final Intent verifyBroadcastLocked(Intent intent) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors() == true) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 75b5929..c9d19cb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1195,6 +1195,8 @@
                                 && r.intent.getPackage() == null
                                 && ((r.intent.getFlags()
                                         & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0))) {
+                        mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
+                                component.getPackageName());
                         Slog.w(TAG, "Background execution not allowed: receiving "
                                 + r.intent + " to "
                                 + component.flattenToShortString());
diff --git a/services/core/java/com/android/server/am/BroadcastStats.java b/services/core/java/com/android/server/am/BroadcastStats.java
index fdbaada..fd24582 100644
--- a/services/core/java/com/android/server/am/BroadcastStats.java
+++ b/services/core/java/com/android/server/am/BroadcastStats.java
@@ -47,6 +47,7 @@
     static final class ActionEntry {
         final String mAction;
         final ArrayMap<String, PackageEntry> mPackages = new ArrayMap<>();
+        final ArrayMap<String, ViolationEntry> mBackgroundCheckViolations = new ArrayMap<>();
         int mReceiveCount;
         int mSkipCount;
         long mTotalDispatchTime;
@@ -61,6 +62,10 @@
         int mSendCount;
     }
 
+    static final class ViolationEntry {
+        int mCount;
+    }
+
     public BroadcastStats() {
         mStartRealtime = SystemClock.elapsedRealtime();
         mStartUptime = SystemClock.uptimeMillis();
@@ -87,6 +92,20 @@
         pe.mSendCount++;
     }
 
+    public void addBackgroundCheckViolation(String action, String targetPackage) {
+        ActionEntry ae = mActions.get(action);
+        if (ae == null) {
+            ae = new ActionEntry(action);
+            mActions.put(action, ae);
+        }
+        ViolationEntry ve = ae.mBackgroundCheckViolations.get(targetPackage);
+        if (ve == null) {
+            ve = new ViolationEntry();
+            ae.mBackgroundCheckViolations.put(targetPackage, ve);
+        }
+        ve.mCount++;
+    }
+
     public boolean dumpStats(PrintWriter pw, String prefix, String dumpPackage) {
         boolean printedSomething = false;
         ArrayList<ActionEntry> actions = new ArrayList<>(mActions.size());
@@ -123,6 +142,15 @@
                 pw.print(pe.mSendCount);
                 pw.println(" times");
             }
+            for (int j=ae.mBackgroundCheckViolations.size()-1; j>=0; j--) {
+                pw.print(prefix);
+                pw.print("  Bg Check Violation ");
+                pw.print(ae.mBackgroundCheckViolations.keyAt(j));
+                pw.print(": ");
+                ViolationEntry ve = ae.mBackgroundCheckViolations.valueAt(j);
+                pw.print(ve.mCount);
+                pw.println(" times");
+            }
         }
         return printedSomething;
     }
@@ -158,6 +186,14 @@
                 pw.print(pe.mSendCount);
                 pw.println();
             }
+            for (int j=ae.mBackgroundCheckViolations.size()-1; j>=0; j--) {
+                pw.print("v,");
+                pw.print(ae.mBackgroundCheckViolations.keyAt(j));
+                ViolationEntry ve = ae.mBackgroundCheckViolations.valueAt(j);
+                pw.print(",");
+                pw.print(ve.mCount);
+                pw.println();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 9dfc7cd..9a98bc6 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -46,6 +46,7 @@
     static final int NOTIFY_TASK_REMOVAL_STARTED_LISTENERS = 13;
     static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
     static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
+    static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
 
     // Delay in notifying task stack change listeners (in millis)
     static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -100,6 +101,10 @@
         l.onPinnedActivityRestartAttempt((String) m.obj);
     };
 
+    private final TaskStackConsumer mNotifyPinnedStackAnimationStarted = (l, m) -> {
+        l.onPinnedStackAnimationStarted();
+    };
+
     private final TaskStackConsumer mNotifyPinnedStackAnimationEnded = (l, m) -> {
         l.onPinnedStackAnimationEnded();
     };
@@ -166,6 +171,9 @@
                 case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
                     break;
+                case NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG:
+                    forAllRemoteListeners(mNotifyPinnedStackAnimationStarted, msg);
+                    break;
                 case NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyPinnedStackAnimationEnded, msg);
                     break;
@@ -276,6 +284,15 @@
         msg.sendToTarget();
     }
 
+    /** Notifies all listeners when the pinned stack animation starts. */
+    void notifyPinnedStackAnimationStarted() {
+        mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
+        final Message msg =
+                mHandler.obtainMessage(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
+        forAllLocalListeners(mNotifyPinnedStackAnimationStarted, msg);
+        msg.sendToTarget();
+    }
+
     /** Notifies all listeners when the pinned stack animation ends. */
     void notifyPinnedStackAnimationEnded() {
         mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index e72f7ff..476eb10 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -192,9 +192,14 @@
                             if (TAG_GROUP.equals(tagName)) {
                                 String id = parser.getAttributeValue(null, ATT_ID);
                                 CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
+                                int groupNameRes = safeInt(parser, ATT_NAME_RES_ID, 0);
                                 if (!TextUtils.isEmpty(id)) {
-                                    final NotificationChannelGroup group =
-                                            new NotificationChannelGroup(id, groupName);
+                                    NotificationChannelGroup group = null;
+                                    if (groupName != null) {
+                                        group = new NotificationChannelGroup(id, groupName);
+                                    } else {
+                                        group = new NotificationChannelGroup(id, groupNameRes);
+                                    }
                                     r.groups.put(id, group);
                                 }
                             }
@@ -202,7 +207,7 @@
                             if (TAG_CHANNEL.equals(tagName)) {
                                 String id = parser.getAttributeValue(null, ATT_ID);
                                 CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
-                                int channelNameRes = safeInt(parser, ATT_NAME_RES_ID, -1);
+                                int channelNameRes = safeInt(parser, ATT_NAME_RES_ID, 0);
                                 int channelImportance =
                                         safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
 
@@ -473,7 +478,8 @@
         Preconditions.checkNotNull(pkg);
         Preconditions.checkNotNull(group);
         Preconditions.checkNotNull(group.getId());
-        Preconditions.checkNotNull(group.getName());
+        Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName())
+                || group.getNameResId() != 0);
         Record r = getOrCreateRecord(pkg, uid);
         if (r == null) {
             throw new IllegalArgumentException("Invalid package");
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index ba4d46a..a692559 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -44,8 +44,11 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -70,6 +73,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -193,6 +197,14 @@
 
     static final String PERMISSION_DENIED = "Operation not permitted for user shell";
 
+    /**
+     * The system property that specifies the default overlays to apply.
+     * This is a semicolon separated list of package names.
+     *
+     * Ex: com.android.vendor.overlay_one;com.android.vendor.overlay_two
+     */
+    private static final String DEFAULT_OVERLAYS_PROP = "ro.boot.vendor.overlay.theme";
+
     private final Object mLock = new Object();
 
     private final AtomicFile mSettingsFile;
@@ -216,7 +228,8 @@
         mUserManager = UserManagerService.getInstance();
         IdmapManager im = new IdmapManager(installer);
         mSettings = new OverlayManagerSettings();
-        mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings);
+        mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
+                getDefaultOverlayPackages());
 
         final IntentFilter packageFilter = new IntentFilter();
         packageFilter.addAction(ACTION_PACKAGE_ADDED);
@@ -257,6 +270,21 @@
         updateAssets(newUserId, targets);
     }
 
+    private static Set<String> getDefaultOverlayPackages() {
+        final String str = SystemProperties.get(DEFAULT_OVERLAYS_PROP);
+        if (TextUtils.isEmpty(str)) {
+            return Collections.emptySet();
+        }
+
+        final ArraySet<String> defaultPackages = new ArraySet<>();
+        for (String packageName : str.split(";")) {
+            if (!TextUtils.isEmpty(packageName)) {
+                defaultPackages.add(packageName);
+            }
+        }
+        return defaultPackages;
+    }
+
     private final class PackageReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 0e33409..ed49383 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -27,8 +27,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -53,13 +53,16 @@
     private final PackageManagerHelper mPackageManager;
     private final IdmapManager mIdmapManager;
     private final OverlayManagerSettings mSettings;
+    private final Set<String> mDefaultOverlays;
 
     OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager,
             @NonNull final IdmapManager idmapManager,
-            @NonNull final OverlayManagerSettings settings) {
+            @NonNull final OverlayManagerSettings settings,
+            @NonNull final Set<String> defaultOverlays) {
         mPackageManager = packageManager;
         mIdmapManager = idmapManager;
         mSettings = settings;
+        mDefaultOverlays = defaultOverlays;
     }
 
     /*
@@ -92,12 +95,22 @@
             final PackageInfo overlayPackage = overlayPackages.get(i);
             final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
             if (oi == null || !oi.targetPackageName.equals(overlayPackage.overlayTarget)) {
-                if (oi != null) {
-                    packagesToUpdateAssets.add(oi.targetPackageName);
-                }
+                // Update the overlay if it didn't exist or had the wrong target package.
                 mSettings.init(overlayPackage.packageName, newUserId,
                         overlayPackage.overlayTarget,
                         overlayPackage.applicationInfo.getBaseCodePath());
+
+                if (oi == null) {
+                    // This overlay does not exist in our settings.
+                    if (mDefaultOverlays.contains(overlayPackage.packageName)) {
+                        // Enable this overlay by default.
+                        mSettings.setEnabled(overlayPackage.packageName, newUserId, true);
+                    }
+                } else {
+                    // The targetPackageName we have stored doesn't match the overlay's target.
+                    // Queue the old target for an update as well.
+                    packagesToUpdateAssets.add(oi.targetPackageName);
+                }
             }
 
             try {
@@ -132,7 +145,7 @@
             }
         }
 
-        return new ArrayList<String>(packagesToUpdateAssets);
+        return new ArrayList<>(packagesToUpdateAssets);
     }
 
     void onUserRemoved(final int userId) {
@@ -303,6 +316,7 @@
 
     void onDump(@NonNull final PrintWriter pw) {
         mSettings.dump(pw);
+        pw.println("Default overlays: " + TextUtils.join(";", mDefaultOverlays));
     }
 
     List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 44908a7..ff5c594 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -45,7 +45,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
-import java.util.Map;
 
 /**
  * Data structure representing the current state of all overlay packages in the
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index caacc46..1fe4ccc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -496,18 +496,6 @@
     private static final String PACKAGE_SCHEME = "package";
 
     private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
-    /**
-     * If VENDOR_OVERLAY_THEME_PROPERTY is set, search for runtime resource overlay APKs also in
-     * VENDOR_OVERLAY_DIR/<value of VENDOR_OVERLAY_THEME_PROPERTY> in addition to
-     * VENDOR_OVERLAY_DIR.
-     */
-    private static final String VENDOR_OVERLAY_THEME_PROPERTY = "ro.boot.vendor.overlay.theme";
-    /**
-     * Same as VENDOR_OVERLAY_THEME_PROPERTY, except persistent. If set will override whatever
-     * is in VENDOR_OVERLAY_THEME_PROPERTY.
-     */
-    private static final String VENDOR_OVERLAY_THEME_PERSIST_PROPERTY
-            = "persist.vendor.overlay.theme";
 
     /** Permission grant: not grant the permission. */
     private static final int GRANT_DENIED = 1;
@@ -2476,16 +2464,6 @@
             // Collect vendor overlay packages. (Do this before scanning any apps.)
             // For security and version matching reason, only consider
             // overlay packages if they reside in the right directory.
-            String overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PERSIST_PROPERTY);
-            if (overlayThemeDir.isEmpty()) {
-                overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PROPERTY);
-            }
-            if (!overlayThemeDir.isEmpty()) {
-                scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlayThemeDir), mDefParseFlags
-                        | PackageParser.PARSE_IS_SYSTEM
-                        | PackageParser.PARSE_IS_SYSTEM_DIR
-                        | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
-            }
             scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
                     | PackageParser.PARSE_IS_SYSTEM
                     | PackageParser.PARSE_IS_SYSTEM_DIR
@@ -3415,6 +3393,8 @@
             return null;
         }
 
+        rebaseEnabledOverlays(packageInfo.applicationInfo, userId);
+
         packageInfo.packageName = packageInfo.applicationInfo.packageName =
                 resolveExternalPackageNameLPr(p);
 
@@ -4147,8 +4127,12 @@
             if (a != null && mSettings.isEnabledAndMatchLPr(a.info, flags, userId)) {
                 PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                 if (ps == null) return null;
-                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
-                        userId);
+                ActivityInfo ri = PackageParser.generateActivityInfo(a, flags,
+                        ps.readUserState(userId), userId);
+                if (ri != null) {
+                    rebaseEnabledOverlays(ri.applicationInfo, userId);
+                }
+                return ri;
             }
         }
         return null;
@@ -4274,8 +4258,12 @@
             if (s != null && mSettings.isEnabledAndMatchLPr(s.info, flags, userId)) {
                 PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                 if (ps == null) return null;
-                return PackageParser.generateServiceInfo(s, flags, ps.readUserState(userId),
-                        userId);
+                ServiceInfo si = PackageParser.generateServiceInfo(s, flags,
+                        ps.readUserState(userId), userId);
+                if (si != null) {
+                    rebaseEnabledOverlays(si.applicationInfo, userId);
+                }
+                return si;
             }
         }
         return null;
@@ -4294,8 +4282,12 @@
             if (p != null && mSettings.isEnabledAndMatchLPr(p.info, flags, userId)) {
                 PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                 if (ps == null) return null;
-                return PackageParser.generateProviderInfo(p, flags, ps.readUserState(userId),
-                        userId);
+                ProviderInfo pi = PackageParser.generateProviderInfo(p, flags,
+                        ps.readUserState(userId), userId);
+                if (pi != null) {
+                    rebaseEnabledOverlays(pi.applicationInfo, userId);
+                }
+                return pi;
             }
         }
         return null;
@@ -15919,14 +15911,6 @@
 
             final PackageSetting ps = mSettings.mPackages.get(pkgName);
 
-            // don't allow an upgrade from full to ephemeral
-            if (isInstantApp && !ps.getInstantApp(user.getIdentifier())) {
-                // can't downgrade from full to instant
-                Slog.w(TAG, "Can't replace app with instant app: " + pkgName);
-                res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
-                return;
-            }
-
             // verify signatures are valid
             if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
                 if (!checkUpgradeKeySetLP(ps, pkg)) {
@@ -15984,6 +15968,27 @@
             // In case of rollback, remember per-user/profile install state
             allUsers = sUserManager.getUserIds();
             installedUsers = ps.queryInstalledUsers(allUsers, true);
+
+            // don't allow an upgrade from full to ephemeral
+            if (isInstantApp) {
+                if (user == null || user.getIdentifier() == UserHandle.USER_ALL) {
+                    for (int currentUser : allUsers) {
+                        if (!ps.getInstantApp(currentUser)) {
+                            // can't downgrade from full to instant
+                            Slog.w(TAG, "Can't replace full app with instant app: " + pkgName
+                                    + " for user: " + currentUser);
+                            res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
+                            return;
+                        }
+                    }
+                } else if (!ps.getInstantApp(user.getIdentifier())) {
+                    // can't downgrade from full to instant
+                    Slog.w(TAG, "Can't replace full app with instant app: " + pkgName
+                            + " for user: " + user.getIdentifier());
+                    res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
+                    return;
+                }
+            }
         }
 
         // Update what is removed
@@ -23207,7 +23212,7 @@
                 ArrayList<String> paths = null;
                 if (overlayPackageNames != null) {
                     final int N = overlayPackageNames.size();
-                    paths = new ArrayList<String>(N);
+                    paths = new ArrayList<>(N);
                     for (int i = 0; i < N; i++) {
                         final String packageName = overlayPackageNames.get(i);
                         final PackageParser.Package pkg = mPackages.get(packageName);
@@ -23222,7 +23227,7 @@
                 ArrayMap<String, ArrayList<String>> userSpecificOverlays =
                     mEnabledOverlayPaths.get(userId);
                 if (userSpecificOverlays == null) {
-                    userSpecificOverlays = new ArrayMap<String, ArrayList<String>>();
+                    userSpecificOverlays = new ArrayMap<>();
                     mEnabledOverlayPaths.put(userId, userSpecificOverlays);
                 }
 
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 10d30aa..c064392 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -34,21 +34,37 @@
 import android.content.pm.UserInfo;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.format.DateUtils;
+import android.util.Pair;
 import android.util.Slog;
+import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.Installer;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 
-
 /**
  * CacheQuotaStrategy is a strategy for determining cache quotas using usage stats and foreground
  * time using the calculation as defined in the refuel rocket.
@@ -58,17 +74,28 @@
 
     private final Object mLock = new Object();
 
+    // XML Constants
+    private static final String CACHE_INFO_TAG = "cache-info";
+    private static final String ATTR_PREVIOUS_BYTES = "previousBytes";
+    private static final String TAG_QUOTA = "quota";
+    private static final String ATTR_UUID = "uuid";
+    private static final String ATTR_UID = "uid";
+    private static final String ATTR_QUOTA_IN_BYTES = "bytes";
+
     private final Context mContext;
     private final UsageStatsManagerInternal mUsageStats;
     private final Installer mInstaller;
     private ServiceConnection mServiceConnection;
     private ICacheQuotaService mRemoteService;
+    private AtomicFile mPreviousValuesFile;
 
     public CacheQuotaStrategy(
             Context context, UsageStatsManagerInternal usageStatsManager, Installer installer) {
         mContext = Preconditions.checkNotNull(context);
         mUsageStats = Preconditions.checkNotNull(usageStatsManager);
         mInstaller = Preconditions.checkNotNull(installer);
+        mPreviousValuesFile = new AtomicFile(new File(
+                new File(Environment.getDataDirectory(), "system"), "cachequota.xml"));
     }
 
     /**
@@ -128,7 +155,7 @@
     }
 
     /**
-     * Returns a list of CacheQuotaRequests which do not have their quotas filled out for apps
+     * Returns a list of CacheQuotaHints which do not have their quotas filled out for apps
      * which have been used in the last year.
      */
     private List<CacheQuotaHint> getUnfulfilledRequests() {
@@ -176,6 +203,11 @@
         final List<CacheQuotaHint> processedRequests =
                 data.getParcelableArrayList(
                         CacheQuotaService.REQUEST_LIST_KEY);
+        pushProcessedQuotas(processedRequests);
+        writeXmlToFile(processedRequests);
+    }
+
+    private void pushProcessedQuotas(List<CacheQuotaHint> processedRequests) {
         final int requestSize = processedRequests.size();
         for (int i = 0; i < requestSize; i++) {
             CacheQuotaHint request = processedRequests.get(i);
@@ -200,8 +232,10 @@
     }
 
     private void disconnectService() {
-        mContext.unbindService(mServiceConnection);
-        mServiceConnection = null;
+        if (mServiceConnection != null) {
+            mContext.unbindService(mServiceConnection);
+            mServiceConnection = null;
+        }
     }
 
     private ComponentName getServiceComponentName() {
@@ -223,4 +257,131 @@
         ServiceInfo serviceInfo = resolveInfo.serviceInfo;
         return new ComponentName(serviceInfo.packageName, serviceInfo.name);
     }
+
+    private void writeXmlToFile(List<CacheQuotaHint> processedRequests) {
+        FileOutputStream fileStream = null;
+        try {
+            XmlSerializer out = new FastXmlSerializer();
+            fileStream = mPreviousValuesFile.startWrite();
+            out.setOutput(fileStream, StandardCharsets.UTF_8.name());
+            saveToXml(out, processedRequests, 0);
+            mPreviousValuesFile.finishWrite(fileStream);
+        } catch (Exception e) {
+            Slog.e(TAG, "An error occurred while writing the cache quota file.", e);
+            mPreviousValuesFile.failWrite(fileStream);
+        }
+    }
+
+    /**
+     * Initializes the quotas from the file.
+     * @return the number of bytes that were free on the device when the quotas were last calced.
+     */
+    public long setupQuotasFromFile() throws IOException {
+        FileInputStream stream;
+        try {
+            stream = mPreviousValuesFile.openRead();
+        } catch (FileNotFoundException e) {
+            // The file may not exist yet -- this isn't truly exceptional.
+            return -1;
+        }
+
+        Pair<Long, List<CacheQuotaHint>> cachedValues = null;
+        try {
+            cachedValues = readFromXml(stream);
+        } catch (XmlPullParserException e) {
+            throw new IllegalStateException(e.getMessage());
+        }
+
+        if (cachedValues == null) {
+            Slog.e(TAG, "An error occurred while parsing the cache quota file.");
+            return -1;
+        }
+        pushProcessedQuotas(cachedValues.second);
+        return cachedValues.first;
+    }
+
+    @VisibleForTesting
+    static void saveToXml(XmlSerializer out,
+            List<CacheQuotaHint> requests, long bytesWhenCalculated) throws IOException {
+        out.startDocument(null, true);
+        out.startTag(null, CACHE_INFO_TAG);
+        int requestSize = requests.size();
+        out.attribute(null, ATTR_PREVIOUS_BYTES, Long.toString(bytesWhenCalculated));
+
+        for (int i = 0; i < requestSize; i++) {
+            CacheQuotaHint request = requests.get(i);
+            out.startTag(null, TAG_QUOTA);
+            String uuid = request.getVolumeUuid();
+            if (uuid != null) {
+                out.attribute(null, ATTR_UUID, request.getVolumeUuid());
+            }
+            out.attribute(null, ATTR_UID, Integer.toString(request.getUid()));
+            out.attribute(null, ATTR_QUOTA_IN_BYTES, Long.toString(request.getQuota()));
+            out.endTag(null, TAG_QUOTA);
+        }
+        out.endTag(null, CACHE_INFO_TAG);
+        out.endDocument();
+    }
+
+    protected static Pair<Long, List<CacheQuotaHint>> readFromXml(InputStream inputStream)
+            throws XmlPullParserException, IOException {
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+        int eventType = parser.getEventType();
+        while (eventType != XmlPullParser.START_TAG &&
+                eventType != XmlPullParser.END_DOCUMENT) {
+            eventType = parser.next();
+        }
+
+        if (eventType == XmlPullParser.END_DOCUMENT) {
+            Slog.d(TAG, "No quotas found in quota file.");
+            return null;
+        }
+
+        String tagName = parser.getName();
+        if (!CACHE_INFO_TAG.equals(tagName)) {
+            throw new IllegalStateException("Invalid starting tag.");
+        }
+
+        final List<CacheQuotaHint> quotas = new ArrayList<>();
+        long previousBytes;
+        try {
+            previousBytes = Long.parseLong(parser.getAttributeValue(
+                    null, ATTR_PREVIOUS_BYTES));
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(
+                    "Previous bytes formatted incorrectly; aborting quota read.");
+        }
+
+        eventType = parser.next();
+        do {
+            if (eventType == XmlPullParser.START_TAG) {
+                tagName = parser.getName();
+                if (TAG_QUOTA.equals(tagName)) {
+                    CacheQuotaHint request = getRequestFromXml(parser);
+                    if (request == null) {
+                        continue;
+                    }
+                    quotas.add(request);
+                }
+            }
+            eventType = parser.next();
+        } while (eventType != XmlPullParser.END_DOCUMENT);
+        return new Pair<>(previousBytes, quotas);
+    }
+
+    @VisibleForTesting
+    static CacheQuotaHint getRequestFromXml(XmlPullParser parser) {
+        try {
+            String uuid = parser.getAttributeValue(null, ATTR_UUID);
+            int uid = Integer.parseInt(parser.getAttributeValue(null, ATTR_UID));
+            long bytes = Long.parseLong(parser.getAttributeValue(null, ATTR_QUOTA_IN_BYTES));
+            return new CacheQuotaHint.Builder()
+                    .setVolumeUuid(uuid).setUid(uid).setQuota(bytes).build();
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Invalid cache quota request, skipping.");
+            return null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 6cc2efb..7588b71 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1439,6 +1439,14 @@
             mBoundsAnimating = true;
             mBoundsAnimatingToFullscreen = toFullscreen;
         }
+
+        if (mStackId == PINNED_STACK_ID) {
+            try {
+                mService.mActivityManager.notifyPinnedStackAnimationStarted();
+            } catch (RemoteException e) {
+                // I don't believe you...
+            }
+        }
     }
 
     @Override  // AnimatesBounds
@@ -1448,6 +1456,7 @@
             mBoundsAnimationTarget.setEmpty();
             mService.requestTraversal();
         }
+
         if (mStackId == PINNED_STACK_ID) {
             try {
                 mService.mActivityManager.notifyPinnedStackAnimationEnded();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8b94ca0..9c3ecd0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -99,6 +99,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.StringParceledListSlice;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -166,6 +167,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
@@ -243,10 +245,14 @@
 
     private static final String TAG_DEFAULT_INPUT_METHOD_SET = "default-ime-set";
 
+    private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
+
     private static final String ATTR_ID = "id";
 
     private static final String ATTR_VALUE = "value";
 
+    private static final String ATTR_ALIAS = "alias";
+
     private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
 
     private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
@@ -480,6 +486,7 @@
         final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
         final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
 
+        // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
         final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
 
         // This is the list of component allowed to start lock task mode.
@@ -504,6 +511,9 @@
 
         boolean mDefaultInputMethodSet = false;
 
+        // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+        Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
+
         // Used for initialization of users created by createAndManageUsers.
         boolean mAdminBroadcastPending = false;
         PersistableBundle mInitBundle = null;
@@ -518,6 +528,7 @@
     final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
 
     final Handler mHandler;
+    final Handler mBackgroundHandler;
 
     /** Listens on any device, even when mHasFeature == false. */
     final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
@@ -619,6 +630,25 @@
                 handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
             } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
                 clearWipeProfileNotification();
+            } else if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
+                mBackgroundHandler.post(() ->  {
+                    try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+                            UserHandle.of(userHandle))) {
+                        final List<String> caCerts =
+                                keyChainConnection.getService().getUserCaAliases().getList();
+                        synchronized (DevicePolicyManagerService.this) {
+                            if (getUserData(userHandle).mOwnerInstalledCaCerts
+                                    .retainAll(caCerts)) {
+                                saveSettingsLocked(userHandle);
+                            }
+                        }
+                    } catch (InterruptedException e) {
+                        Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+                        Thread.currentThread().interrupt();
+                    } catch (RemoteException e) {
+                        Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+                    }
+                });
             }
         }
 
@@ -1786,6 +1816,7 @@
                 .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
         mIsWatch = mInjector.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_WATCH);
+        mBackgroundHandler = BackgroundThread.getHandler();
 
         // Broadcast filter for changes to the trusted certificate store. These changes get a
         // separate intent filter so we can listen to them even when device_admin is off.
@@ -1807,6 +1838,7 @@
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
+        filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
         filter = new IntentFilter();
@@ -2580,6 +2612,12 @@
                 out.endTag(null, TAG_DEFAULT_INPUT_METHOD_SET);
             }
 
+            for (final String cert : policy.mOwnerInstalledCaCerts) {
+                out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+                out.attribute(null, ATTR_ALIAS, cert);
+                out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+            }
+
             out.endTag(null, "policies");
 
             out.endDocument();
@@ -2696,6 +2734,7 @@
             policy.mAdminList.clear();
             policy.mAdminMap.clear();
             policy.mAffiliationIds.clear();
+            policy.mOwnerInstalledCaCerts.clear();
             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -2791,6 +2830,8 @@
                             parser.getAttributeValue(null, ATTR_VALUE));
                 } else if (TAG_DEFAULT_INPUT_METHOD_SET.equals(tag)) {
                     policy.mDefaultInputMethodSet = true;
+                } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
+                    policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
                 } else {
                     Slog.w(LOG_TAG, "Unknown tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -4691,17 +4732,15 @@
             return false;
         }
 
-        final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+        final UserHandle userHandle = UserHandle.of(mInjector.userHandleGetCallingUserId());
         final long id = mInjector.binderClearCallingIdentity();
+        String alias = null;
         try {
-            final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
-            try {
-                keyChainConnection.getService().installCaCertificate(pemCert);
-                return true;
+            try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+                    userHandle)) {
+                alias = keyChainConnection.getService().installCaCertificate(pemCert);
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
-            } finally {
-                keyChainConnection.close();
             }
         } catch (InterruptedException e1) {
             Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
@@ -4709,6 +4748,16 @@
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
+        if (alias == null) {
+            Log.w(LOG_TAG, "Problem installing cert");
+        } else {
+            synchronized (this) {
+                final int userId = userHandle.getIdentifier();
+                getUserData(userId).mOwnerInstalledCaCerts.add(alias);
+                saveSettingsLocked(userId);
+            }
+            return true;
+        }
         return false;
     }
 
@@ -4722,25 +4771,31 @@
     public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
         enforceCanManageCaCerts(admin, callerPackage);
 
-        final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+        final int userId = mInjector.userHandleGetCallingUserId();
+        final UserHandle userHandle = UserHandle.of(userId);
         final long id = mInjector.binderClearCallingIdentity();
         try {
-            final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
-            try {
+            try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+                    userHandle)) {
                 for (int i = 0 ; i < aliases.length; i++) {
                     keyChainConnection.getService().deleteCaCertificate(aliases[i]);
                 }
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
-            } finally {
-                keyChainConnection.close();
+                return;
             }
         } catch (InterruptedException ie) {
             Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
             Thread.currentThread().interrupt();
+            return;
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
+        synchronized (this) {
+            if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
+                saveSettingsLocked(userId);
+            }
+        }
     }
 
     @Override
@@ -6731,6 +6786,7 @@
         }
         final DevicePolicyData policyData = getUserData(userId);
         policyData.mDefaultInputMethodSet = false;
+        policyData.mOwnerInstalledCaCerts.clear();
         saveSettingsLocked(userId);
         clearUserPoliciesLocked(userId);
         mOwners.removeProfileOwner(userId);
@@ -7127,29 +7183,32 @@
     }
 
     private void enforceFullCrossUsersPermission(int userHandle) {
-        enforceSystemUserOrPermission(userHandle,
+        enforceSystemUserOrPermissionIfCrossUser(userHandle,
                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
     }
 
     private void enforceCrossUsersPermission(int userHandle) {
-        enforceSystemUserOrPermission(userHandle,
+        enforceSystemUserOrPermissionIfCrossUser(userHandle,
                 android.Manifest.permission.INTERACT_ACROSS_USERS);
     }
 
-    private void enforceSystemUserOrPermission(int userHandle, String permission) {
-        if (userHandle < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userHandle);
-        }
-        final int callingUid = mInjector.binderGetCallingUid();
-        if (userHandle == UserHandle.getUserId(callingUid)) {
-            return;
-        }
-        if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
+    private void enforceSystemUserOrPermission(String permission) {
+        if (!(isCallerWithSystemUid() || mInjector.binderGetCallingUid() == Process.ROOT_UID)) {
             mContext.enforceCallingOrSelfPermission(permission,
                     "Must be system or have " + permission + " permission");
         }
     }
 
+    private void enforceSystemUserOrPermissionIfCrossUser(int userHandle, String permission) {
+        if (userHandle < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userHandle);
+        }
+        if (userHandle == mInjector.userHandleGetCallingUserId()) {
+            return;
+        }
+        enforceSystemUserOrPermission(permission);
+    }
+
     private void enforceManagedProfile(int userHandle, String message) {
         if(!isManagedProfile(userHandle)) {
             throw new SecurityException("You can not " + message + " outside a managed profile.");
@@ -7184,6 +7243,21 @@
                 "Only profile owner, device owner and system may call this method.");
     }
 
+    private void enforceProfileOwnerOrFullCrossUsersPermission(int userId) {
+        if (userId == mInjector.userHandleGetCallingUserId()) {
+            synchronized (this) {
+                if (getActiveAdminWithPolicyForUidLocked(null,
+                        DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
+                                != null) {
+                    // Device Owner/Profile Owner may access the user it runs on.
+                    return;
+                }
+            }
+        }
+        // Otherwise, INTERACT_ACROSS_USERS_FULL permission, system UID or root UID is required.
+        enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+    }
+
     private void ensureCallerPackage(@Nullable String packageName) {
         if (packageName == null) {
             Preconditions.checkState(isCallerWithSystemUid(),
@@ -10897,4 +10971,14 @@
         }
         return getUserData(userId).mDefaultInputMethodSet;
     }
+
+    @Override
+    public StringParceledListSlice getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+        final int userId = user.getIdentifier();
+        enforceProfileOwnerOrFullCrossUsersPermission(userId);
+        synchronized (this) {
+            return new StringParceledListSlice(
+                    new ArrayList<>(getUserData(userId).mOwnerInstalledCaCerts));
+        }
+    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index ffb0a9e..25c29ee 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -209,6 +209,12 @@
         assertEquals(expected.getLightColor(), actual.getLightColor());
     }
 
+    private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getName(), actual.getName());
+        assertEquals(expected.getNameResId(), actual.getNameResId());
+    }
+
     @Test
     public void testFindAfterRankingWithASplitGroup() throws Exception {
         ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -262,8 +268,10 @@
     @Test
     public void testChannelXml() throws Exception {
         int nameResId = 924896;
+        int groupNameResId = 426272;
 
-        NotificationChannelGroup ncg = new NotificationChannelGroup("1", "2");
+        NotificationChannelGroup ncg = new NotificationChannelGroup("1", groupNameResId);
+        NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         NotificationChannel channel2 =
@@ -278,6 +286,7 @@
         channel2.setLightColor(Color.BLUE);
 
         mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
         mHelper.createNotificationChannel(pkg, uid, channel1, true);
         mHelper.createNotificationChannel(pkg, uid, channel2, false);
 
@@ -308,7 +317,9 @@
         for (NotificationChannelGroup actual : actualGroups) {
             if (ncg.getId().equals(actual.getId())) {
                 foundNcg = true;
-                 break;
+                compareGroups(ncg, actual);
+            } else if (ncg2.getId().equals(actual.getId())) {
+                compareGroups(ncg2, actual);
             }
         }
         assertTrue(foundNcg);
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 756514b..f797f31 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -49,6 +49,8 @@
 import android.os.UserManager;
 import android.os.UserManagerInternal;
 import android.provider.Settings;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
 import android.telephony.TelephonyManager;
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -122,6 +124,34 @@
     public DevicePolicyManager dpm;
     public DevicePolicyManagerServiceTestable dpms;
 
+    /*
+     * The CA cert below is the content of cacert.pem as generated by:
+     *
+     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+     */
+    private static final String TEST_CA =
+            "-----BEGIN CERTIFICATE-----\n" +
+            "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+            "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+            "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+            "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+            "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+            "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+            "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+            "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+            "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+            "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+            "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+            "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+            "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+            "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+            "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+            "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+            "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+            "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+            "wQ==\n" +
+            "-----END CERTIFICATE-----\n";
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -3916,6 +3946,124 @@
         assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
     }
 
+    public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
+        setDeviceOwner();
+
+        mContext.packageName = admin1.getPackageName();
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        verifyCanGetOwnerInstalledCaCerts(admin1);
+    }
+
+    public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
+        setAsProfileOwner(admin1);
+
+        mContext.packageName = admin1.getPackageName();
+        verifyCanGetOwnerInstalledCaCerts(admin1);
+        verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1);
+    }
+
+    public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
+        setAsProfileOwner(admin1);
+
+        final String delegate = "com.example.delegate";
+        final int delegateUid = setupPackageInPackageManager(delegate, 20988);
+        dpm.setCertInstallerPackage(admin1, delegate);
+
+        mContext.packageName = delegate;
+        mContext.binder.callingUid = delegateUid;
+        verifyCanGetOwnerInstalledCaCerts(null);
+        verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null);
+    }
+
+    private void verifyCanGetOwnerInstalledCaCerts(ComponentName caller) throws Exception {
+        final UserHandle user = UserHandle.getUserHandleForUid(mContext.binder.callingUid);
+        final int ownerUid = user.equals(UserHandle.SYSTEM) ?
+                DpmMockContext.CALLER_SYSTEM_USER_UID : DpmMockContext.CALLER_UID;
+
+        mContext.applicationInfo = new ApplicationInfo();
+        mContext.userContexts.put(user, mContext);
+        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+        // Install a CA cert.
+        final String alias = "cert";
+        final byte[] caCert = TEST_CA.getBytes();
+        when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+                .thenReturn(alias);
+        assertTrue(dpm.installCaCert(caller, caCert));
+        when(mContext.keyChainConnection.getService().getUserCaAliases())
+                .thenReturn(asSlice(new String[] {alias}));
+        mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+        flushTasks();
+
+        // Device Owner / Profile Owner can find out which CA certs were installed by itself.
+        final String packageName = mContext.packageName;
+        mContext.packageName = admin1.getPackageName();
+        final long callerIdentity = mContext.binder.clearCallingIdentity();
+        mContext.binder.callingUid = ownerUid;
+        List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+        assertNotNull(ownerInstalledCaCerts);
+        assertEquals(1, ownerInstalledCaCerts.size());
+        assertTrue(ownerInstalledCaCerts.contains(alias));
+
+        // Restarting the DPMS should not lose information.
+        initializeDpms();
+        assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+        // System can find out which CA certs were installed by the Device Owner / Profile Owner.
+        mContext.packageName = "com.android.frameworks.servicestests";
+        mContext.binder.clearCallingIdentity();
+        assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+        // Remove the CA cert.
+        mContext.packageName = packageName;
+        mContext.binder.restoreCallingIdentity(callerIdentity);
+        reset(mContext.keyChainConnection.getService());
+        mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+        flushTasks();
+
+        // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
+        // Owner.
+        mContext.packageName = admin1.getPackageName();
+        mContext.binder.callingUid = ownerUid;
+        ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+        assertNotNull(ownerInstalledCaCerts);
+        assertTrue(ownerInstalledCaCerts.isEmpty());
+
+        mContext.packageName = packageName;
+        mContext.binder.restoreCallingIdentity(callerIdentity);
+    }
+
+    private void verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(ComponentName caller)
+            throws Exception {
+        final UserHandle user = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+
+        mContext.applicationInfo = new ApplicationInfo();
+        mContext.userContexts.put(user, mContext);
+        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+        // Install a CA cert.
+        final String alias = "cert";
+        final byte[] caCert = TEST_CA.getBytes();
+        when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+                .thenReturn(alias);
+        assertTrue(dpm.installCaCert(caller, caCert));
+        when(mContext.keyChainConnection.getService().getUserCaAliases())
+                .thenReturn(asSlice(new String[] {alias}));
+        mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+        flushTasks();
+
+        // Removing the Profile Owner should clear the information which CA certs were installed
+        // by it.
+        mContext.packageName = admin1.getPackageName();
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.clearProfileOwner(admin1);
+        mContext.packageName = "com.android.frameworks.servicestests";
+        mContext.binder.clearCallingIdentity();
+        final List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+        assertNotNull(ownerInstalledCaCerts);
+        assertTrue(ownerInstalledCaCerts.isEmpty());
+    }
+
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
         when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
                 userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
@@ -3978,4 +4126,31 @@
     private static StringParceledListSlice asSlice(String[] s) {
         return new StringParceledListSlice(Arrays.asList(s));
     }
+
+    private void flushTasks() throws Exception {
+        Boolean tasksFlushed[] = new Boolean[] {false};
+        final Runnable tasksFlushedNotifier = () -> {
+            synchronized (tasksFlushed) {
+                tasksFlushed[0] = true;
+                tasksFlushed.notify();
+            }
+        };
+
+        // Flush main thread handler.
+        dpms.mHandler.post(tasksFlushedNotifier);
+        synchronized (tasksFlushed) {
+            if (!tasksFlushed[0]) {
+                tasksFlushed.wait();
+            }
+        }
+
+        // Flush background thread handler.
+        tasksFlushed[0] = false;
+        dpms.mBackgroundHandler.post(tasksFlushedNotifier);
+        synchronized (tasksFlushed) {
+            if (!tasksFlushed[0]) {
+                tasksFlushed.wait();
+            }
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 258b393..7d017c5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -316,6 +316,41 @@
 
     public ApplicationInfo applicationInfo = null;
 
+    // We have to keep track of broadcast receivers registered for a given intent ourselves as the
+    // DPM unit tests mock out the package manager and PackageManager.queryBroadcastReceivers() does
+    // not work.
+    private class BroadcastReceiverRegistration {
+        public final BroadcastReceiver receiver;
+        public final IntentFilter filter;
+        public final Handler scheduler;
+
+        public BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter,
+                Handler scheduler) {
+            this.receiver = receiver;
+            this.filter = filter;
+            this.scheduler = scheduler;
+        }
+
+        public void sendBroadcastIfApplicable(int userId, Intent intent) {
+            final BroadcastReceiver.PendingResult result = new BroadcastReceiver.PendingResult(
+                    0 /* resultCode */, null /* resultData */, null /* resultExtras */,
+                    0 /* type */, false /* ordered */, false /* sticky */, null /* token */, userId,
+                    0 /* flags */);
+            if (filter.match(null, intent, false, "DpmMockContext") > 0) {
+                if (scheduler != null) {
+                    scheduler.post(() -> {
+                        receiver.setPendingResult(result);
+                        receiver.onReceive(DpmMockContext.this, intent);
+                    });
+                } else {
+                    receiver.setPendingResult(result);
+                    receiver.onReceive(DpmMockContext.this, intent);
+                }
+            }
+        }
+    }
+    private List<BroadcastReceiverRegistration> mBroadcastReceivers = new ArrayList<>();
+
     public DpmMockContext(Context context, File dataDir) {
         realTestContext = context;
 
@@ -476,6 +511,13 @@
                 .thenReturn(isRunning);
     }
 
+    public void injectBroadcast(Intent intent) {
+        final int userId = UserHandle.getUserId(binder.getCallingUid());
+        for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
+            receiver.sendBroadcastIfApplicable(userId, intent);
+        }
+    }
+
     @Override
     public Resources getResources() {
         return resources;
@@ -681,24 +723,28 @@
 
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, null));
         return spiedContext.registerReceiver(receiver, filter);
     }
 
     @Override
     public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
             String broadcastPermission, Handler scheduler) {
+        mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
         return spiedContext.registerReceiver(receiver, filter, broadcastPermission, scheduler);
     }
 
     @Override
     public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
             IntentFilter filter, String broadcastPermission, Handler scheduler) {
+        mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
         return spiedContext.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
                 scheduler);
     }
 
     @Override
     public void unregisterReceiver(BroadcastReceiver receiver) {
+        mBroadcastReceivers.removeIf(r -> r.receiver == receiver);
         spiedContext.unregisterReceiver(receiver);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
new file mode 100644
index 0000000..1d62e01
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 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.server.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.usage.CacheQuotaHint;
+import android.test.AndroidTestCase;
+import android.util.Pair;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class CacheQuotaStrategyTest extends AndroidTestCase {
+    StringWriter mWriter;
+    FastXmlSerializer mOut;
+
+    @Before
+    public void setUp() throws Exception {
+        mWriter = new StringWriter();
+        mOut = new FastXmlSerializer();
+        mOut.setOutput(mWriter);
+    }
+
+    @Test
+    public void testEmptyWrite() throws Exception {
+        CacheQuotaStrategy.saveToXml(mOut, new ArrayList<>(), 0);
+        mOut.flush();
+
+        assertThat(mWriter.toString()).isEqualTo(
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+                        "<cache-info previousBytes=\"0\" />\n");
+    }
+
+    @Test
+    public void testWriteOneQuota() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        requests.add(buildCacheQuotaHint("uuid", 0, 100));
+
+        CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+        mOut.flush();
+
+        assertThat(mWriter.toString()).isEqualTo(
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+                "<cache-info previousBytes=\"1000\">\n"
+                        + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+                        + "</cache-info>\n");
+    }
+
+    @Test
+    public void testWriteMultipleQuotas() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        requests.add(buildCacheQuotaHint("uuid", 0, 100));
+        requests.add(buildCacheQuotaHint("uuid2", 10, 250));
+
+        CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+        mOut.flush();
+
+        assertThat(mWriter.toString()).isEqualTo(
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+                        "<cache-info previousBytes=\"1000\">\n"
+                        + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+                        + "<quota uuid=\"uuid2\" uid=\"10\" bytes=\"250\" />\n"
+                        + "</cache-info>\n");
+    }
+
+    @Test
+    public void testNullUuidDoesntCauseCrash() throws Exception {
+        ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+        requests.add(buildCacheQuotaHint(null, 0, 100));
+        requests.add(buildCacheQuotaHint(null, 10, 250));
+
+        CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+        mOut.flush();
+
+        assertThat(mWriter.toString()).isEqualTo(
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+                        "<cache-info previousBytes=\"1000\">\n"
+                        + "<quota uid=\"0\" bytes=\"100\" />\n"
+                        + "<quota uid=\"10\" bytes=\"250\" />\n"
+                        + "</cache-info>\n");
+    }
+
+    @Test
+    public void testReadMultipleQuotas() throws Exception {
+        String input = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<cache-info previousBytes=\"1000\">\n"
+                + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+                + "<quota uuid=\"uuid2\" uid=\"10\" bytes=\"250\" />\n"
+                + "</cache-info>\n";
+
+        Pair<Long, List<CacheQuotaHint>> output =
+                CacheQuotaStrategy.readFromXml(new ByteArrayInputStream(input.getBytes("UTF-8")));
+
+        assertThat(output.first).isEqualTo(1000);
+        assertThat(output.second).containsExactly(buildCacheQuotaHint("uuid", 0, 100),
+                buildCacheQuotaHint("uuid2", 10, 250));
+    }
+
+    private CacheQuotaHint buildCacheQuotaHint(String volumeUuid, int uid, long quota) {
+        return new CacheQuotaHint.Builder()
+                .setVolumeUuid(volumeUuid).setUid(uid).setQuota(quota).build();
+    }
+}
\ No newline at end of file
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index ed1530a..89e68a6 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -55,6 +55,8 @@
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.storage.CacheQuotaStrategy;
 
+import java.io.IOException;
+
 public class StorageStatsService extends IStorageStatsManager.Stub {
     private static final String TAG = "StorageStatsService";
 
@@ -97,7 +99,7 @@
         invalidateMounts();
 
         mHandler = new H(IoThread.get().getLooper());
-        mHandler.sendEmptyMessageDelayed(H.MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+        mHandler.sendEmptyMessageDelayed(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE, DELAY_IN_MILLIS);
 
         mStorage.registerListener(new StorageEventListener() {
             @Override
@@ -343,12 +345,14 @@
 
     private class H extends Handler {
         private static final int MSG_CHECK_STORAGE_DELTA = 100;
+        private static final int MSG_LOAD_CACHED_QUOTAS_FROM_FILE = 101;
         /**
          * By only triggering a re-calculation after the storage has changed sizes, we can avoid
          * recalculating quotas too often. Minimum change delta defines the percentage of change
          * we need to see before we recalculate.
          */
         private static final double MINIMUM_CHANGE_DELTA = 0.05;
+        private static final int UNSET = -1;
         private static final boolean DEBUG = false;
 
         private final StatFs mStats;
@@ -361,7 +365,6 @@
             mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
             mPreviousBytes = mStats.getFreeBytes();
             mMinimumThresholdBytes = mStats.getTotalBytes() * MINIMUM_CHANGE_DELTA;
-            // TODO: Load cache quotas from a file to avoid re-doing work.
         }
 
         public void handleMessage(Message msg) {
@@ -378,7 +381,26 @@
                     long bytesDelta = Math.abs(mPreviousBytes - mStats.getFreeBytes());
                     if (bytesDelta > mMinimumThresholdBytes) {
                         mPreviousBytes = mStats.getFreeBytes();
-                        recalculateQuotas();
+                        recalculateQuotas(getInitializedStrategy());
+                    }
+                    sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+                    break;
+                }
+                case MSG_LOAD_CACHED_QUOTAS_FROM_FILE: {
+                    CacheQuotaStrategy strategy = getInitializedStrategy();
+                    mPreviousBytes = UNSET;
+                    try {
+                        mPreviousBytes = strategy.setupQuotasFromFile();
+                    } catch (IOException e) {
+                        Slog.e(TAG, "An error occurred while reading the cache quota file.", e);
+                    } catch (IllegalStateException e) {
+                        Slog.e(TAG, "Cache quota XML file is malformed?", e);
+                    }
+
+                    // If errors occurred getting the quotas from disk, let's re-calc them.
+                    if (mPreviousBytes < 0) {
+                        mPreviousBytes = mStats.getFreeBytes();
+                        recalculateQuotas(strategy);
                     }
                     sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
                     break;
@@ -391,17 +413,18 @@
             }
         }
 
-        private void recalculateQuotas() {
+        private void recalculateQuotas(CacheQuotaStrategy strategy) {
             if (DEBUG) {
                 Slog.v(TAG, ">>> recalculating quotas ");
             }
 
+            strategy.recalculateQuotas();
+        }
+
+        private CacheQuotaStrategy getInitializedStrategy() {
             UsageStatsManagerInternal usageStatsManager =
                     LocalServices.getService(UsageStatsManagerInternal.class);
-            CacheQuotaStrategy strategy = new CacheQuotaStrategy(
-                    mContext, usageStatsManager, mInstaller);
-            // TODO: Save cache quotas to an XML file.
-            strategy.recalculateQuotas();
+            return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller);
         }
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ab988b2..3c7ee43 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -98,7 +98,7 @@
 
     static final String TAG = "UsageStatsService";
     public static final boolean ENABLE_TIME_CHANGE_CORRECTION
-            = SystemProperties.getBoolean("debug.time_change_correction", true);
+            = SystemProperties.getBoolean("persist.debug.time_correction", true);
 
     static final boolean DEBUG = false; // Never submit with true
     static final boolean COMPRESS_TIME = false;
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index ffc2de1..ec3d75e 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -76,7 +76,7 @@
             <View xmlns:test="http://com.test"
                   attr="hey">
               <Layout test:hello="hi" />
-              <Layout>Some text</Layout>
+              <Layout>Some text\\</Layout>
             </View>)EOF");
 
   android::ResXMLTree tree;
@@ -128,7 +128,7 @@
 
   ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
   const char16_t* text = tree.getText(&len);
-  EXPECT_EQ(StringPiece16(text, len), u"Some text");
+  EXPECT_EQ(StringPiece16(text, len), u"Some text\\");
 
   ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
   ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index f8fa80e..7210d21 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -164,6 +164,7 @@
   StringBuilder& Append(const android::StringPiece& str);
   const std::string& ToString() const;
   const std::string& Error() const;
+  bool IsEmpty() const;
 
   // When building StyledStrings, we need UTF-16 indices into the string,
   // which is what the Java layer expects when dealing with java
@@ -185,6 +186,8 @@
 
 inline const std::string& StringBuilder::Error() const { return error_; }
 
+inline bool StringBuilder::IsEmpty() const { return str_.empty(); }
+
 inline size_t StringBuilder::Utf16Len() const { return utf16_len_; }
 
 inline StringBuilder::operator bool() const { return error_.empty(); }
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index fab2f19..d9ea1bc 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -18,7 +18,6 @@
 
 #include <expat.h>
 
-#include <cassert>
 #include <memory>
 #include <stack>
 #include <string>
@@ -41,6 +40,8 @@
   std::unique_ptr<xml::Node> root;
   std::stack<xml::Node*> node_stack;
   std::string pending_comment;
+  std::unique_ptr<xml::Text> last_text_node;
+  util::StringBuilder pending_text;
 };
 
 /**
@@ -62,6 +63,19 @@
   }
 }
 
+static void FinishPendingText(Stack* stack) {
+  if (stack->last_text_node != nullptr) {
+    if (!stack->pending_text.IsEmpty()) {
+      stack->last_text_node->text = stack->pending_text.ToString();
+      stack->pending_text = {};
+      stack->node_stack.top()->AppendChild(std::move(stack->last_text_node));
+    } else {
+      // Drop an empty text node.
+      stack->last_text_node = nullptr;
+    }
+  }
+}
+
 static void AddToStack(Stack* stack, XML_Parser parser,
                        std::unique_ptr<Node> node) {
   node->line_number = XML_GetCurrentLineNumber(parser);
@@ -83,6 +97,7 @@
                                           const char* uri) {
   XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
   Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+  FinishPendingText(stack);
 
   std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
   if (prefix) {
@@ -99,6 +114,7 @@
 static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) {
   XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
   Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+  FinishPendingText(stack);
 
   CHECK(!stack->node_stack.empty());
   stack->node_stack.pop();
@@ -113,6 +129,7 @@
                                         const char** attrs) {
   XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
   Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+  FinishPendingText(stack);
 
   std::unique_ptr<Element> el = util::make_unique<Element>();
   SplitName(name, &el->namespace_uri, &el->name);
@@ -120,7 +137,9 @@
   while (*attrs) {
     Attribute attribute;
     SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);
-    attribute.value = *attrs++;
+    util::StringBuilder builder;
+    builder.Append(*attrs++);
+    attribute.value = builder.ToString();
 
     // Insert in sorted order.
     auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(),
@@ -135,41 +154,38 @@
 static void XMLCALL EndElementHandler(void* user_data, const char* name) {
   XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
   Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+  FinishPendingText(stack);
 
   CHECK(!stack->node_stack.empty());
   // stack->nodeStack.top()->comment = std::move(stack->pendingComment);
   stack->node_stack.pop();
 }
 
-static void XMLCALL CharacterDataHandler(void* user_data, const char* s,
-                                         int len) {
+static void XMLCALL CharacterDataHandler(void* user_data, const char* s, int len) {
   XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
   Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
 
-  if (!s || len <= 0) {
+  const StringPiece str(s, len);
+  if (str.empty()) {
     return;
   }
 
   // See if we can just append the text to a previous text node.
-  if (!stack->node_stack.empty()) {
-    Node* currentParent = stack->node_stack.top();
-    if (!currentParent->children.empty()) {
-      Node* last_child = currentParent->children.back().get();
-      if (Text* text = NodeCast<Text>(last_child)) {
-        text->text.append(s, len);
-        return;
-      }
-    }
+  if (stack->last_text_node != nullptr) {
+    stack->pending_text.Append(str);
+    return;
   }
 
-  std::unique_ptr<Text> text = util::make_unique<Text>();
-  text->text.assign(s, len);
-  AddToStack(stack, parser, std::move(text));
+  stack->last_text_node = util::make_unique<Text>();
+  stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser);
+  stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser);
+  stack->pending_text.Append(str);
 }
 
 static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
   XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
   Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+  FinishPendingText(stack);
 
   if (!stack->pending_comment.empty()) {
     stack->pending_comment += '\n';
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index a414afe..0fc3cec6 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -49,4 +49,23 @@
   EXPECT_EQ(ns->namespace_prefix, "android");
 }
 
+TEST(XmlDomTest, HandleEscapes) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
+      R"EOF(<shortcode pattern="\\d{5}">\\d{5}</shortcode>)EOF");
+
+  xml::Element* el = xml::FindRootElement(doc->root.get());
+  ASSERT_NE(nullptr, el);
+
+  xml::Attribute* attr = el->FindAttribute({}, "pattern");
+  ASSERT_NE(nullptr, attr);
+
+  EXPECT_EQ("\\d{5}", attr->value);
+
+  ASSERT_EQ(1u, el->children.size());
+
+  xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get());
+  ASSERT_NE(nullptr, text);
+  EXPECT_EQ("\\d{5}", text->text);
+}
+
 }  // namespace aapt
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a4ab64f..a1099f8 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -376,7 +376,9 @@
 
     /**
      * Flag indicating if this network is provided by a home Passpoint provider or a roaming
-     * Passpoint provider.
+     * Passpoint provider.  This flag will be {@code true} if this network is provided by
+     * a home Passpoint provider and {@code false} if is provided by a roaming Passpoint provider
+     * or is a non-Passpoint network.
      */
     public boolean isHomeProviderNetwork;