Merge "Flip task snapshot flag"
diff --git a/Android.mk b/Android.mk
index 2539c3d..63ad83f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -107,6 +107,7 @@
 	core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreSession.aidl \
+	core/java/android/app/backup/ISelectBackupTransportCallback.aidl \
 	core/java/android/app/usage/IStorageStatsManager.aidl \
 	core/java/android/app/usage/IUsageStatsManager.aidl \
 	core/java/android/bluetooth/IBluetooth.aidl \
@@ -424,10 +425,12 @@
 	media/java/android/media/projection/IMediaProjectionManager.aidl \
 	media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl \
 	media/java/android/media/session/IActiveSessionsListener.aidl \
-	media/java/android/media/session/ISessionController.aidl \
-	media/java/android/media/session/ISessionControllerCallback.aidl \
+	media/java/android/media/session/IOnMediaKeyListener.aidl \
+	media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl \
 	media/java/android/media/session/ISession.aidl \
 	media/java/android/media/session/ISessionCallback.aidl \
+	media/java/android/media/session/ISessionController.aidl \
+	media/java/android/media/session/ISessionControllerCallback.aidl \
 	media/java/android/media/session/ISessionManager.aidl \
 	media/java/android/media/tv/ITvInputClient.aidl \
 	media/java/android/media/tv/ITvInputHardware.aidl \
@@ -461,6 +464,8 @@
         telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl \
         telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl \
 	telephony/java/com/android/ims/internal/IImsService.aidl \
+	telephony/java/com/android/ims/internal/IImsServiceController.aidl \
+	telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl \
 	telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
 	telephony/java/com/android/ims/internal/IImsUt.aidl \
 	telephony/java/com/android/ims/internal/IImsUtListener.aidl \
@@ -536,6 +541,7 @@
     framework-protos                                    \
     android.hardware.thermal@1.0-java-constants         \
     android.hardware.health@1.0-java-constants          \
+    android.hardware.usb@1.0-java-constants             \
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := stream
 LOCAL_PROTOC_FLAGS := \
diff --git a/api/current.txt b/api/current.txt
index cd66ed6..8adf4b2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -408,6 +408,7 @@
     field public static final int colorForeground = 16842800; // 0x1010030
     field public static final int colorForegroundInverse = 16843270; // 0x1010206
     field public static final int colorLongPressedHighlight = 16843662; // 0x101038e
+    field public static final int colorMode = 16844108; // 0x101054c
     field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391
     field public static final int colorPressedHighlight = 16843661; // 0x101038d
     field public static final int colorPrimary = 16843827; // 0x1010433
@@ -738,6 +739,7 @@
     field public static final int isScrollContainer = 16843342; // 0x101024e
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
+    field public static final int isolatedSplits = 16844109; // 0x101054d
     field public static final int itemBackground = 16843056; // 0x1010130
     field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
     field public static final int itemPadding = 16843565; // 0x101032d
@@ -1169,6 +1171,7 @@
     field public static final int spinnerStyle = 16842881; // 0x1010081
     field public static final int spinnersShown = 16843595; // 0x101034b
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
+    field public static final int splitName = 16844107; // 0x101054b
     field public static final int splitTrack = 16843852; // 0x101044c
     field public static final int spotShadowAlpha = 16843967; // 0x10104bf
     field public static final int src = 16843033; // 0x1010119
@@ -1817,6 +1820,7 @@
     field public static final int tabs = 16908307; // 0x1020013
     field public static final int text1 = 16908308; // 0x1020014
     field public static final int text2 = 16908309; // 0x1020015
+    field public static final int textAssist = 16908353; // 0x1020041
     field public static final int title = 16908310; // 0x1020016
     field public static final int toggle = 16908311; // 0x1020017
     field public static final int undo = 16908338; // 0x1020032
@@ -3064,8 +3068,10 @@
 
   public static abstract interface Animator.AnimatorListener {
     method public abstract void onAnimationCancel(android.animation.Animator);
+    method public default void onAnimationEnd(android.animation.Animator, boolean);
     method public abstract void onAnimationEnd(android.animation.Animator);
     method public abstract void onAnimationRepeat(android.animation.Animator);
+    method public default void onAnimationStart(android.animation.Animator, boolean);
     method public abstract void onAnimationStart(android.animation.Animator);
   }
 
@@ -3101,6 +3107,8 @@
     method public void playSequentially(java.util.List<android.animation.Animator>);
     method public void playTogether(android.animation.Animator...);
     method public void playTogether(java.util.Collection<android.animation.Animator>);
+    method public void reverse();
+    method public void setCurrentPlayTime(long);
     method public android.animation.AnimatorSet setDuration(long);
     method public void setInterpolator(android.animation.TimeInterpolator);
     method public void setStartDelay(long);
@@ -3536,7 +3544,6 @@
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
     method public int getTaskId();
-    method public android.text.TextAssistant getTextAssistant();
     method public final java.lang.CharSequence getTitle();
     method public final int getTitleColor();
     method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3686,7 +3693,6 @@
     method public final void setResult(int, android.content.Intent);
     method public final deprecated void setSecondaryProgress(int);
     method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
-    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
     method public deprecated void setTitleColor(int);
@@ -5016,6 +5022,7 @@
     method public android.graphics.drawable.Icon getLargeIcon();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
+    method public long getTimeout();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
     field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5043,8 +5050,10 @@
     field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
     field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
     field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+    field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
     field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
     field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+    field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
     field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
     field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
     field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
@@ -5207,6 +5216,7 @@
     method public android.app.Notification.Builder setChannel(java.lang.String);
     method public android.app.Notification.Builder setChronometerCountDown(boolean);
     method public android.app.Notification.Builder setColor(int);
+    method public android.app.Notification.Builder setColorized(boolean);
     method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
     method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -5244,6 +5254,7 @@
     method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setTimeout(long);
     method public android.app.Notification.Builder setUsesChronometer(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setVisibility(int);
@@ -5310,9 +5321,11 @@
 
   public static class Notification.MessagingStyle extends android.app.Notification.Style {
     ctor public Notification.MessagingStyle(java.lang.CharSequence);
+    method public android.app.Notification.MessagingStyle addHistoricMessage(android.app.Notification.MessagingStyle.Message);
     method public android.app.Notification.MessagingStyle addMessage(java.lang.CharSequence, long, java.lang.CharSequence);
     method public android.app.Notification.MessagingStyle addMessage(android.app.Notification.MessagingStyle.Message);
     method public java.lang.CharSequence getConversationTitle();
+    method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
     method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
     method public java.lang.CharSequence getUserDisplayName();
     method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
@@ -6112,6 +6125,7 @@
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+    method public android.content.Intent createAdminSupportIntent(java.lang.String);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
     method public void enableSystemApp(android.content.ComponentName, java.lang.String);
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6120,16 +6134,18 @@
     method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
     method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
-    method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+    method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
     method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
-    method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+    method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
     method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
     method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
+    method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+    method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
     method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -6174,7 +6190,7 @@
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
     method public boolean isBackupServiceEnabled(android.content.ComponentName);
-    method public boolean isCallerApplicationRestrictionsManagingPackage();
+    method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
     method public boolean isDeviceOwnerApp(java.lang.String);
     method public boolean isLockTaskPermitted(java.lang.String);
     method public boolean isManagedProfile(android.content.ComponentName);
@@ -6202,14 +6218,15 @@
     method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
-    method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public void setAutoTimeRequired(android.content.ComponentName, boolean);
     method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
     method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
     method public void setCameraDisabled(android.content.ComponentName, boolean);
-    method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+    method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+    method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6257,6 +6274,7 @@
     method public void uninstallCaCert(android.content.ComponentName, byte[]);
     method public void wipeData(int);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
+    field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6266,6 +6284,12 @@
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+    field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+    field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+    field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+    field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+    field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+    field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
     field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
     field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
     field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6273,6 +6297,7 @@
     field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
     field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
     field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+    field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
     field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
     field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
     field public static final java.lang.String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
@@ -6331,6 +6356,8 @@
     field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
     field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
     field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+    field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+    field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -8355,6 +8382,7 @@
     method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public abstract deprecated void clearWallpaper() throws java.io.IOException;
     method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.Context createDeviceProtectedStorageContext();
     method public abstract android.content.Context createDisplayContext(android.view.Display);
     method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8553,6 +8581,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public deprecated void clearWallpaper() throws java.io.IOException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8861,6 +8890,7 @@
     field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
     field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
     field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+    field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
     field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
@@ -9575,7 +9605,10 @@
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
     method public final int getThemeResource();
-    field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000
+    field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
+    field public static final int COLOR_MODE_HDR = 2; // 0x2
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+    field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
     field public static final int CONFIG_DENSITY = 4096; // 0x1000
     field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
     field public static final int CONFIG_KEYBOARD = 16; // 0x10
@@ -9636,6 +9669,7 @@
     field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb
     field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc
     field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1
+    field public int colorMode;
     field public int configChanges;
     field public int documentLaunchMode;
     field public int flags;
@@ -9732,6 +9766,7 @@
     field public int requiresSmallestWidthDp;
     field public java.lang.String[] sharedLibraryFiles;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public int targetSdkVersion;
@@ -9760,6 +9795,7 @@
     field public boolean enabled;
     field public boolean exported;
     field public java.lang.String processName;
+    field public java.lang.String splitName;
   }
 
   public class ConfigurationInfo implements android.os.Parcelable {
@@ -9813,6 +9849,7 @@
     field public boolean handleProfiling;
     field public java.lang.String publicSourceDir;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public java.lang.String targetPackage;
@@ -10175,6 +10212,7 @@
     field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+    field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded";
     field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet";
     field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch";
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
@@ -10610,16 +10648,16 @@
     method public void setToDefaults();
     method public int updateFrom(android.content.res.Configuration);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc
-    field public static final int COLORIMETRY_HDR_NO = 4; // 0x4
-    field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2
-    field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_HDR_YES = 8; // 0x8
-    field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2
+    field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc
+    field public static final int COLOR_MODE_HDR_NO = 4; // 0x4
+    field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2
+    field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_HDR_YES = 8; // 0x8
+    field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
     field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
     field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
@@ -10685,7 +10723,7 @@
     field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0
     field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7
     field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6
-    field public int colorimetry;
+    field public int colorMode;
     field public int densityDpi;
     field public float fontScale;
     field public int hardKeyboardHidden;
@@ -13226,6 +13264,7 @@
   }
 
   public class Typeface {
+    method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13246,6 +13285,14 @@
     field public static final android.graphics.Typeface SERIF;
   }
 
+  public static abstract interface Typeface.FontRequestCallback {
+    method public abstract void onTypefaceRequestFailed(int);
+    method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+    field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+    field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+  }
+
   public class Xfermode {
     ctor public Xfermode();
   }
@@ -13652,6 +13699,23 @@
     method public void addLevel(int, int, android.graphics.drawable.Drawable);
   }
 
+  public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+    ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+    method public void draw(android.graphics.Canvas);
+    method public android.graphics.drawable.Drawable getBackground();
+    method public android.graphics.drawable.Drawable getForeground();
+    method public android.graphics.Path getIconMask();
+    method public int getOpacity();
+    method public void invalidateDrawable(android.graphics.drawable.Drawable);
+    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+    method public void setAlpha(int);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setOpacity(int);
+    method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+    field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
+    field public static final float MASK_SIZE = 100.0f;
+  }
+
   public class NinePatchDrawable extends android.graphics.drawable.Drawable {
     ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
     ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -13800,6 +13864,19 @@
 
 }
 
+package android.graphics.fonts {
+
+  public final class FontRequest implements android.os.Parcelable {
+    ctor public FontRequest(java.lang.String, java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getProviderAuthority();
+    method public java.lang.String getQuery();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+  }
+
+}
+
 package android.graphics.pdf {
 
   public class PdfDocument {
@@ -14138,9 +14215,10 @@
     method public int getWidth();
     method public boolean isDestroyed();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BLOB = 33; // 0x21
     field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
     field public static final int RGBA_8888 = 1; // 0x1
-    field public static final int RGBA_FP16 = 5; // 0x5
+    field public static final int RGBA_FP16 = 22; // 0x16
     field public static final int RGBX_8888 = 2; // 0x2
     field public static final int RGB_565 = 4; // 0x4
     field public static final int RGB_888 = 3; // 0x3
@@ -14161,6 +14239,7 @@
   public final class Sensor {
     method public int getFifoMaxEventCount();
     method public int getFifoReservedEventCount();
+    method public int getHighestDirectReportRateLevel();
     method public int getId();
     method public int getMaxDelay();
     method public float getMaximumRange();
@@ -14174,6 +14253,7 @@
     method public java.lang.String getVendor();
     method public int getVersion();
     method public boolean isAdditionalInfoSupported();
+    method public boolean isDirectChannelTypeSupported(int);
     method public boolean isDynamicSensor();
     method public boolean isWakeUpSensor();
     field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14251,6 +14331,17 @@
     field public final int type;
   }
 
+  public final class SensorDirectChannel implements java.lang.AutoCloseable {
+    method public void close();
+    method public boolean isValid();
+    field public static final int RATE_FAST = 2; // 0x2
+    field public static final int RATE_NORMAL = 1; // 0x1
+    field public static final int RATE_STOP = 0; // 0x0
+    field public static final int RATE_VERY_FAST = 3; // 0x3
+    field public static final int TYPE_ASHMEM = 1; // 0x1
+    field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+  }
+
   public class SensorEvent {
     field public int accuracy;
     field public android.hardware.Sensor sensor;
@@ -14282,6 +14373,9 @@
 
   public abstract class SensorManager {
     method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+    method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+    method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+    method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
     method public boolean flush(android.hardware.SensorEventListener);
     method public static float getAltitude(float, float);
     method public static void getAngleChange(float[], float[], float[]);
@@ -14411,7 +14505,7 @@
     method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void close();
-    method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
+    method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
     method public abstract android.hardware.camera2.CameraDevice getDevice();
     method public abstract android.view.Surface getInputSurface();
     method public abstract boolean isReprocessable();
@@ -14826,6 +14920,7 @@
     field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+    field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -14905,6 +15000,7 @@
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+    field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15052,10 +15148,12 @@
     ctor public OutputConfiguration(android.view.Surface);
     ctor public OutputConfiguration(int, android.view.Surface);
     ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
+    method public void addSurface(android.view.Surface);
     method public int describeContents();
+    method public void enableSurfaceSharing();
     method public android.view.Surface getSurface();
     method public int getSurfaceGroupId();
-    method public void setDeferredSurface(android.view.Surface);
+    method public java.util.List<android.view.Surface> getSurfaces();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
     field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
@@ -17605,6 +17703,15 @@
     method public boolean isTransitionalDifferent();
   }
 
+  public final class ListFormatter {
+    method public java.lang.String format(java.lang.Object...);
+    method public java.lang.String format(java.util.Collection<?>);
+    method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+    method public static android.icu.text.ListFormatter getInstance();
+    method public java.lang.String getPatternForNumItems(int);
+  }
+
   public abstract class LocaleDisplayNames {
     method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
     method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17614,6 +17721,8 @@
     method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
     method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
     method public abstract android.icu.util.ULocale getLocale();
+    method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+    method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
     method public abstract java.lang.String keyDisplayName(java.lang.String);
     method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
     method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17633,9 +17742,19 @@
     enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
   }
 
+  public static class LocaleDisplayNames.UiListItem {
+    ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+    field public final android.icu.util.ULocale minimized;
+    field public final android.icu.util.ULocale modified;
+    field public final java.lang.String nameInDisplayLocale;
+    field public final java.lang.String nameInSelf;
+  }
+
   public class MeasureFormat extends android.icu.text.UFormat {
     method public final boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+    method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
     method public final java.lang.String formatMeasures(android.icu.util.Measure...);
     method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
     method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -18104,6 +18223,14 @@
     method public void setUpperCaseFirst(boolean);
   }
 
+  public final class ScientificNumberFormatter {
+    method public java.lang.String format(java.lang.Object);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+  }
+
   public abstract class SearchIterator {
     ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
     method public final int first();
@@ -18879,6 +19006,34 @@
     method public long getToDate();
   }
 
+  public final class EthiopicCalendar extends android.icu.util.CECalendar {
+    ctor public EthiopicCalendar();
+    ctor public EthiopicCalendar(android.icu.util.TimeZone);
+    ctor public EthiopicCalendar(java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.ULocale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+    ctor public EthiopicCalendar(int, int, int);
+    ctor public EthiopicCalendar(java.util.Date);
+    ctor public EthiopicCalendar(int, int, int, int, int, int);
+    method protected deprecated int handleGetExtendedYear();
+    method public boolean isAmeteAlemEra();
+    method public void setAmeteAlemEra(boolean);
+    field public static final int GENBOT = 8; // 0x8
+    field public static final int HAMLE = 10; // 0xa
+    field public static final int HEDAR = 2; // 0x2
+    field public static final int MEGABIT = 6; // 0x6
+    field public static final int MESKEREM = 0; // 0x0
+    field public static final int MIAZIA = 7; // 0x7
+    field public static final int NEHASSE = 11; // 0xb
+    field public static final int PAGUMEN = 12; // 0xc
+    field public static final int SENE = 9; // 0x9
+    field public static final int TAHSAS = 3; // 0x3
+    field public static final int TEKEMT = 1; // 0x1
+    field public static final int TER = 4; // 0x4
+    field public static final int YEKATIT = 5; // 0x5
+  }
+
   public abstract interface Freezable<T> implements java.lang.Cloneable {
     method public abstract T cloneAsThawed();
     method public abstract T freeze();
@@ -19407,6 +19562,35 @@
     enum_constant public static final android.icu.util.ULocale.Category FORMAT;
   }
 
+  public final class UniversalTimeScale {
+    method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+    method public static long from(long, int);
+    method public static long getTimeScaleValue(int, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+    method public static long toLong(long, int);
+    field public static final int DB2_TIME = 8; // 0x8
+    field public static final int DOTNET_DATE_TIME = 4; // 0x4
+    field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+    field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+    field public static final int EXCEL_TIME = 7; // 0x7
+    field public static final int FROM_MAX_VALUE = 3; // 0x3
+    field public static final int FROM_MIN_VALUE = 2; // 0x2
+    field public static final int ICU4C_TIME = 2; // 0x2
+    field public static final int JAVA_TIME = 0; // 0x0
+    field public static final int MAC_OLD_TIME = 5; // 0x5
+    field public static final int MAC_TIME = 6; // 0x6
+    field public static final int MAX_SCALE = 10; // 0xa
+    field public static final int TO_MAX_VALUE = 5; // 0x5
+    field public static final int TO_MIN_VALUE = 4; // 0x4
+    field public static final int UNITS_VALUE = 0; // 0x0
+    field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+    field public static final int UNIX_TIME = 1; // 0x1
+    field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+  }
+
   public abstract interface ValueIterator {
     method public abstract boolean next(android.icu.util.ValueIterator.Element);
     method public abstract void reset();
@@ -20205,7 +20389,7 @@
     field public static final android.os.Parcelable.Creator<android.media.AudioAttributes> CREATOR;
     field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
     field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
-    field public static final int FLAG_LOW_LATENCY = 256; // 0x100
+    field public static final deprecated int FLAG_LOW_LATENCY = 256; // 0x100
     field public static final int USAGE_ALARM = 4; // 0x4
     field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
     field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -20646,6 +20830,7 @@
     method protected deprecated int getNativeFrameCount();
     method public static int getNativeOutputSampleRate(int);
     method public int getNotificationMarkerPosition();
+    method public int getPerformanceMode();
     method public int getPlayState();
     method public int getPlaybackHeadPosition();
     method public android.media.PlaybackParams getPlaybackParams();
@@ -20692,6 +20877,9 @@
     field public static final int ERROR_INVALID_OPERATION = -3; // 0xfffffffd
     field public static final int MODE_STATIC = 0; // 0x0
     field public static final int MODE_STREAM = 1; // 0x1
+    field public static final int PERFORMANCE_MODE_LOW_LATENCY = 1; // 0x1
+    field public static final int PERFORMANCE_MODE_NONE = 0; // 0x0
+    field public static final int PERFORMANCE_MODE_POWER_SAVING = 2; // 0x2
     field public static final int PLAYSTATE_PAUSED = 2; // 0x2
     field public static final int PLAYSTATE_PLAYING = 3; // 0x3
     field public static final int PLAYSTATE_STOPPED = 1; // 0x1
@@ -20709,6 +20897,7 @@
     method public android.media.AudioTrack.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setAudioFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
+    method public android.media.AudioTrack.Builder setPerformanceMode(int);
     method public android.media.AudioTrack.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
   }
@@ -20723,6 +20912,40 @@
     method public default void onRoutingChanged(android.media.AudioRouting);
   }
 
+  public final class BufferingParams implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getInitialBufferingMode();
+    method public int getInitialBufferingWatermarkKB();
+    method public int getInitialBufferingWatermarkMs();
+    method public int getRebufferingMode();
+    method public int getRebufferingWatermarkHighKB();
+    method public int getRebufferingWatermarkHighMs();
+    method public int getRebufferingWatermarkLowKB();
+    method public int getRebufferingWatermarkLowMs();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+    field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+    field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+    field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+    field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+  }
+
+  public static class BufferingParams.Builder {
+    ctor public BufferingParams.Builder();
+    ctor public BufferingParams.Builder(android.media.BufferingParams);
+    method public android.media.BufferingParams build();
+    method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+    method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+    method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingMode(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+  }
+
   public class CamcorderProfile {
     method public static android.media.CamcorderProfile get(int);
     method public static android.media.CamcorderProfile get(int, int);
@@ -21895,7 +22118,9 @@
     method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
     method public void deselectTrack(int) throws java.lang.IllegalStateException;
     method public int getAudioSessionId();
+    method public android.media.BufferingParams getBufferingParams();
     method public int getCurrentPosition();
+    method public android.media.BufferingParams getDefaultBufferingParams();
     method public int getDuration();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -21918,6 +22143,7 @@
     method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public deprecated void setAudioStreamType(int);
     method public void setAuxEffectSendLevel(float);
+    method public void setBufferingParams(android.media.BufferingParams);
     method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
     method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
     method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -23580,6 +23806,7 @@
     field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
     field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
     field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+    field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
     field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
     field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
     field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -23620,8 +23847,14 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
     field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
+    field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position";
+    field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
     field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
     field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
@@ -23750,6 +23983,7 @@
     field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
     field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
     field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+    field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
     field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
     field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
     field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -29809,6 +30043,7 @@
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
+    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -29853,6 +30088,7 @@
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
@@ -30609,6 +30845,7 @@
     method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
     method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
     method public int getOrder();
+    method public android.preference.PreferenceGroup getParent();
     method protected boolean getPersistedBoolean(boolean);
     method protected float getPersistedFloat(float);
     method protected int getPersistedInt(int);
@@ -32937,7 +33174,6 @@
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
-    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
     method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
     method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
@@ -32951,6 +33187,16 @@
     method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
   }
 
+  public class FontsContract {
+  }
+
+  public static final class FontsContract.Columns implements android.provider.BaseColumns {
+    ctor public FontsContract.Columns();
+    field public static final java.lang.String STYLE = "font_style";
+    field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+    field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+  }
+
   public final deprecated class LiveFolders implements android.provider.BaseColumns {
     field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
     field public static final java.lang.String DESCRIPTION = "description";
@@ -33316,6 +33562,7 @@
     field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
     field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
     field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+    field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -33368,8 +33615,10 @@
     field public static final java.lang.String AUTHORITY = "settings";
     field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
     field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+    field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
     field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
     field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+    field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -35424,17 +35673,25 @@
     ctor public AutoFillService();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
+    method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
     method public void onDisconnected();
     method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
     method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
     field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
     field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+    field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+    field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+    field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+    field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
     field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
   }
 
   public final class FillCallback {
+    method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
     method public void onFailure(java.lang.CharSequence);
+    method public void onFillResponseAuthentication(int);
     method public void onSuccess(android.view.autofill.FillResponse);
   }
 
@@ -35719,6 +35976,7 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
     method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+    method public final void unsnoozeNotification(java.lang.String);
     method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
@@ -35734,6 +35992,7 @@
     method public final int getCurrentInterruptionFilter();
     method public final int getCurrentListenerHints();
     method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+    method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onInterruptionFilterChanged(int);
     method public void onListenerConnected();
@@ -35752,8 +36011,6 @@
     method public final void setNotificationsShown(java.lang.String[]);
     method public final void snoozeNotification(java.lang.String, java.lang.String);
     method public final void snoozeNotification(java.lang.String, long);
-    method public final void snoozeNotification(java.lang.String);
-    method public final void unsnoozeNotification(java.lang.String);
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -35778,9 +36035,9 @@
     field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
     field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
     field public static final int REASON_SNOOZED = 18; // 0x12
+    field public static final int REASON_TIMEOUT = 19; // 0x13
     field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
     field public static final int REASON_USER_STOPPED = 6; // 0x6
-    field public static final int REASON_USER_SWITCH = 19; // 0x13
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
     field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
     field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -35788,6 +36045,7 @@
 
   public static class NotificationListenerService.Ranking {
     ctor public NotificationListenerService.Ranking();
+    method public boolean canShowBadge();
     method public java.util.List<java.lang.String> getAdditionalPeople();
     method public android.app.NotificationChannel getChannel();
     method public int getImportance();
@@ -35829,7 +36087,6 @@
     method public int getId();
     method public java.lang.String getKey();
     method public android.app.Notification getNotification();
-    method public android.app.NotificationChannel getNotificationChannel();
     method public java.lang.String getOverrideGroupKey();
     method public java.lang.String getPackageName();
     method public long getPostTime();
@@ -36245,6 +36502,7 @@
     method public abstract int getMaxBufferSize();
     method public abstract boolean hasFinished();
     method public abstract boolean hasStarted();
+    method public default void rangeStart(int, int, int);
     method public abstract int start(int, int, int);
   }
 
@@ -36397,6 +36655,7 @@
     method public void onError(java.lang.String, int);
     method public abstract void onStart(java.lang.String);
     method public void onStop(java.lang.String, boolean);
+    method public void onUtteranceRangeStart(java.lang.String, int, int);
   }
 
   public class Voice implements android.os.Parcelable {
@@ -37862,6 +38121,7 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+    field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
@@ -38488,6 +38748,7 @@
     method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+    method public boolean isConcurrentVoiceAndDataAllowed();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
@@ -38528,6 +38789,7 @@
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+    field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
     field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
     field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
     field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -38536,6 +38798,7 @@
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
     field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+    field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -39081,6 +39344,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -39925,22 +40189,6 @@
     method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
   }
 
-  public abstract interface TextAssistant {
-    method public abstract void addLinks(android.text.Spannable, int);
-    method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-  public class TextClassification {
-    ctor public TextClassification();
-    method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
-  }
-
-  public final class TextClassificationManager implements android.text.TextAssistant {
-    method public void addLinks(android.text.Spannable, int);
-    method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
-    method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
   public abstract interface TextDirectionHeuristic {
     method public abstract boolean isRtl(char[], int, int);
     method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39956,13 +40204,6 @@
     field public static final android.text.TextDirectionHeuristic RTL;
   }
 
-  public final class TextLanguage {
-    ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
-    method public int getEndIndex();
-    method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
-    method public int getStartIndex();
-  }
-
   public class TextPaint extends android.graphics.Paint {
     ctor public TextPaint();
     ctor public TextPaint(int);
@@ -39975,13 +40216,6 @@
     field public int linkColor;
   }
 
-  public class TextSelection {
-    ctor public TextSelection();
-    method public int getSelectionEndIndex();
-    method public int getSelectionStartIndex();
-    method public android.text.TextClassification getTextClassification();
-  }
-
   public class TextUtils {
     method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
     method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -43726,6 +43960,7 @@
     method public float getElevation();
     method public boolean getFilterTouchesWhenObscured();
     method public boolean getFitsSystemWindows();
+    method public int getFocusable();
     method public java.util.ArrayList<android.view.View> getFocusables(int);
     method public void getFocusedRect(android.graphics.Rect);
     method public android.graphics.drawable.Drawable getForeground();
@@ -44030,6 +44265,7 @@
     method public void setFilterTouchesWhenObscured(boolean);
     method public void setFitsSystemWindows(boolean);
     method public void setFocusable(boolean);
+    method public void setFocusable(int);
     method public void setFocusableInTouchMode(boolean);
     method public void setFocusedByDefault(boolean);
     method public void setForeground(android.graphics.drawable.Drawable);
@@ -44168,8 +44404,10 @@
     field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
     field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
     field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+    field public static final int FOCUSABLE = 1; // 0x1
     field public static final int FOCUSABLES_ALL = 0; // 0x0
     field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+    field public static final int FOCUSABLE_AUTO = 16; // 0x10
     field protected static final int[] FOCUSED_SELECTED_STATE_SET;
     field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     field protected static final int[] FOCUSED_STATE_SET;
@@ -44199,6 +44437,7 @@
     field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
     field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
     field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+    field public static final int NOT_FOCUSABLE = 0; // 0x0
     field public static final int NO_ID = -1; // 0xffffffff
     field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
     field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -44903,6 +45142,7 @@
     method public boolean getAllowReturnTransitionOverlap();
     method public final android.view.WindowManager.LayoutParams getAttributes();
     method public final android.view.Window.Callback getCallback();
+    method public int getColorMode();
     method public final android.view.Window getContainer();
     method public android.transition.Scene getContentScene();
     method public final android.content.Context getContext();
@@ -44959,6 +45199,7 @@
     method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable);
     method public abstract void setChildInt(int, int);
     method public void setClipToOutline(boolean);
+    method public void setColorMode(int);
     method public void setContainer(android.view.Window);
     method public abstract void setContentView(int);
     method public abstract void setContentView(android.view.View);
@@ -45157,8 +45398,10 @@
     method public final int copyFrom(android.view.WindowManager.LayoutParams);
     method public java.lang.String debug(java.lang.String);
     method public int describeContents();
+    method public int getColorMode();
     method public final java.lang.CharSequence getTitle();
     method public static boolean mayUseInputMethod(int);
+    method public void setColorMode(int);
     method public final void setTitle(java.lang.CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ALPHA_CHANGED = 128; // 0x80
@@ -46027,6 +46270,13 @@
     field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
   }
 
+  public final class AutoFillManager {
+    method public void updateAutoFillInput(android.view.View, int);
+    method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+    field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+    field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+  }
+
   public final class AutoFillType implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutoFillType forList();
@@ -46061,6 +46311,8 @@
   public static final class Dataset.Builder {
     ctor public Dataset.Builder(java.lang.CharSequence);
     method public android.view.autofill.Dataset build();
+    method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+    method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
     method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
     method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
   }
@@ -46076,6 +46328,8 @@
     method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
     method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
     method public android.view.autofill.FillResponse build();
+    method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+    method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
     method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
   }
 
@@ -46086,7 +46340,7 @@
 
   public static abstract class VirtualViewDelegate.Callback {
     ctor public VirtualViewDelegate.Callback();
-    method public void onFocusChanged(int, boolean);
+    method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
     method public void onNodeRemoved(int...);
     method public void onValueChanged(int);
   }
@@ -46493,6 +46747,83 @@
 
 }
 
+package android.view.textclassifier {
+
+  public abstract interface LinksInfo {
+    method public abstract boolean apply(java.lang.CharSequence);
+  }
+
+  public final class TextClassificationManager {
+    method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+    method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+  }
+
+  public final class TextClassificationResult {
+    method public float getConfidenceScore(java.lang.String);
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public android.graphics.drawable.Drawable getIcon();
+    method public android.content.Intent getIntent();
+    method public java.lang.CharSequence getLabel();
+    method public android.view.View.OnClickListener getOnClickListener();
+    method public java.lang.String getText();
+  }
+
+  public static final class TextClassificationResult.Builder {
+    ctor public TextClassificationResult.Builder();
+    method public android.view.textclassifier.TextClassificationResult build();
+    method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+    method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+    method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+    method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+    method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+    method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+  }
+
+  public abstract interface TextClassifier {
+    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+    field public static final android.view.textclassifier.TextClassifier NO_OP;
+    field public static final java.lang.String TYPE_ADDRESS = "address";
+    field public static final java.lang.String TYPE_EMAIL = "email";
+    field public static final java.lang.String TYPE_OTHER = "other";
+    field public static final java.lang.String TYPE_PHONE = "phone";
+  }
+
+  public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+  }
+
+  public final class TextLanguage {
+    method public float getConfidenceScore(java.util.Locale);
+    method public int getEndIndex();
+    method public java.util.Locale getLanguage(int);
+    method public int getLanguageCount();
+    method public int getStartIndex();
+  }
+
+  public static final class TextLanguage.Builder {
+    ctor public TextLanguage.Builder(int, int);
+    method public android.view.textclassifier.TextLanguage build();
+    method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+  }
+
+  public final class TextSelection {
+    method public float getConfidenceScore(java.lang.String);
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public int getSelectionEndIndex();
+    method public int getSelectionStartIndex();
+  }
+
+  public static final class TextSelection.Builder {
+    ctor public TextSelection.Builder(int, int);
+    method public android.view.textclassifier.TextSelection build();
+    method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+  }
+
+}
+
 package android.view.textservice {
 
   public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -49582,7 +49913,7 @@
     method public float getShadowRadius();
     method public final boolean getShowSoftInputOnFocus();
     method public java.lang.CharSequence getText();
-    method public android.text.TextAssistant getTextAssistant();
+    method public android.view.textclassifier.TextClassifier getTextClassifier();
     method public final android.content.res.ColorStateList getTextColors();
     method public java.util.Locale getTextLocale();
     method public android.os.LocaleList getTextLocales();
@@ -49698,7 +50029,7 @@
     method public final void setText(int, android.widget.TextView.BufferType);
     method public void setTextAppearance(int);
     method public deprecated void setTextAppearance(android.content.Context, int);
-    method public void setTextAssistant(android.text.TextAssistant);
+    method public void setTextClassifier(android.view.textclassifier.TextClassifier);
     method public void setTextColor(int);
     method public void setTextColor(android.content.res.ColorStateList);
     method public void setTextIsSelectable(boolean);
@@ -61841,6 +62172,9 @@
     method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
     method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
     method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+    method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
     method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
     method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
     method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -61851,7 +62185,11 @@
     method public static final <T> java.util.List<T> emptyList();
     method public static <T> java.util.ListIterator<T> emptyListIterator();
     method public static final <K, V> java.util.Map<K, V> emptyMap();
+    method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+    method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
     method public static final <T> java.util.Set<T> emptySet();
+    method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+    method public static <E> java.util.SortedSet<E> emptySortedSet();
     method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
     method public static <T> void fill(java.util.List<? super T>, T);
     method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -61880,12 +62218,16 @@
     method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
     method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
     method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+    method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+    method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
     method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
     method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
     method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
     method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
     method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+    method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+    method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
     method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
     method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/system-current.txt b/api/system-current.txt
index a3deab0..b319c75 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -37,6 +37,7 @@
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
     field public static final java.lang.String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
     field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
+    field public static final java.lang.String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
     field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
     field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
     field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
@@ -217,6 +218,7 @@
     field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
     field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
     field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
+    field public static final java.lang.String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER";
     field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
     field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
     field public static final deprecated java.lang.String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
@@ -224,6 +226,7 @@
     field public static final java.lang.String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
     field public static final java.lang.String SET_TIME = "android.permission.SET_TIME";
     field public static final java.lang.String SET_TIME_ZONE = "android.permission.SET_TIME_ZONE";
+    field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final java.lang.String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
     field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final java.lang.String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
@@ -517,6 +520,7 @@
     field public static final int colorForeground = 16842800; // 0x1010030
     field public static final int colorForegroundInverse = 16843270; // 0x1010206
     field public static final int colorLongPressedHighlight = 16843662; // 0x101038e
+    field public static final int colorMode = 16844108; // 0x101054c
     field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391
     field public static final int colorPressedHighlight = 16843661; // 0x101038d
     field public static final int colorPrimary = 16843827; // 0x1010433
@@ -847,6 +851,7 @@
     field public static final int isScrollContainer = 16843342; // 0x101024e
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
+    field public static final int isolatedSplits = 16844109; // 0x101054d
     field public static final int itemBackground = 16843056; // 0x1010130
     field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
     field public static final int itemPadding = 16843565; // 0x101032d
@@ -1282,6 +1287,7 @@
     field public static final int spinnerStyle = 16842881; // 0x1010081
     field public static final int spinnersShown = 16843595; // 0x101034b
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
+    field public static final int splitName = 16844107; // 0x101054b
     field public static final int splitTrack = 16843852; // 0x101044c
     field public static final int spotShadowAlpha = 16843967; // 0x10104bf
     field public static final int src = 16843033; // 0x1010119
@@ -1930,6 +1936,7 @@
     field public static final int tabs = 16908307; // 0x1020013
     field public static final int text1 = 16908308; // 0x1020014
     field public static final int text2 = 16908309; // 0x1020015
+    field public static final int textAssist = 16908353; // 0x1020041
     field public static final int title = 16908310; // 0x1020016
     field public static final int toggle = 16908311; // 0x1020017
     field public static final int undo = 16908338; // 0x1020032
@@ -3181,8 +3188,10 @@
 
   public static abstract interface Animator.AnimatorListener {
     method public abstract void onAnimationCancel(android.animation.Animator);
+    method public default void onAnimationEnd(android.animation.Animator, boolean);
     method public abstract void onAnimationEnd(android.animation.Animator);
     method public abstract void onAnimationRepeat(android.animation.Animator);
+    method public default void onAnimationStart(android.animation.Animator, boolean);
     method public abstract void onAnimationStart(android.animation.Animator);
   }
 
@@ -3218,6 +3227,8 @@
     method public void playSequentially(java.util.List<android.animation.Animator>);
     method public void playTogether(android.animation.Animator...);
     method public void playTogether(java.util.Collection<android.animation.Animator>);
+    method public void reverse();
+    method public void setCurrentPlayTime(long);
     method public android.animation.AnimatorSet setDuration(long);
     method public void setInterpolator(android.animation.TimeInterpolator);
     method public void setStartDelay(long);
@@ -3655,7 +3666,6 @@
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
     method public int getTaskId();
-    method public android.text.TextAssistant getTextAssistant();
     method public final java.lang.CharSequence getTitle();
     method public final int getTitleColor();
     method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3807,7 +3817,6 @@
     method public final void setResult(int, android.content.Intent);
     method public final deprecated void setSecondaryProgress(int);
     method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
-    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
     method public deprecated void setTitleColor(int);
@@ -5174,6 +5183,7 @@
     method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String);
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
+    method public long getTimeout();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
     field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5201,8 +5211,10 @@
     field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
     field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
     field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+    field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
     field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
     field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+    field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
     field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
     field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
     field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
@@ -5367,6 +5379,7 @@
     method public android.app.Notification.Builder setChannel(java.lang.String);
     method public android.app.Notification.Builder setChronometerCountDown(boolean);
     method public android.app.Notification.Builder setColor(int);
+    method public android.app.Notification.Builder setColorized(boolean);
     method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
     method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -5404,6 +5417,7 @@
     method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setTimeout(long);
     method public android.app.Notification.Builder setUsesChronometer(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setVisibility(int);
@@ -5470,9 +5484,11 @@
 
   public static class Notification.MessagingStyle extends android.app.Notification.Style {
     ctor public Notification.MessagingStyle(java.lang.CharSequence);
+    method public android.app.Notification.MessagingStyle addHistoricMessage(android.app.Notification.MessagingStyle.Message);
     method public android.app.Notification.MessagingStyle addMessage(java.lang.CharSequence, long, java.lang.CharSequence);
     method public android.app.Notification.MessagingStyle addMessage(android.app.Notification.MessagingStyle.Message);
     method public java.lang.CharSequence getConversationTitle();
+    method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
     method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
     method public java.lang.CharSequence getUserDisplayName();
     method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
@@ -6306,6 +6322,7 @@
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+    method public android.content.Intent createAdminSupportIntent(java.lang.String);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
     method public void enableSystemApp(android.content.ComponentName, java.lang.String);
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6314,16 +6331,18 @@
     method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
     method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
-    method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+    method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
     method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
-    method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+    method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
     method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
     method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
+    method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+    method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
     method public deprecated java.lang.String getDeviceInitializerApp();
     method public deprecated android.content.ComponentName getDeviceInitializerComponent();
     method public java.lang.String getDeviceOwner();
@@ -6379,7 +6398,7 @@
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
     method public boolean isBackupServiceEnabled(android.content.ComponentName);
-    method public boolean isCallerApplicationRestrictionsManagingPackage();
+    method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
     method public boolean isDeviceManaged();
     method public boolean isDeviceOwnerApp(java.lang.String);
     method public boolean isDeviceProvisioned();
@@ -6414,14 +6433,15 @@
     method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
-    method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public void setAutoTimeRequired(android.content.ComponentName, boolean);
     method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
     method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
     method public void setCameraDisabled(android.content.ComponentName, boolean);
-    method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+    method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+    method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setDeviceProvisioningConfigApplied();
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6470,6 +6490,7 @@
     method public void uninstallCaCert(android.content.ComponentName, byte[]);
     method public void wipeData(int);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
+    field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6482,6 +6503,12 @@
     field public static final java.lang.String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+    field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+    field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+    field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+    field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+    field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+    field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
     field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
     field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
     field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6489,6 +6516,7 @@
     field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
     field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
     field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+    field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
     field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
     field public static final java.lang.String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
     field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
@@ -6552,6 +6580,8 @@
     field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
     field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
     field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+    field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+    field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -6787,15 +6817,18 @@
     method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
     method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, int);
     method public int requestRestore(android.app.backup.RestoreObserver);
-    method public java.lang.String selectBackupTransport(java.lang.String);
+    method public deprecated java.lang.String selectBackupTransport(java.lang.String);
+    method public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback);
     method public void setAutoRestore(boolean);
     method public void setBackupEnabled(boolean);
     field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15
     field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f
     field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
     field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
+    field public static final int ERROR_TRANSPORT_INVALID = -2; // 0xfffffffe
     field public static final int ERROR_TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
     field public static final int ERROR_TRANSPORT_QUOTA_EXCEEDED = -1005; // 0xfffffc13
+    field public static final int ERROR_TRANSPORT_UNAVAILABLE = -1; // 0xffffffff
     field public static final int FLAG_NON_INCREMENTAL_BACKUP = 1; // 0x1
     field public static final java.lang.String PACKAGE_MANAGER_SENTINEL = "@pm@";
     field public static final int SUCCESS = 0; // 0x0
@@ -6910,6 +6943,12 @@
     field public long token;
   }
 
+  public abstract class SelectBackupTransportCallback {
+    ctor public SelectBackupTransportCallback();
+    method public void onFailure(int);
+    method public void onSuccess(java.lang.String);
+  }
+
   public class SharedPreferencesBackupHelper extends android.app.backup.FileBackupHelperBase implements android.app.backup.BackupHelper {
     ctor public SharedPreferencesBackupHelper(android.content.Context, java.lang.String...);
     method public void performBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor);
@@ -8726,6 +8765,7 @@
     method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public abstract deprecated void clearWallpaper() throws java.io.IOException;
     method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.Context createCredentialProtectedStorageContext();
     method public abstract android.content.Context createDeviceProtectedStorageContext();
     method public abstract android.content.Context createDisplayContext(android.view.Display);
@@ -8936,6 +8976,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public deprecated void clearWallpaper() throws java.io.IOException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createCredentialProtectedStorageContext();
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
@@ -9249,6 +9290,7 @@
     field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
     field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
     field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+    field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
     field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
@@ -9978,7 +10020,10 @@
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
     method public final int getThemeResource();
-    field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000
+    field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
+    field public static final int COLOR_MODE_HDR = 2; // 0x2
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+    field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
     field public static final int CONFIG_DENSITY = 4096; // 0x1000
     field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
     field public static final int CONFIG_KEYBOARD = 16; // 0x10
@@ -10039,6 +10084,7 @@
     field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb
     field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc
     field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1
+    field public int colorMode;
     field public int configChanges;
     field public int documentLaunchMode;
     field public int flags;
@@ -10136,6 +10182,7 @@
     field public int requiresSmallestWidthDp;
     field public java.lang.String[] sharedLibraryFiles;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public int targetSdkVersion;
@@ -10164,6 +10211,7 @@
     field public boolean enabled;
     field public boolean exported;
     field public java.lang.String processName;
+    field public java.lang.String splitName;
   }
 
   public class ConfigurationInfo implements android.os.Parcelable {
@@ -10194,7 +10242,8 @@
 
   public final class EphemeralResolveInfo implements android.os.Parcelable {
     ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List<android.content.IntentFilter>);
-    ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
+    ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
+    ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>, int);
     ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List<android.content.pm.EphemeralIntentFilter>);
     method public int describeContents();
     method public byte[] getDigestBytes();
@@ -10202,6 +10251,7 @@
     method public deprecated java.util.List<android.content.IntentFilter> getFilters();
     method public java.util.List<android.content.pm.EphemeralIntentFilter> getIntentFilters();
     method public java.lang.String getPackageName();
+    method public int getVersionCode();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.EphemeralResolveInfo> CREATOR;
     field public static final java.lang.String SHA_ALGORITHM = "SHA-256";
@@ -10250,6 +10300,7 @@
     field public boolean handleProfiling;
     field public java.lang.String publicSourceDir;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public java.lang.String targetPackage;
@@ -10644,6 +10695,7 @@
     field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+    field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded";
     field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet";
     field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch";
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
@@ -11155,16 +11207,16 @@
     method public void setToDefaults();
     method public int updateFrom(android.content.res.Configuration);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc
-    field public static final int COLORIMETRY_HDR_NO = 4; // 0x4
-    field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2
-    field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_HDR_YES = 8; // 0x8
-    field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2
+    field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc
+    field public static final int COLOR_MODE_HDR_NO = 4; // 0x4
+    field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2
+    field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_HDR_YES = 8; // 0x8
+    field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
     field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
     field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
@@ -11230,7 +11282,7 @@
     field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0
     field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7
     field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6
-    field public int colorimetry;
+    field public int colorMode;
     field public int densityDpi;
     field public float fontScale;
     field public int hardKeyboardHidden;
@@ -13771,6 +13823,7 @@
   }
 
   public class Typeface {
+    method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13791,6 +13844,14 @@
     field public static final android.graphics.Typeface SERIF;
   }
 
+  public static abstract interface Typeface.FontRequestCallback {
+    method public abstract void onTypefaceRequestFailed(int);
+    method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+    field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+    field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+  }
+
   public class Xfermode {
     ctor public Xfermode();
   }
@@ -14197,6 +14258,23 @@
     method public void addLevel(int, int, android.graphics.drawable.Drawable);
   }
 
+  public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+    ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+    method public void draw(android.graphics.Canvas);
+    method public android.graphics.drawable.Drawable getBackground();
+    method public android.graphics.drawable.Drawable getForeground();
+    method public android.graphics.Path getIconMask();
+    method public int getOpacity();
+    method public void invalidateDrawable(android.graphics.drawable.Drawable);
+    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+    method public void setAlpha(int);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setOpacity(int);
+    method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+    field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
+    field public static final float MASK_SIZE = 100.0f;
+  }
+
   public class NinePatchDrawable extends android.graphics.drawable.Drawable {
     ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
     ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -14345,6 +14423,19 @@
 
 }
 
+package android.graphics.fonts {
+
+  public final class FontRequest implements android.os.Parcelable {
+    ctor public FontRequest(java.lang.String, java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getProviderAuthority();
+    method public java.lang.String getQuery();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+  }
+
+}
+
 package android.graphics.pdf {
 
   public class PdfDocument {
@@ -14683,9 +14774,10 @@
     method public int getWidth();
     method public boolean isDestroyed();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BLOB = 33; // 0x21
     field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
     field public static final int RGBA_8888 = 1; // 0x1
-    field public static final int RGBA_FP16 = 5; // 0x5
+    field public static final int RGBA_FP16 = 22; // 0x16
     field public static final int RGBX_8888 = 2; // 0x2
     field public static final int RGB_565 = 4; // 0x4
     field public static final int RGB_888 = 3; // 0x3
@@ -14706,6 +14798,7 @@
   public final class Sensor {
     method public int getFifoMaxEventCount();
     method public int getFifoReservedEventCount();
+    method public int getHighestDirectReportRateLevel();
     method public int getId();
     method public int getMaxDelay();
     method public float getMaximumRange();
@@ -14721,6 +14814,7 @@
     method public int getVersion();
     method public boolean isAdditionalInfoSupported();
     method public boolean isDataInjectionSupported();
+    method public boolean isDirectChannelTypeSupported(int);
     method public boolean isDynamicSensor();
     method public boolean isWakeUpSensor();
     field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14802,6 +14896,17 @@
     field public final int type;
   }
 
+  public final class SensorDirectChannel implements java.lang.AutoCloseable {
+    method public void close();
+    method public boolean isValid();
+    field public static final int RATE_FAST = 2; // 0x2
+    field public static final int RATE_NORMAL = 1; // 0x1
+    field public static final int RATE_STOP = 0; // 0x0
+    field public static final int RATE_VERY_FAST = 3; // 0x3
+    field public static final int TYPE_ASHMEM = 1; // 0x1
+    field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+  }
+
   public class SensorEvent {
     field public int accuracy;
     field public android.hardware.Sensor sensor;
@@ -14833,6 +14938,9 @@
 
   public abstract class SensorManager {
     method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+    method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+    method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+    method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
     method public boolean flush(android.hardware.SensorEventListener);
     method public static float getAltitude(float, float);
     method public static void getAngleChange(float[], float[], float[]);
@@ -14964,7 +15072,7 @@
     method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void close();
-    method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
+    method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
     method public abstract android.hardware.camera2.CameraDevice getDevice();
     method public abstract android.view.Surface getInputSurface();
     method public abstract boolean isReprocessable();
@@ -15379,6 +15487,7 @@
     field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+    field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15458,6 +15567,7 @@
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+    field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15607,11 +15717,13 @@
     ctor public OutputConfiguration(android.view.Surface, int);
     ctor public OutputConfiguration(int, android.view.Surface, int);
     ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
+    method public void addSurface(android.view.Surface);
     method public int describeContents();
+    method public void enableSurfaceSharing();
     method public int getRotation();
     method public android.view.Surface getSurface();
     method public int getSurfaceGroupId();
-    method public void setDeferredSurface(android.view.Surface);
+    method public java.util.List<android.view.Surface> getSurfaces();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
     field public static final int ROTATION_0 = 0; // 0x0
@@ -18872,6 +18984,15 @@
     method public boolean isTransitionalDifferent();
   }
 
+  public final class ListFormatter {
+    method public java.lang.String format(java.lang.Object...);
+    method public java.lang.String format(java.util.Collection<?>);
+    method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+    method public static android.icu.text.ListFormatter getInstance();
+    method public java.lang.String getPatternForNumItems(int);
+  }
+
   public abstract class LocaleDisplayNames {
     method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
     method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -18881,6 +19002,8 @@
     method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
     method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
     method public abstract android.icu.util.ULocale getLocale();
+    method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+    method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
     method public abstract java.lang.String keyDisplayName(java.lang.String);
     method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
     method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -18900,9 +19023,19 @@
     enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
   }
 
+  public static class LocaleDisplayNames.UiListItem {
+    ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+    field public final android.icu.util.ULocale minimized;
+    field public final android.icu.util.ULocale modified;
+    field public final java.lang.String nameInDisplayLocale;
+    field public final java.lang.String nameInSelf;
+  }
+
   public class MeasureFormat extends android.icu.text.UFormat {
     method public final boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+    method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
     method public final java.lang.String formatMeasures(android.icu.util.Measure...);
     method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
     method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -19371,6 +19504,14 @@
     method public void setUpperCaseFirst(boolean);
   }
 
+  public final class ScientificNumberFormatter {
+    method public java.lang.String format(java.lang.Object);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+  }
+
   public abstract class SearchIterator {
     ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
     method public final int first();
@@ -20146,6 +20287,34 @@
     method public long getToDate();
   }
 
+  public final class EthiopicCalendar extends android.icu.util.CECalendar {
+    ctor public EthiopicCalendar();
+    ctor public EthiopicCalendar(android.icu.util.TimeZone);
+    ctor public EthiopicCalendar(java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.ULocale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+    ctor public EthiopicCalendar(int, int, int);
+    ctor public EthiopicCalendar(java.util.Date);
+    ctor public EthiopicCalendar(int, int, int, int, int, int);
+    method protected deprecated int handleGetExtendedYear();
+    method public boolean isAmeteAlemEra();
+    method public void setAmeteAlemEra(boolean);
+    field public static final int GENBOT = 8; // 0x8
+    field public static final int HAMLE = 10; // 0xa
+    field public static final int HEDAR = 2; // 0x2
+    field public static final int MEGABIT = 6; // 0x6
+    field public static final int MESKEREM = 0; // 0x0
+    field public static final int MIAZIA = 7; // 0x7
+    field public static final int NEHASSE = 11; // 0xb
+    field public static final int PAGUMEN = 12; // 0xc
+    field public static final int SENE = 9; // 0x9
+    field public static final int TAHSAS = 3; // 0x3
+    field public static final int TEKEMT = 1; // 0x1
+    field public static final int TER = 4; // 0x4
+    field public static final int YEKATIT = 5; // 0x5
+  }
+
   public abstract interface Freezable<T> implements java.lang.Cloneable {
     method public abstract T cloneAsThawed();
     method public abstract T freeze();
@@ -20674,6 +20843,35 @@
     enum_constant public static final android.icu.util.ULocale.Category FORMAT;
   }
 
+  public final class UniversalTimeScale {
+    method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+    method public static long from(long, int);
+    method public static long getTimeScaleValue(int, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+    method public static long toLong(long, int);
+    field public static final int DB2_TIME = 8; // 0x8
+    field public static final int DOTNET_DATE_TIME = 4; // 0x4
+    field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+    field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+    field public static final int EXCEL_TIME = 7; // 0x7
+    field public static final int FROM_MAX_VALUE = 3; // 0x3
+    field public static final int FROM_MIN_VALUE = 2; // 0x2
+    field public static final int ICU4C_TIME = 2; // 0x2
+    field public static final int JAVA_TIME = 0; // 0x0
+    field public static final int MAC_OLD_TIME = 5; // 0x5
+    field public static final int MAC_TIME = 6; // 0x6
+    field public static final int MAX_SCALE = 10; // 0xa
+    field public static final int TO_MAX_VALUE = 5; // 0x5
+    field public static final int TO_MIN_VALUE = 4; // 0x4
+    field public static final int UNITS_VALUE = 0; // 0x0
+    field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+    field public static final int UNIX_TIME = 1; // 0x1
+    field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+  }
+
   public abstract interface ValueIterator {
     method public abstract boolean next(android.icu.util.ValueIterator.Element);
     method public abstract void reset();
@@ -21747,7 +21945,7 @@
     field public static final int FLAG_BYPASS_MUTE = 128; // 0x80
     field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
     field public static final int FLAG_HW_HOTWORD = 32; // 0x20
-    field public static final int FLAG_LOW_LATENCY = 256; // 0x100
+    field public static final deprecated int FLAG_LOW_LATENCY = 256; // 0x100
     field public static final int USAGE_ALARM = 4; // 0x4
     field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
     field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -22233,6 +22431,7 @@
     method protected deprecated int getNativeFrameCount();
     method public static int getNativeOutputSampleRate(int);
     method public int getNotificationMarkerPosition();
+    method public int getPerformanceMode();
     method public int getPlayState();
     method public int getPlaybackHeadPosition();
     method public android.media.PlaybackParams getPlaybackParams();
@@ -22279,6 +22478,9 @@
     field public static final int ERROR_INVALID_OPERATION = -3; // 0xfffffffd
     field public static final int MODE_STATIC = 0; // 0x0
     field public static final int MODE_STREAM = 1; // 0x1
+    field public static final int PERFORMANCE_MODE_LOW_LATENCY = 1; // 0x1
+    field public static final int PERFORMANCE_MODE_NONE = 0; // 0x0
+    field public static final int PERFORMANCE_MODE_POWER_SAVING = 2; // 0x2
     field public static final int PLAYSTATE_PAUSED = 2; // 0x2
     field public static final int PLAYSTATE_PLAYING = 3; // 0x3
     field public static final int PLAYSTATE_STOPPED = 1; // 0x1
@@ -22296,6 +22498,7 @@
     method public android.media.AudioTrack.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setAudioFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
+    method public android.media.AudioTrack.Builder setPerformanceMode(int);
     method public android.media.AudioTrack.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
   }
@@ -22310,6 +22513,40 @@
     method public default void onRoutingChanged(android.media.AudioRouting);
   }
 
+  public final class BufferingParams implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getInitialBufferingMode();
+    method public int getInitialBufferingWatermarkKB();
+    method public int getInitialBufferingWatermarkMs();
+    method public int getRebufferingMode();
+    method public int getRebufferingWatermarkHighKB();
+    method public int getRebufferingWatermarkHighMs();
+    method public int getRebufferingWatermarkLowKB();
+    method public int getRebufferingWatermarkLowMs();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+    field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+    field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+    field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+    field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+  }
+
+  public static class BufferingParams.Builder {
+    ctor public BufferingParams.Builder();
+    ctor public BufferingParams.Builder(android.media.BufferingParams);
+    method public android.media.BufferingParams build();
+    method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+    method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+    method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingMode(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+  }
+
   public class CamcorderProfile {
     method public static android.media.CamcorderProfile get(int);
     method public static android.media.CamcorderProfile get(int, int);
@@ -23482,7 +23719,9 @@
     method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
     method public void deselectTrack(int) throws java.lang.IllegalStateException;
     method public int getAudioSessionId();
+    method public android.media.BufferingParams getBufferingParams();
     method public int getCurrentPosition();
+    method public android.media.BufferingParams getDefaultBufferingParams();
     method public int getDuration();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -23505,6 +23744,7 @@
     method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public deprecated void setAudioStreamType(int);
     method public void setAuxEffectSendLevel(float);
+    method public void setBufferingParams(android.media.BufferingParams);
     method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
     method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
     method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -25067,12 +25307,22 @@
     method public void addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler);
     method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
     method public void removeOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+    method public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, android.os.Handler);
+    method public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, android.os.Handler);
   }
 
   public static abstract interface MediaSessionManager.OnActiveSessionsChangedListener {
     method public abstract void onActiveSessionsChanged(java.util.List<android.media.session.MediaController>);
   }
 
+  public static abstract interface MediaSessionManager.OnMediaKeyListener {
+    method public abstract boolean onMediaKey(android.view.KeyEvent);
+  }
+
+  public static abstract interface MediaSessionManager.OnVolumeKeyLongPressListener {
+    method public abstract void onVolumeKeyLongPress(android.view.KeyEvent);
+  }
+
   public final class PlaybackState implements android.os.Parcelable {
     method public int describeContents();
     method public long getActions();
@@ -25268,6 +25518,7 @@
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
     field public static final java.lang.String COLUMN_SERVICE_ID = "service_id";
     field public static final java.lang.String COLUMN_SERVICE_TYPE = "service_type";
+    field public static final java.lang.String COLUMN_TRANSIENT = "transient";
     field public static final java.lang.String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
     field public static final java.lang.String COLUMN_TYPE = "type";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
@@ -25299,6 +25550,7 @@
     field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
     field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
     field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+    field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
     field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
     field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
     field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -25339,8 +25591,14 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
     field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
+    field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position";
+    field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
     field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
     field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
@@ -25350,6 +25608,7 @@
     field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final java.lang.String COLUMN_TITLE = "title";
+    field public static final java.lang.String COLUMN_TRANSIENT = "transient";
     field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number";
     field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height";
     field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width";
@@ -25549,6 +25808,7 @@
     field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
     field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
     field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+    field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
     field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
     field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
     field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -25804,6 +26064,47 @@
 
 }
 
+package android.metrics {
+
+  public class LogMaker {
+    ctor public LogMaker(int);
+    ctor public LogMaker(java.lang.Object[]);
+    method public android.metrics.LogMaker addTaggedData(int, java.lang.Object);
+    method public void deserialize(java.lang.Object[]);
+    method public int getCategory();
+    method public long getCounterBucket();
+    method public java.lang.String getCounterName();
+    method public int getCounterValue();
+    method public java.lang.String getPackageName();
+    method public int getSubtype();
+    method public java.lang.Object getTaggedData(int);
+    method public long getTimestamp();
+    method public int getType();
+    method public boolean isLongCounterBucket();
+    method public boolean isValidValue(java.lang.Object);
+    method public java.lang.Object[] serialize();
+    method public android.metrics.LogMaker setCategory(int);
+    method public android.metrics.LogMaker setCounterBucket(int);
+    method public android.metrics.LogMaker setCounterBucket(long);
+    method public android.metrics.LogMaker setCounterName(java.lang.String);
+    method public android.metrics.LogMaker setCounterValue(int);
+    method public android.metrics.LogMaker setPackageName(java.lang.String);
+    method public android.metrics.LogMaker setSubtype(int);
+    method public android.metrics.LogMaker setTimestamp(long);
+    method public android.metrics.LogMaker setType(int);
+  }
+
+  public class MetricsReader {
+    ctor public MetricsReader();
+    method public void checkpoint();
+    method public boolean hasNext();
+    method public android.metrics.LogMaker next();
+    method public void read(long);
+    method public void reset();
+  }
+
+}
+
 package android.mtp {
 
   public final class MtpConstants {
@@ -26320,6 +26621,10 @@
     field public static final android.os.Parcelable.Creator<android.net.Network> CREATOR;
   }
 
+  public class NetworkBadging {
+    method public static android.graphics.drawable.Drawable getWifiIcon(int, int, android.content.res.Resources.Theme);
+  }
+
   public final class NetworkCapabilities implements android.os.Parcelable {
     ctor public NetworkCapabilities(android.net.NetworkCapabilities);
     method public int describeContents();
@@ -32459,6 +32764,7 @@
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
+    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -32503,6 +32809,7 @@
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
@@ -33376,6 +33683,7 @@
     method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
     method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
     method public int getOrder();
+    method public android.preference.PreferenceGroup getParent();
     method protected boolean getPersistedBoolean(boolean);
     method protected float getPersistedFloat(float);
     method protected int getPersistedInt(int);
@@ -35760,7 +36068,6 @@
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
-    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
     method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
     method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
@@ -35774,6 +36081,16 @@
     method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
   }
 
+  public class FontsContract {
+  }
+
+  public static final class FontsContract.Columns implements android.provider.BaseColumns {
+    ctor public FontsContract.Columns();
+    field public static final java.lang.String STYLE = "font_style";
+    field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+    field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+  }
+
   public final deprecated class LiveFolders implements android.provider.BaseColumns {
     field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
     field public static final java.lang.String DESCRIPTION = "description";
@@ -36241,6 +36558,7 @@
     field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
     field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
     field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+    field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
     field public static final java.lang.String ACTION_CONFIGURE_WIFI_SETTINGS = "android.settings.CONFIGURE_WIFI_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
@@ -36296,8 +36614,10 @@
     field public static final java.lang.String AUTHORITY = "settings";
     field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
     field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+    field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
     field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
     field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+    field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -38189,6 +38509,18 @@
 
 package android.security.keystore {
 
+  public abstract class AttestationUtils {
+    method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException;
+    field public static final int ID_TYPE_IMEI = 2; // 0x2
+    field public static final int ID_TYPE_MEID = 3; // 0x3
+    field public static final int ID_TYPE_SERIAL = 1; // 0x1
+  }
+
+  public class DeviceIdAttestationException extends java.lang.Exception {
+    ctor public DeviceIdAttestationException(java.lang.String);
+    ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+  }
+
   public class KeyExpiredException extends java.security.InvalidKeyException {
     ctor public KeyExpiredException();
     ctor public KeyExpiredException(java.lang.String);
@@ -38362,17 +38694,25 @@
     ctor public AutoFillService();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
+    method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
     method public void onDisconnected();
     method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
     method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
     field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
     field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+    field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+    field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+    field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+    field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
     field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
   }
 
   public final class FillCallback {
+    method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
     method public void onFailure(java.lang.CharSequence);
+    method public void onFillResponseAuthentication(int);
     method public void onSuccess(android.view.autofill.FillResponse);
   }
 
@@ -38657,6 +38997,7 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
     method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+    method public final void unsnoozeNotification(java.lang.String);
     method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
@@ -38674,6 +39015,7 @@
     method public final int getCurrentInterruptionFilter();
     method public final int getCurrentListenerHints();
     method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+    method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onInterruptionFilterChanged(int);
     method public void onListenerConnected();
@@ -38694,9 +39036,7 @@
     method public final void setOnNotificationPostedTrim(int);
     method public final void snoozeNotification(java.lang.String, java.lang.String);
     method public final void snoozeNotification(java.lang.String, long);
-    method public final void snoozeNotification(java.lang.String);
     method public void unregisterAsSystemService() throws android.os.RemoteException;
-    method public final void unsnoozeNotification(java.lang.String);
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -38721,9 +39061,9 @@
     field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
     field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
     field public static final int REASON_SNOOZED = 18; // 0x12
+    field public static final int REASON_TIMEOUT = 19; // 0x13
     field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
     field public static final int REASON_USER_STOPPED = 6; // 0x6
-    field public static final int REASON_USER_SWITCH = 19; // 0x13
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
     field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
     field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -38733,6 +39073,7 @@
 
   public static class NotificationListenerService.Ranking {
     ctor public NotificationListenerService.Ranking();
+    method public boolean canShowBadge();
     method public java.util.List<java.lang.String> getAdditionalPeople();
     method public android.app.NotificationChannel getChannel();
     method public int getImportance();
@@ -38774,7 +39115,6 @@
     method public int getId();
     method public java.lang.String getKey();
     method public android.app.Notification getNotification();
-    method public android.app.NotificationChannel getNotificationChannel();
     method public java.lang.String getOverrideGroupKey();
     method public java.lang.String getPackageName();
     method public long getPostTime();
@@ -39235,6 +39575,7 @@
     method public abstract int getMaxBufferSize();
     method public abstract boolean hasFinished();
     method public abstract boolean hasStarted();
+    method public default void rangeStart(int, int, int);
     method public abstract int start(int, int, int);
   }
 
@@ -39387,6 +39728,7 @@
     method public void onError(java.lang.String, int);
     method public abstract void onStart(java.lang.String);
     method public void onStop(java.lang.String, boolean);
+    method public void onUtteranceRangeStart(java.lang.String, int, int);
   }
 
   public class Voice implements android.os.Parcelable {
@@ -41074,6 +41416,7 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+    field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
@@ -41744,6 +42087,7 @@
     method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+    method public boolean isConcurrentVoiceAndDataAllowed();
     method public boolean isDataConnectivityPossible();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isIdle();
@@ -41808,6 +42152,7 @@
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+    field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
     field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
     field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
     field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -41816,6 +42161,7 @@
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
     field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+    field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -42022,6 +42368,15 @@
 
 }
 
+package android.telephony.ims {
+
+  public class ImsServiceBase extends android.app.Service {
+    ctor public ImsServiceBase();
+    method public android.os.IBinder onBind(android.content.Intent);
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -42366,6 +42721,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createCredentialProtectedStorageContext();
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
@@ -43228,22 +43584,6 @@
     method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
   }
 
-  public abstract interface TextAssistant {
-    method public abstract void addLinks(android.text.Spannable, int);
-    method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-  public class TextClassification {
-    ctor public TextClassification();
-    method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
-  }
-
-  public final class TextClassificationManager implements android.text.TextAssistant {
-    method public void addLinks(android.text.Spannable, int);
-    method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
-    method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
   public abstract interface TextDirectionHeuristic {
     method public abstract boolean isRtl(char[], int, int);
     method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -43259,13 +43599,6 @@
     field public static final android.text.TextDirectionHeuristic RTL;
   }
 
-  public final class TextLanguage {
-    ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
-    method public int getEndIndex();
-    method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
-    method public int getStartIndex();
-  }
-
   public class TextPaint extends android.graphics.Paint {
     ctor public TextPaint();
     ctor public TextPaint(int);
@@ -43278,13 +43611,6 @@
     field public int linkColor;
   }
 
-  public class TextSelection {
-    ctor public TextSelection();
-    method public int getSelectionEndIndex();
-    method public int getSelectionStartIndex();
-    method public android.text.TextClassification getTextClassification();
-  }
-
   public class TextUtils {
     method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
     method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -47030,6 +47356,7 @@
     method public float getElevation();
     method public boolean getFilterTouchesWhenObscured();
     method public boolean getFitsSystemWindows();
+    method public int getFocusable();
     method public java.util.ArrayList<android.view.View> getFocusables(int);
     method public void getFocusedRect(android.graphics.Rect);
     method public android.graphics.drawable.Drawable getForeground();
@@ -47334,6 +47661,7 @@
     method public void setFilterTouchesWhenObscured(boolean);
     method public void setFitsSystemWindows(boolean);
     method public void setFocusable(boolean);
+    method public void setFocusable(int);
     method public void setFocusableInTouchMode(boolean);
     method public void setFocusedByDefault(boolean);
     method public void setForeground(android.graphics.drawable.Drawable);
@@ -47472,8 +47800,10 @@
     field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
     field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
     field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+    field public static final int FOCUSABLE = 1; // 0x1
     field public static final int FOCUSABLES_ALL = 0; // 0x0
     field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+    field public static final int FOCUSABLE_AUTO = 16; // 0x10
     field protected static final int[] FOCUSED_SELECTED_STATE_SET;
     field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     field protected static final int[] FOCUSED_STATE_SET;
@@ -47503,6 +47833,7 @@
     field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
     field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
     field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+    field public static final int NOT_FOCUSABLE = 0; // 0x0
     field public static final int NO_ID = -1; // 0xffffffff
     field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
     field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -48207,6 +48538,7 @@
     method public boolean getAllowReturnTransitionOverlap();
     method public final android.view.WindowManager.LayoutParams getAttributes();
     method public final android.view.Window.Callback getCallback();
+    method public int getColorMode();
     method public final android.view.Window getContainer();
     method public android.transition.Scene getContentScene();
     method public final android.content.Context getContext();
@@ -48263,6 +48595,7 @@
     method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable);
     method public abstract void setChildInt(int, int);
     method public void setClipToOutline(boolean);
+    method public void setColorMode(int);
     method public void setContainer(android.view.Window);
     method public abstract void setContentView(int);
     method public abstract void setContentView(android.view.View);
@@ -48462,9 +48795,11 @@
     method public final int copyFrom(android.view.WindowManager.LayoutParams);
     method public java.lang.String debug(java.lang.String);
     method public int describeContents();
+    method public int getColorMode();
     method public final java.lang.CharSequence getTitle();
     method public final long getUserActivityTimeout();
     method public static boolean mayUseInputMethod(int);
+    method public void setColorMode(int);
     method public final void setTitle(java.lang.CharSequence);
     method public final void setUserActivityTimeout(long);
     method public void writeToParcel(android.os.Parcel, int);
@@ -49334,6 +49669,13 @@
     field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
   }
 
+  public final class AutoFillManager {
+    method public void updateAutoFillInput(android.view.View, int);
+    method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+    field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+    field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+  }
+
   public final class AutoFillType implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutoFillType forList();
@@ -49368,6 +49710,8 @@
   public static final class Dataset.Builder {
     ctor public Dataset.Builder(java.lang.CharSequence);
     method public android.view.autofill.Dataset build();
+    method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+    method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
     method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
     method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
   }
@@ -49383,6 +49727,8 @@
     method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
     method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
     method public android.view.autofill.FillResponse build();
+    method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+    method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
     method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
   }
 
@@ -49393,7 +49739,7 @@
 
   public static abstract class VirtualViewDelegate.Callback {
     ctor public VirtualViewDelegate.Callback();
-    method public void onFocusChanged(int, boolean);
+    method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
     method public void onNodeRemoved(int...);
     method public void onValueChanged(int);
   }
@@ -49800,6 +50146,83 @@
 
 }
 
+package android.view.textclassifier {
+
+  public abstract interface LinksInfo {
+    method public abstract boolean apply(java.lang.CharSequence);
+  }
+
+  public final class TextClassificationManager {
+    method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+    method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+  }
+
+  public final class TextClassificationResult {
+    method public float getConfidenceScore(java.lang.String);
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public android.graphics.drawable.Drawable getIcon();
+    method public android.content.Intent getIntent();
+    method public java.lang.CharSequence getLabel();
+    method public android.view.View.OnClickListener getOnClickListener();
+    method public java.lang.String getText();
+  }
+
+  public static final class TextClassificationResult.Builder {
+    ctor public TextClassificationResult.Builder();
+    method public android.view.textclassifier.TextClassificationResult build();
+    method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+    method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+    method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+    method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+    method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+    method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+  }
+
+  public abstract interface TextClassifier {
+    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+    field public static final android.view.textclassifier.TextClassifier NO_OP;
+    field public static final java.lang.String TYPE_ADDRESS = "address";
+    field public static final java.lang.String TYPE_EMAIL = "email";
+    field public static final java.lang.String TYPE_OTHER = "other";
+    field public static final java.lang.String TYPE_PHONE = "phone";
+  }
+
+  public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+  }
+
+  public final class TextLanguage {
+    method public float getConfidenceScore(java.util.Locale);
+    method public int getEndIndex();
+    method public java.util.Locale getLanguage(int);
+    method public int getLanguageCount();
+    method public int getStartIndex();
+  }
+
+  public static final class TextLanguage.Builder {
+    ctor public TextLanguage.Builder(int, int);
+    method public android.view.textclassifier.TextLanguage build();
+    method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+  }
+
+  public final class TextSelection {
+    method public float getConfidenceScore(java.lang.String);
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public int getSelectionEndIndex();
+    method public int getSelectionStartIndex();
+  }
+
+  public static final class TextSelection.Builder {
+    ctor public TextSelection.Builder(int, int);
+    method public android.view.textclassifier.TextSelection build();
+    method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+  }
+
+}
+
 package android.view.textservice {
 
   public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -53250,7 +53673,7 @@
     method public float getShadowRadius();
     method public final boolean getShowSoftInputOnFocus();
     method public java.lang.CharSequence getText();
-    method public android.text.TextAssistant getTextAssistant();
+    method public android.view.textclassifier.TextClassifier getTextClassifier();
     method public final android.content.res.ColorStateList getTextColors();
     method public java.util.Locale getTextLocale();
     method public android.os.LocaleList getTextLocales();
@@ -53366,7 +53789,7 @@
     method public final void setText(int, android.widget.TextView.BufferType);
     method public void setTextAppearance(int);
     method public deprecated void setTextAppearance(android.content.Context, int);
-    method public void setTextAssistant(android.text.TextAssistant);
+    method public void setTextClassifier(android.view.textclassifier.TextClassifier);
     method public void setTextColor(int);
     method public void setTextColor(android.content.res.ColorStateList);
     method public void setTextIsSelectable(boolean);
@@ -65509,6 +65932,9 @@
     method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
     method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
     method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+    method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
     method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
     method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
     method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -65519,7 +65945,11 @@
     method public static final <T> java.util.List<T> emptyList();
     method public static <T> java.util.ListIterator<T> emptyListIterator();
     method public static final <K, V> java.util.Map<K, V> emptyMap();
+    method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+    method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
     method public static final <T> java.util.Set<T> emptySet();
+    method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+    method public static <E> java.util.SortedSet<E> emptySortedSet();
     method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
     method public static <T> void fill(java.util.List<? super T>, T);
     method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -65548,12 +65978,16 @@
     method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
     method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
     method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+    method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+    method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
     method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
     method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
     method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
     method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
     method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+    method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+    method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
     method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
     method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/test-current.txt b/api/test-current.txt
index f07f3b4..24673e1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -408,6 +408,7 @@
     field public static final int colorForeground = 16842800; // 0x1010030
     field public static final int colorForegroundInverse = 16843270; // 0x1010206
     field public static final int colorLongPressedHighlight = 16843662; // 0x101038e
+    field public static final int colorMode = 16844108; // 0x101054c
     field public static final int colorMultiSelectHighlight = 16843665; // 0x1010391
     field public static final int colorPressedHighlight = 16843661; // 0x101038d
     field public static final int colorPrimary = 16843827; // 0x1010433
@@ -738,6 +739,7 @@
     field public static final int isScrollContainer = 16843342; // 0x101024e
     field public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
+    field public static final int isolatedSplits = 16844109; // 0x101054d
     field public static final int itemBackground = 16843056; // 0x1010130
     field public static final int itemIconDisabledAlpha = 16843057; // 0x1010131
     field public static final int itemPadding = 16843565; // 0x101032d
@@ -1169,6 +1171,7 @@
     field public static final int spinnerStyle = 16842881; // 0x1010081
     field public static final int spinnersShown = 16843595; // 0x101034b
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
+    field public static final int splitName = 16844107; // 0x101054b
     field public static final int splitTrack = 16843852; // 0x101044c
     field public static final int spotShadowAlpha = 16843967; // 0x10104bf
     field public static final int src = 16843033; // 0x1010119
@@ -1817,6 +1820,7 @@
     field public static final int tabs = 16908307; // 0x1020013
     field public static final int text1 = 16908308; // 0x1020014
     field public static final int text2 = 16908309; // 0x1020015
+    field public static final int textAssist = 16908353; // 0x1020041
     field public static final int title = 16908310; // 0x1020016
     field public static final int toggle = 16908311; // 0x1020017
     field public static final int undo = 16908338; // 0x1020032
@@ -3064,8 +3068,10 @@
 
   public static abstract interface Animator.AnimatorListener {
     method public abstract void onAnimationCancel(android.animation.Animator);
+    method public default void onAnimationEnd(android.animation.Animator, boolean);
     method public abstract void onAnimationEnd(android.animation.Animator);
     method public abstract void onAnimationRepeat(android.animation.Animator);
+    method public default void onAnimationStart(android.animation.Animator, boolean);
     method public abstract void onAnimationStart(android.animation.Animator);
   }
 
@@ -3101,6 +3107,8 @@
     method public void playSequentially(java.util.List<android.animation.Animator>);
     method public void playTogether(android.animation.Animator...);
     method public void playTogether(java.util.Collection<android.animation.Animator>);
+    method public void reverse();
+    method public void setCurrentPlayTime(long);
     method public android.animation.AnimatorSet setDuration(long);
     method public void setInterpolator(android.animation.TimeInterpolator);
     method public void setStartDelay(long);
@@ -3538,7 +3546,6 @@
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
     method public int getTaskId();
-    method public android.text.TextAssistant getTextAssistant();
     method public final java.lang.CharSequence getTitle();
     method public final int getTitleColor();
     method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3688,7 +3695,6 @@
     method public final void setResult(int, android.content.Intent);
     method public final deprecated void setSecondaryProgress(int);
     method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
-    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
     method public deprecated void setTitleColor(int);
@@ -5026,6 +5032,7 @@
     method public android.graphics.drawable.Icon getLargeIcon();
     method public android.graphics.drawable.Icon getSmallIcon();
     method public java.lang.String getSortKey();
+    method public long getTimeout();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
     field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5053,8 +5060,10 @@
     field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
     field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
     field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
+    field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
     field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
     field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+    field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
     field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
     field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
     field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
@@ -5217,6 +5226,7 @@
     method public android.app.Notification.Builder setChannel(java.lang.String);
     method public android.app.Notification.Builder setChronometerCountDown(boolean);
     method public android.app.Notification.Builder setColor(int);
+    method public android.app.Notification.Builder setColorized(boolean);
     method public deprecated android.app.Notification.Builder setContent(android.widget.RemoteViews);
     method public deprecated android.app.Notification.Builder setContentInfo(java.lang.CharSequence);
     method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent);
@@ -5254,6 +5264,7 @@
     method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setTimeout(long);
     method public android.app.Notification.Builder setUsesChronometer(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setVisibility(int);
@@ -5320,9 +5331,11 @@
 
   public static class Notification.MessagingStyle extends android.app.Notification.Style {
     ctor public Notification.MessagingStyle(java.lang.CharSequence);
+    method public android.app.Notification.MessagingStyle addHistoricMessage(android.app.Notification.MessagingStyle.Message);
     method public android.app.Notification.MessagingStyle addMessage(java.lang.CharSequence, long, java.lang.CharSequence);
     method public android.app.Notification.MessagingStyle addMessage(android.app.Notification.MessagingStyle.Message);
     method public java.lang.CharSequence getConversationTitle();
+    method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
     method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
     method public java.lang.CharSequence getUserDisplayName();
     method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
@@ -6129,6 +6142,7 @@
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
+    method public android.content.Intent createAdminSupportIntent(java.lang.String);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
     method public void enableSystemApp(android.content.ComponentName, java.lang.String);
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
@@ -6137,16 +6151,18 @@
     method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
     method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
-    method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+    method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
     method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
-    method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+    method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
     method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
     method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
     method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
+    method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+    method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
     method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
     method public java.lang.CharSequence getDeviceOwnerOrganizationName();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
@@ -6195,7 +6211,7 @@
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
     method public boolean isBackupServiceEnabled(android.content.ComponentName);
-    method public boolean isCallerApplicationRestrictionsManagingPackage();
+    method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
     method public boolean isDeviceManaged();
     method public boolean isDeviceOwnerApp(java.lang.String);
     method public boolean isLockTaskPermitted(java.lang.String);
@@ -6224,14 +6240,15 @@
     method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
-    method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public void setAutoTimeRequired(android.content.ComponentName, boolean);
     method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
     method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
     method public void setCameraDisabled(android.content.ComponentName, boolean);
-    method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+    method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
     method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+    method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6279,6 +6296,7 @@
     method public void uninstallCaCert(android.content.ComponentName, byte[]);
     method public void wipeData(int);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
+    field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
     field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6288,6 +6306,12 @@
     field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+    field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+    field public static final java.lang.String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+    field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+    field public static final java.lang.String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+    field public static final java.lang.String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+    field public static final java.lang.String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
     field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
     field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
     field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6295,6 +6319,7 @@
     field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
     field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
     field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+    field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
     field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
     field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
     field public static final java.lang.String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
@@ -6326,6 +6351,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID";
+    field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
     field public static final int FLAG_EVICT_CE_KEY = 1; // 0x1
     field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
     field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
@@ -6353,6 +6379,8 @@
     field public static final int PERMISSION_POLICY_AUTO_DENY = 2; // 0x2
     field public static final int PERMISSION_POLICY_AUTO_GRANT = 1; // 0x1
     field public static final int PERMISSION_POLICY_PROMPT = 0; // 0x0
+    field public static final java.lang.String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+    field public static final java.lang.String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
@@ -8378,6 +8406,7 @@
     method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public abstract deprecated void clearWallpaper() throws java.io.IOException;
     method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public abstract android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.Context createDeviceProtectedStorageContext();
     method public abstract android.content.Context createDisplayContext(android.view.Display);
     method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8577,6 +8606,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public deprecated void clearWallpaper() throws java.io.IOException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -8886,6 +8916,7 @@
     field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
     field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
     field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
+    field public static final java.lang.String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
     field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
@@ -9600,7 +9631,10 @@
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
     method public final int getThemeResource();
-    field public static final int CONFIG_COLORIMETRY = 16384; // 0x4000
+    field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
+    field public static final int COLOR_MODE_HDR = 2; // 0x2
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+    field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
     field public static final int CONFIG_DENSITY = 4096; // 0x1000
     field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
     field public static final int CONFIG_KEYBOARD = 16; // 0x10
@@ -9661,6 +9695,7 @@
     field public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11; // 0xb
     field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc
     field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1
+    field public int colorMode;
     field public int configChanges;
     field public int documentLaunchMode;
     field public int flags;
@@ -9759,6 +9794,7 @@
     field public int requiresSmallestWidthDp;
     field public java.lang.String[] sharedLibraryFiles;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public int targetSdkVersion;
@@ -9787,6 +9823,7 @@
     field public boolean enabled;
     field public boolean exported;
     field public java.lang.String processName;
+    field public java.lang.String splitName;
   }
 
   public class ConfigurationInfo implements android.os.Parcelable {
@@ -9840,6 +9877,7 @@
     field public boolean handleProfiling;
     field public java.lang.String publicSourceDir;
     field public java.lang.String sourceDir;
+    field public java.lang.String[] splitNames;
     field public java.lang.String[] splitPublicSourceDirs;
     field public java.lang.String[] splitSourceDirs;
     field public java.lang.String targetPackage;
@@ -10205,6 +10243,7 @@
     field public static final java.lang.String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final java.lang.String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final java.lang.String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+    field public static final java.lang.String FEATURE_EMBEDDED = "android.hardware.type.embedded";
     field public static final java.lang.String FEATURE_ETHERNET = "android.hardware.ethernet";
     field public static final java.lang.String FEATURE_FAKETOUCH = "android.hardware.faketouch";
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
@@ -10642,16 +10681,16 @@
     method public void setToDefaults();
     method public int updateFrom(android.content.res.Configuration);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final int COLORIMETRY_HDR_MASK = 12; // 0xc
-    field public static final int COLORIMETRY_HDR_NO = 4; // 0x4
-    field public static final int COLORIMETRY_HDR_SHIFT = 2; // 0x2
-    field public static final int COLORIMETRY_HDR_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_HDR_YES = 8; // 0x8
-    field public static final int COLORIMETRY_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 1; // 0x1
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
-    field public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 2; // 0x2
+    field public static final int COLOR_MODE_HDR_MASK = 12; // 0xc
+    field public static final int COLOR_MODE_HDR_NO = 4; // 0x4
+    field public static final int COLOR_MODE_HDR_SHIFT = 2; // 0x2
+    field public static final int COLOR_MODE_HDR_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_HDR_YES = 8; // 0x8
+    field public static final int COLOR_MODE_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 3; // 0x3
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 1; // 0x1
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; // 0x0
+    field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
     field public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
     field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
@@ -10717,7 +10756,7 @@
     field public static final int UI_MODE_TYPE_UNDEFINED = 0; // 0x0
     field public static final int UI_MODE_TYPE_VR_HEADSET = 7; // 0x7
     field public static final int UI_MODE_TYPE_WATCH = 6; // 0x6
-    field public int colorimetry;
+    field public int colorMode;
     field public int densityDpi;
     field public float fontScale;
     field public int hardKeyboardHidden;
@@ -13258,6 +13297,7 @@
   }
 
   public class Typeface {
+    method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13278,6 +13318,14 @@
     field public static final android.graphics.Typeface SERIF;
   }
 
+  public static abstract interface Typeface.FontRequestCallback {
+    method public abstract void onTypefaceRequestFailed(int);
+    method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
+    field public static final int FAIL_REASON_FONT_LOAD_ERROR = 1; // 0x1
+    field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
+    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
+  }
+
   public class Xfermode {
     ctor public Xfermode();
   }
@@ -13684,6 +13732,23 @@
     method public void addLevel(int, int, android.graphics.drawable.Drawable);
   }
 
+  public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+    ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+    method public void draw(android.graphics.Canvas);
+    method public android.graphics.drawable.Drawable getBackground();
+    method public android.graphics.drawable.Drawable getForeground();
+    method public android.graphics.Path getIconMask();
+    method public int getOpacity();
+    method public void invalidateDrawable(android.graphics.drawable.Drawable);
+    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+    method public void setAlpha(int);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setOpacity(int);
+    method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+    field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
+    field public static final float MASK_SIZE = 100.0f;
+  }
+
   public class NinePatchDrawable extends android.graphics.drawable.Drawable {
     ctor public deprecated NinePatchDrawable(android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
     ctor public NinePatchDrawable(android.content.res.Resources, android.graphics.Bitmap, byte[], android.graphics.Rect, java.lang.String);
@@ -13832,6 +13897,19 @@
 
 }
 
+package android.graphics.fonts {
+
+  public final class FontRequest implements android.os.Parcelable {
+    ctor public FontRequest(java.lang.String, java.lang.String);
+    method public int describeContents();
+    method public java.lang.String getProviderAuthority();
+    method public java.lang.String getQuery();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
+  }
+
+}
+
 package android.graphics.pdf {
 
   public class PdfDocument {
@@ -14170,9 +14248,10 @@
     method public int getWidth();
     method public boolean isDestroyed();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BLOB = 33; // 0x21
     field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
     field public static final int RGBA_8888 = 1; // 0x1
-    field public static final int RGBA_FP16 = 5; // 0x5
+    field public static final int RGBA_FP16 = 22; // 0x16
     field public static final int RGBX_8888 = 2; // 0x2
     field public static final int RGB_565 = 4; // 0x4
     field public static final int RGB_888 = 3; // 0x3
@@ -14193,6 +14272,7 @@
   public final class Sensor {
     method public int getFifoMaxEventCount();
     method public int getFifoReservedEventCount();
+    method public int getHighestDirectReportRateLevel();
     method public int getId();
     method public int getMaxDelay();
     method public float getMaximumRange();
@@ -14206,6 +14286,7 @@
     method public java.lang.String getVendor();
     method public int getVersion();
     method public boolean isAdditionalInfoSupported();
+    method public boolean isDirectChannelTypeSupported(int);
     method public boolean isDynamicSensor();
     method public boolean isWakeUpSensor();
     field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14283,6 +14364,17 @@
     field public final int type;
   }
 
+  public final class SensorDirectChannel implements java.lang.AutoCloseable {
+    method public void close();
+    method public boolean isValid();
+    field public static final int RATE_FAST = 2; // 0x2
+    field public static final int RATE_NORMAL = 1; // 0x1
+    field public static final int RATE_STOP = 0; // 0x0
+    field public static final int RATE_VERY_FAST = 3; // 0x3
+    field public static final int TYPE_ASHMEM = 1; // 0x1
+    field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+  }
+
   public class SensorEvent {
     field public int accuracy;
     field public android.hardware.Sensor sensor;
@@ -14314,6 +14406,9 @@
 
   public abstract class SensorManager {
     method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+    method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+    method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+    method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
     method public boolean flush(android.hardware.SensorEventListener);
     method public static float getAltitude(float, float);
     method public static void getAngleChange(float[], float[], float[]);
@@ -14443,7 +14538,7 @@
     method public abstract int capture(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract int captureBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraCaptureSession.CaptureCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void close();
-    method public abstract void finishDeferredConfiguration(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
+    method public abstract void finalizeOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>) throws android.hardware.camera2.CameraAccessException;
     method public abstract android.hardware.camera2.CameraDevice getDevice();
     method public abstract android.view.Surface getInputSurface();
     method public abstract boolean isReprocessable();
@@ -14858,6 +14953,7 @@
     field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+    field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -14937,6 +15033,7 @@
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_STATE;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_CAPTURE_INTENT;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
+    field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -15084,10 +15181,12 @@
     ctor public OutputConfiguration(android.view.Surface);
     ctor public OutputConfiguration(int, android.view.Surface);
     ctor public OutputConfiguration(android.util.Size, java.lang.Class<T>);
+    method public void addSurface(android.view.Surface);
     method public int describeContents();
+    method public void enableSurfaceSharing();
     method public android.view.Surface getSurface();
     method public int getSurfaceGroupId();
-    method public void setDeferredSurface(android.view.Surface);
+    method public java.util.List<android.view.Surface> getSurfaces();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
     field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
@@ -17637,6 +17736,15 @@
     method public boolean isTransitionalDifferent();
   }
 
+  public final class ListFormatter {
+    method public java.lang.String format(java.lang.Object...);
+    method public java.lang.String format(java.util.Collection<?>);
+    method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+    method public static android.icu.text.ListFormatter getInstance();
+    method public java.lang.String getPatternForNumItems(int);
+  }
+
   public abstract class LocaleDisplayNames {
     method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
     method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17646,6 +17754,8 @@
     method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
     method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
     method public abstract android.icu.util.ULocale getLocale();
+    method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+    method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
     method public abstract java.lang.String keyDisplayName(java.lang.String);
     method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
     method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17665,9 +17775,19 @@
     enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
   }
 
+  public static class LocaleDisplayNames.UiListItem {
+    ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+    field public final android.icu.util.ULocale minimized;
+    field public final android.icu.util.ULocale modified;
+    field public final java.lang.String nameInDisplayLocale;
+    field public final java.lang.String nameInSelf;
+  }
+
   public class MeasureFormat extends android.icu.text.UFormat {
     method public final boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+    method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
     method public final java.lang.String formatMeasures(android.icu.util.Measure...);
     method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
     method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -18136,6 +18256,14 @@
     method public void setUpperCaseFirst(boolean);
   }
 
+  public final class ScientificNumberFormatter {
+    method public java.lang.String format(java.lang.Object);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+  }
+
   public abstract class SearchIterator {
     ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
     method public final int first();
@@ -18911,6 +19039,34 @@
     method public long getToDate();
   }
 
+  public final class EthiopicCalendar extends android.icu.util.CECalendar {
+    ctor public EthiopicCalendar();
+    ctor public EthiopicCalendar(android.icu.util.TimeZone);
+    ctor public EthiopicCalendar(java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.ULocale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+    ctor public EthiopicCalendar(int, int, int);
+    ctor public EthiopicCalendar(java.util.Date);
+    ctor public EthiopicCalendar(int, int, int, int, int, int);
+    method protected deprecated int handleGetExtendedYear();
+    method public boolean isAmeteAlemEra();
+    method public void setAmeteAlemEra(boolean);
+    field public static final int GENBOT = 8; // 0x8
+    field public static final int HAMLE = 10; // 0xa
+    field public static final int HEDAR = 2; // 0x2
+    field public static final int MEGABIT = 6; // 0x6
+    field public static final int MESKEREM = 0; // 0x0
+    field public static final int MIAZIA = 7; // 0x7
+    field public static final int NEHASSE = 11; // 0xb
+    field public static final int PAGUMEN = 12; // 0xc
+    field public static final int SENE = 9; // 0x9
+    field public static final int TAHSAS = 3; // 0x3
+    field public static final int TEKEMT = 1; // 0x1
+    field public static final int TER = 4; // 0x4
+    field public static final int YEKATIT = 5; // 0x5
+  }
+
   public abstract interface Freezable<T> implements java.lang.Cloneable {
     method public abstract T cloneAsThawed();
     method public abstract T freeze();
@@ -19439,6 +19595,35 @@
     enum_constant public static final android.icu.util.ULocale.Category FORMAT;
   }
 
+  public final class UniversalTimeScale {
+    method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+    method public static long from(long, int);
+    method public static long getTimeScaleValue(int, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+    method public static long toLong(long, int);
+    field public static final int DB2_TIME = 8; // 0x8
+    field public static final int DOTNET_DATE_TIME = 4; // 0x4
+    field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+    field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+    field public static final int EXCEL_TIME = 7; // 0x7
+    field public static final int FROM_MAX_VALUE = 3; // 0x3
+    field public static final int FROM_MIN_VALUE = 2; // 0x2
+    field public static final int ICU4C_TIME = 2; // 0x2
+    field public static final int JAVA_TIME = 0; // 0x0
+    field public static final int MAC_OLD_TIME = 5; // 0x5
+    field public static final int MAC_TIME = 6; // 0x6
+    field public static final int MAX_SCALE = 10; // 0xa
+    field public static final int TO_MAX_VALUE = 5; // 0x5
+    field public static final int TO_MIN_VALUE = 4; // 0x4
+    field public static final int UNITS_VALUE = 0; // 0x0
+    field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+    field public static final int UNIX_TIME = 1; // 0x1
+    field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+  }
+
   public abstract interface ValueIterator {
     method public abstract boolean next(android.icu.util.ValueIterator.Element);
     method public abstract void reset();
@@ -20295,7 +20480,7 @@
     field public static final android.os.Parcelable.Creator<android.media.AudioAttributes> CREATOR;
     field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
     field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
-    field public static final int FLAG_LOW_LATENCY = 256; // 0x100
+    field public static final deprecated int FLAG_LOW_LATENCY = 256; // 0x100
     field public static final int USAGE_ALARM = 4; // 0x4
     field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
     field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -20736,6 +20921,7 @@
     method protected deprecated int getNativeFrameCount();
     method public static int getNativeOutputSampleRate(int);
     method public int getNotificationMarkerPosition();
+    method public int getPerformanceMode();
     method public int getPlayState();
     method public int getPlaybackHeadPosition();
     method public android.media.PlaybackParams getPlaybackParams();
@@ -20782,6 +20968,9 @@
     field public static final int ERROR_INVALID_OPERATION = -3; // 0xfffffffd
     field public static final int MODE_STATIC = 0; // 0x0
     field public static final int MODE_STREAM = 1; // 0x1
+    field public static final int PERFORMANCE_MODE_LOW_LATENCY = 1; // 0x1
+    field public static final int PERFORMANCE_MODE_NONE = 0; // 0x0
+    field public static final int PERFORMANCE_MODE_POWER_SAVING = 2; // 0x2
     field public static final int PLAYSTATE_PAUSED = 2; // 0x2
     field public static final int PLAYSTATE_PLAYING = 3; // 0x3
     field public static final int PLAYSTATE_STOPPED = 1; // 0x1
@@ -20799,6 +20988,7 @@
     method public android.media.AudioTrack.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setAudioFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
+    method public android.media.AudioTrack.Builder setPerformanceMode(int);
     method public android.media.AudioTrack.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
     method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
   }
@@ -20813,6 +21003,40 @@
     method public default void onRoutingChanged(android.media.AudioRouting);
   }
 
+  public final class BufferingParams implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getInitialBufferingMode();
+    method public int getInitialBufferingWatermarkKB();
+    method public int getInitialBufferingWatermarkMs();
+    method public int getRebufferingMode();
+    method public int getRebufferingWatermarkHighKB();
+    method public int getRebufferingWatermarkHighMs();
+    method public int getRebufferingWatermarkLowKB();
+    method public int getRebufferingWatermarkLowMs();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BUFFERING_MODE_NONE = 0; // 0x0
+    field public static final int BUFFERING_MODE_SIZE_ONLY = 2; // 0x2
+    field public static final int BUFFERING_MODE_TIME_ONLY = 1; // 0x1
+    field public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3; // 0x3
+    field public static final android.os.Parcelable.Creator<android.media.BufferingParams> CREATOR;
+  }
+
+  public static class BufferingParams.Builder {
+    ctor public BufferingParams.Builder();
+    ctor public BufferingParams.Builder(android.media.BufferingParams);
+    method public android.media.BufferingParams build();
+    method public android.media.BufferingParams.Builder setInitialBufferingMode(int);
+    method public android.media.BufferingParams.Builder setInitialBufferingWatermarkKB(int);
+    method public android.media.BufferingParams.Builder setInitialBufferingWatermarkMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingMode(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkHighKB(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkHighMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkLowKB(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarkLowMs(int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarksKB(int, int);
+    method public android.media.BufferingParams.Builder setRebufferingWatermarksMs(int, int);
+  }
+
   public class CamcorderProfile {
     method public static android.media.CamcorderProfile get(int);
     method public static android.media.CamcorderProfile get(int, int);
@@ -21985,7 +22209,9 @@
     method public static android.media.MediaPlayer create(android.content.Context, int, android.media.AudioAttributes, int);
     method public void deselectTrack(int) throws java.lang.IllegalStateException;
     method public int getAudioSessionId();
+    method public android.media.BufferingParams getBufferingParams();
     method public int getCurrentPosition();
+    method public android.media.BufferingParams getDefaultBufferingParams();
     method public int getDuration();
     method public android.media.PlaybackParams getPlaybackParams();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
@@ -22008,6 +22234,7 @@
     method public void setAudioSessionId(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public deprecated void setAudioStreamType(int);
     method public void setAuxEffectSendLevel(float);
+    method public void setBufferingParams(android.media.BufferingParams);
     method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
     method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
     method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
@@ -23670,6 +23897,7 @@
     field public static final java.lang.String TYPE_NTSC = "TYPE_NTSC";
     field public static final java.lang.String TYPE_OTHER = "TYPE_OTHER";
     field public static final java.lang.String TYPE_PAL = "TYPE_PAL";
+    field public static final java.lang.String TYPE_PREVIEW = "TYPE_PREVIEW";
     field public static final java.lang.String TYPE_SECAM = "TYPE_SECAM";
     field public static final java.lang.String TYPE_S_DMB = "TYPE_S_DMB";
     field public static final java.lang.String TYPE_T_DMB = "TYPE_T_DMB";
@@ -23710,8 +23938,14 @@
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description";
     field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_DURATION = "preview_duration";
+    field public static final java.lang.String COLUMN_PREVIEW_INTENT_URI = "preview_intent_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION = "preview_last_playback_position";
+    field public static final java.lang.String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+    field public static final java.lang.String COLUMN_PREVIEW_WEIGHT = "preview_weight";
     field public static final java.lang.String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
     field public static final java.lang.String COLUMN_SEARCHABLE = "searchable";
     field public static final java.lang.String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
@@ -23840,6 +24074,7 @@
     field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
     field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
     field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+    field public static final java.lang.String ACTION_VIEW_RECORDING_SCHEDULES = "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
     field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
     field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
     field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -29920,6 +30155,7 @@
     method public final android.util.SizeF readSizeF();
     method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader);
     method public final android.util.SparseBooleanArray readSparseBooleanArray();
+    method public final android.util.SparseIntArray readSparseIntArray();
     method public final java.lang.String readString();
     method public final void readStringArray(java.lang.String[]);
     method public final void readStringList(java.util.List<java.lang.String>);
@@ -29964,6 +30200,7 @@
     method public final void writeSizeF(android.util.SizeF);
     method public final void writeSparseArray(android.util.SparseArray<java.lang.Object>);
     method public final void writeSparseBooleanArray(android.util.SparseBooleanArray);
+    method public final void writeSparseIntArray(android.util.SparseIntArray);
     method public final void writeString(java.lang.String);
     method public final void writeStringArray(java.lang.String[]);
     method public final void writeStringList(java.util.List<java.lang.String>);
@@ -30722,6 +30959,7 @@
     method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
     method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
     method public int getOrder();
+    method public android.preference.PreferenceGroup getParent();
     method protected boolean getPersistedBoolean(boolean);
     method protected float getPersistedFloat(float);
     method protected int getPersistedInt(int);
@@ -33053,7 +33291,6 @@
     method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
-    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
     method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
     method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
     method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
@@ -33067,6 +33304,16 @@
     method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
   }
 
+  public class FontsContract {
+  }
+
+  public static final class FontsContract.Columns implements android.provider.BaseColumns {
+    ctor public FontsContract.Columns();
+    field public static final java.lang.String STYLE = "font_style";
+    field public static final java.lang.String TTC_INDEX = "font_ttc_index";
+    field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
+  }
+
   public final deprecated class LiveFolders implements android.provider.BaseColumns {
     field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
     field public static final java.lang.String DESCRIPTION = "description";
@@ -33432,6 +33679,7 @@
     field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
     field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
     field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+    field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
     field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
     field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
@@ -33485,8 +33733,10 @@
     field public static final java.lang.String AUTHORITY = "settings";
     field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
     field public static final java.lang.String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+    field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
     field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
     field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+    field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -35372,6 +35622,18 @@
 
 package android.security.keystore {
 
+  public abstract class AttestationUtils {
+    method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException;
+    field public static final int ID_TYPE_IMEI = 2; // 0x2
+    field public static final int ID_TYPE_MEID = 3; // 0x3
+    field public static final int ID_TYPE_SERIAL = 1; // 0x1
+  }
+
+  public class DeviceIdAttestationException extends java.lang.Exception {
+    ctor public DeviceIdAttestationException(java.lang.String);
+    ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+  }
+
   public class KeyExpiredException extends java.security.InvalidKeyException {
     ctor public KeyExpiredException();
     ctor public KeyExpiredException(java.lang.String);
@@ -35545,17 +35807,25 @@
     ctor public AutoFillService();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
+    method public void onDatasetAuthenticationRequest(android.os.Bundle, int);
     method public void onDisconnected();
     method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback);
+    method public void onFillResponseAuthenticationRequest(android.os.Bundle, int);
     method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback);
     field public static final java.lang.String EXTRA_DATASET_EXTRAS = "android.service.autofill.extra.DATASET_EXTRAS";
     field public static final java.lang.String EXTRA_RESPONSE_EXTRAS = "android.service.autofill.extra.RESPONSE_EXTRAS";
+    field public static final int FLAG_AUTHENTICATION_ERROR = 4; // 0x4
+    field public static final int FLAG_AUTHENTICATION_REQUESTED = 1; // 0x1
+    field public static final int FLAG_AUTHENTICATION_SUCCESS = 2; // 0x2
+    field public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE = 8; // 0x8
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutoFillService";
     field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
   }
 
   public final class FillCallback {
+    method public void onDatasetAuthentication(android.view.autofill.Dataset, int);
     method public void onFailure(java.lang.CharSequence);
+    method public void onFillResponseAuthentication(int);
     method public void onSuccess(android.view.autofill.FillResponse);
   }
 
@@ -35840,6 +36110,7 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, int, boolean);
     method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
+    method public final void unsnoozeNotification(java.lang.String);
     method public void updateNotificationChannel(java.lang.String, android.app.NotificationChannel);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
   }
@@ -35855,6 +36126,7 @@
     method public final int getCurrentInterruptionFilter();
     method public final int getCurrentListenerHints();
     method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
+    method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onInterruptionFilterChanged(int);
     method public void onListenerConnected();
@@ -35873,8 +36145,6 @@
     method public final void setNotificationsShown(java.lang.String[]);
     method public final void snoozeNotification(java.lang.String, java.lang.String);
     method public final void snoozeNotification(java.lang.String, long);
-    method public final void snoozeNotification(java.lang.String);
-    method public final void unsnoozeNotification(java.lang.String);
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -35899,9 +36169,9 @@
     field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
     field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
     field public static final int REASON_SNOOZED = 18; // 0x12
+    field public static final int REASON_TIMEOUT = 19; // 0x13
     field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
     field public static final int REASON_USER_STOPPED = 6; // 0x6
-    field public static final int REASON_USER_SWITCH = 19; // 0x13
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
     field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
     field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -35909,6 +36179,7 @@
 
   public static class NotificationListenerService.Ranking {
     ctor public NotificationListenerService.Ranking();
+    method public boolean canShowBadge();
     method public java.util.List<java.lang.String> getAdditionalPeople();
     method public android.app.NotificationChannel getChannel();
     method public int getImportance();
@@ -35950,7 +36221,6 @@
     method public int getId();
     method public java.lang.String getKey();
     method public android.app.Notification getNotification();
-    method public android.app.NotificationChannel getNotificationChannel();
     method public java.lang.String getOverrideGroupKey();
     method public java.lang.String getPackageName();
     method public long getPostTime();
@@ -36366,6 +36636,7 @@
     method public abstract int getMaxBufferSize();
     method public abstract boolean hasFinished();
     method public abstract boolean hasStarted();
+    method public default void rangeStart(int, int, int);
     method public abstract int start(int, int, int);
   }
 
@@ -36518,6 +36789,7 @@
     method public void onError(java.lang.String, int);
     method public abstract void onStart(java.lang.String);
     method public void onStop(java.lang.String, boolean);
+    method public void onUtteranceRangeStart(java.lang.String, int, int);
   }
 
   public class Voice implements android.os.Parcelable {
@@ -37983,6 +38255,7 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+    field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
@@ -38609,6 +38882,7 @@
     method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String);
     method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
     method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+    method public boolean isConcurrentVoiceAndDataAllowed();
     method public boolean isHearingAidCompatibilitySupported();
     method public boolean isNetworkRoaming();
     method public boolean isSmsCapable();
@@ -38649,6 +38923,7 @@
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
+    field public static final java.lang.String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
     field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
     field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
     field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
@@ -38657,6 +38932,7 @@
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
     field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
+    field public static final java.lang.String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -39202,6 +39478,7 @@
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
+    method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -40049,22 +40326,6 @@
     method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
   }
 
-  public abstract interface TextAssistant {
-    method public abstract void addLinks(android.text.Spannable, int);
-    method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
-  public class TextClassification {
-    ctor public TextClassification();
-    method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
-  }
-
-  public final class TextClassificationManager implements android.text.TextAssistant {
-    method public void addLinks(android.text.Spannable, int);
-    method public java.util.List<android.text.TextLanguage> detectLanguages(java.lang.CharSequence);
-    method public android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
-  }
-
   public abstract interface TextDirectionHeuristic {
     method public abstract boolean isRtl(char[], int, int);
     method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -40080,13 +40341,6 @@
     field public static final android.text.TextDirectionHeuristic RTL;
   }
 
-  public final class TextLanguage {
-    ctor public TextLanguage(int, int, java.util.Map<java.lang.String, java.lang.Float>);
-    method public int getEndIndex();
-    method public java.util.Map<java.lang.String, java.lang.Float> getLanguageConfidence();
-    method public int getStartIndex();
-  }
-
   public class TextPaint extends android.graphics.Paint {
     ctor public TextPaint();
     ctor public TextPaint(int);
@@ -40099,13 +40353,6 @@
     field public int linkColor;
   }
 
-  public class TextSelection {
-    ctor public TextSelection();
-    method public int getSelectionEndIndex();
-    method public int getSelectionStartIndex();
-    method public android.text.TextClassification getTextClassification();
-  }
-
   public class TextUtils {
     method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
     method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -44017,6 +44264,7 @@
     method public float getElevation();
     method public boolean getFilterTouchesWhenObscured();
     method public boolean getFitsSystemWindows();
+    method public int getFocusable();
     method public java.util.ArrayList<android.view.View> getFocusables(int);
     method public void getFocusedRect(android.graphics.Rect);
     method public android.graphics.drawable.Drawable getForeground();
@@ -44322,6 +44570,7 @@
     method public void setFilterTouchesWhenObscured(boolean);
     method public void setFitsSystemWindows(boolean);
     method public void setFocusable(boolean);
+    method public void setFocusable(int);
     method public void setFocusableInTouchMode(boolean);
     method public void setFocusedByDefault(boolean);
     method public void setForeground(android.graphics.drawable.Drawable);
@@ -44460,8 +44709,10 @@
     field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
     field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
     field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
+    field public static final int FOCUSABLE = 1; // 0x1
     field public static final int FOCUSABLES_ALL = 0; // 0x0
     field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
+    field public static final int FOCUSABLE_AUTO = 16; // 0x10
     field protected static final int[] FOCUSED_SELECTED_STATE_SET;
     field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
     field protected static final int[] FOCUSED_STATE_SET;
@@ -44491,6 +44742,7 @@
     field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
     field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
     field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+    field public static final int NOT_FOCUSABLE = 0; // 0x0
     field public static final int NO_ID = -1; // 0xffffffff
     field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
     field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
@@ -45199,6 +45451,7 @@
     method public boolean getAllowReturnTransitionOverlap();
     method public final android.view.WindowManager.LayoutParams getAttributes();
     method public final android.view.Window.Callback getCallback();
+    method public int getColorMode();
     method public final android.view.Window getContainer();
     method public android.transition.Scene getContentScene();
     method public final android.content.Context getContext();
@@ -45255,6 +45508,7 @@
     method public abstract void setChildDrawable(int, android.graphics.drawable.Drawable);
     method public abstract void setChildInt(int, int);
     method public void setClipToOutline(boolean);
+    method public void setColorMode(int);
     method public void setContainer(android.view.Window);
     method public abstract void setContentView(int);
     method public abstract void setContentView(android.view.View);
@@ -45453,8 +45707,10 @@
     method public final int copyFrom(android.view.WindowManager.LayoutParams);
     method public java.lang.String debug(java.lang.String);
     method public int describeContents();
+    method public int getColorMode();
     method public final java.lang.CharSequence getTitle();
     method public static boolean mayUseInputMethod(int);
+    method public void setColorMode(int);
     method public final void setTitle(java.lang.CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ALPHA_CHANGED = 128; // 0x80
@@ -46325,6 +46581,13 @@
     field public static final android.os.Parcelable.Creator<android.view.autofill.AutoFillId> CREATOR;
   }
 
+  public final class AutoFillManager {
+    method public void updateAutoFillInput(android.view.View, int);
+    method public void updateAutoFillInput(android.view.View, int, android.graphics.Rect, int);
+    field public static final int FLAG_UPDATE_UI_HIDE = 2; // 0x2
+    field public static final int FLAG_UPDATE_UI_SHOW = 1; // 0x1
+  }
+
   public final class AutoFillType implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutoFillType forList();
@@ -46359,6 +46622,8 @@
   public static final class Dataset.Builder {
     ctor public Dataset.Builder(java.lang.CharSequence);
     method public android.view.autofill.Dataset build();
+    method public android.view.autofill.Dataset.Builder requiresCustomAuthentication(android.os.Bundle, int);
+    method public android.view.autofill.Dataset.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
     method public android.view.autofill.Dataset.Builder setExtras(android.os.Bundle);
     method public android.view.autofill.Dataset.Builder setValue(android.view.autofill.AutoFillId, android.view.autofill.AutoFillValue);
   }
@@ -46374,6 +46639,8 @@
     method public android.view.autofill.FillResponse.Builder addDataset(android.view.autofill.Dataset);
     method public android.view.autofill.FillResponse.Builder addSavableFields(android.view.autofill.AutoFillId...);
     method public android.view.autofill.FillResponse build();
+    method public android.view.autofill.FillResponse.Builder requiresCustomAuthentication(android.os.Bundle, int);
+    method public android.view.autofill.FillResponse.Builder requiresFingerprintAuthentication(android.hardware.fingerprint.FingerprintManager.CryptoObject, android.os.Bundle, int);
     method public android.view.autofill.FillResponse.Builder setExtras(android.os.Bundle);
   }
 
@@ -46384,7 +46651,7 @@
 
   public static abstract class VirtualViewDelegate.Callback {
     ctor public VirtualViewDelegate.Callback();
-    method public void onFocusChanged(int, boolean);
+    method public void onAutoFillInputUpdated(int, android.graphics.Rect, int);
     method public void onNodeRemoved(int...);
     method public void onValueChanged(int);
   }
@@ -46791,6 +47058,83 @@
 
 }
 
+package android.view.textclassifier {
+
+  public abstract interface LinksInfo {
+    method public abstract boolean apply(java.lang.CharSequence);
+  }
+
+  public final class TextClassificationManager {
+    method public java.util.List<android.view.textclassifier.TextLanguage> detectLanguages(java.lang.CharSequence);
+    method public android.view.textclassifier.TextClassifier getDefaultTextClassifier();
+  }
+
+  public final class TextClassificationResult {
+    method public float getConfidenceScore(java.lang.String);
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public android.graphics.drawable.Drawable getIcon();
+    method public android.content.Intent getIntent();
+    method public java.lang.CharSequence getLabel();
+    method public android.view.View.OnClickListener getOnClickListener();
+    method public java.lang.String getText();
+  }
+
+  public static final class TextClassificationResult.Builder {
+    ctor public TextClassificationResult.Builder();
+    method public android.view.textclassifier.TextClassificationResult build();
+    method public android.view.textclassifier.TextClassificationResult.Builder setEntityType(java.lang.String, float);
+    method public android.view.textclassifier.TextClassificationResult.Builder setIcon(android.graphics.drawable.Drawable);
+    method public android.view.textclassifier.TextClassificationResult.Builder setIntent(android.content.Intent);
+    method public android.view.textclassifier.TextClassificationResult.Builder setLabel(java.lang.String);
+    method public android.view.textclassifier.TextClassificationResult.Builder setOnClickListener(android.view.View.OnClickListener);
+    method public android.view.textclassifier.TextClassificationResult.Builder setText(java.lang.String);
+  }
+
+  public abstract interface TextClassifier {
+    method public abstract android.view.textclassifier.LinksInfo getLinks(java.lang.CharSequence, int);
+    method public abstract android.view.textclassifier.TextClassificationResult getTextClassificationResult(java.lang.CharSequence, int, int);
+    method public abstract android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+    field public static final android.view.textclassifier.TextClassifier NO_OP;
+    field public static final java.lang.String TYPE_ADDRESS = "address";
+    field public static final java.lang.String TYPE_EMAIL = "email";
+    field public static final java.lang.String TYPE_OTHER = "other";
+    field public static final java.lang.String TYPE_PHONE = "phone";
+  }
+
+  public static abstract class TextClassifier.EntityType implements java.lang.annotation.Annotation {
+  }
+
+  public final class TextLanguage {
+    method public float getConfidenceScore(java.util.Locale);
+    method public int getEndIndex();
+    method public java.util.Locale getLanguage(int);
+    method public int getLanguageCount();
+    method public int getStartIndex();
+  }
+
+  public static final class TextLanguage.Builder {
+    ctor public TextLanguage.Builder(int, int);
+    method public android.view.textclassifier.TextLanguage build();
+    method public android.view.textclassifier.TextLanguage.Builder setLanguage(java.util.Locale, float);
+  }
+
+  public final class TextSelection {
+    method public float getConfidenceScore(java.lang.String);
+    method public java.lang.String getEntity(int);
+    method public int getEntityCount();
+    method public int getSelectionEndIndex();
+    method public int getSelectionStartIndex();
+  }
+
+  public static final class TextSelection.Builder {
+    ctor public TextSelection.Builder(int, int);
+    method public android.view.textclassifier.TextSelection build();
+    method public android.view.textclassifier.TextSelection.Builder setEntityType(java.lang.String, float);
+  }
+
+}
+
 package android.view.textservice {
 
   public final class SentenceSuggestionsInfo implements android.os.Parcelable {
@@ -49887,7 +50231,7 @@
     method public float getShadowRadius();
     method public final boolean getShowSoftInputOnFocus();
     method public java.lang.CharSequence getText();
-    method public android.text.TextAssistant getTextAssistant();
+    method public android.view.textclassifier.TextClassifier getTextClassifier();
     method public final android.content.res.ColorStateList getTextColors();
     method public java.util.Locale getTextLocale();
     method public android.os.LocaleList getTextLocales();
@@ -50003,7 +50347,7 @@
     method public final void setText(int, android.widget.TextView.BufferType);
     method public void setTextAppearance(int);
     method public deprecated void setTextAppearance(android.content.Context, int);
-    method public void setTextAssistant(android.text.TextAssistant);
+    method public void setTextClassifier(android.view.textclassifier.TextClassifier);
     method public void setTextColor(int);
     method public void setTextColor(android.content.res.ColorStateList);
     method public void setTextIsSelectable(boolean);
@@ -62154,6 +62498,9 @@
     method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
     method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
     method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+    method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
     method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
     method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
     method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -62164,7 +62511,11 @@
     method public static final <T> java.util.List<T> emptyList();
     method public static <T> java.util.ListIterator<T> emptyListIterator();
     method public static final <K, V> java.util.Map<K, V> emptyMap();
+    method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+    method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
     method public static final <T> java.util.Set<T> emptySet();
+    method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+    method public static <E> java.util.SortedSet<E> emptySortedSet();
     method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
     method public static <T> void fill(java.util.List<? super T>, T);
     method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -62193,12 +62544,16 @@
     method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
     method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
     method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+    method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+    method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
     method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
     method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
     method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
     method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
     method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+    method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+    method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
     method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
     method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 780db5e..7e91391 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -18,20 +18,24 @@
 
 import android.app.backup.BackupManager;
 import android.app.backup.BackupProgress;
-import android.app.backup.RestoreSet;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupObserver;
 import android.app.backup.IRestoreObserver;
 import android.app.backup.IRestoreSession;
+import android.app.backup.RestoreSet;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 public final class Bmgr {
     IBackupManager mBmgr;
@@ -363,6 +367,11 @@
                 return;
             }
 
+            if ("-c".equals(which)) {
+                doTransportByComponent();
+                return;
+            }
+
             String old = mBmgr.selectBackupTransport(which);
             if (old == null) {
                 System.out.println("Unknown transport '" + which
@@ -370,12 +379,50 @@
             } else {
                 System.out.println("Selected transport " + which + " (formerly " + old + ")");
             }
+
         } catch (RemoteException e) {
             System.err.println(e.toString());
             System.err.println(BMGR_NOT_RUNNING_ERR);
         }
     }
 
+    private void doTransportByComponent() {
+        String which = nextArg();
+        if (which == null) {
+            showUsage();
+            return;
+        }
+
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        try {
+            mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which),
+                    new ISelectBackupTransportCallback.Stub() {
+                        @Override
+                        public void onSuccess(String transportName) {
+                            System.out.println("Success. Selected transport: " + transportName);
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onFailure(int reason) {
+                            System.err.println("Failure. error=" + reason);
+                            latch.countDown();
+                        }
+                    });
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(BMGR_NOT_RUNNING_ERR);
+            return;
+        }
+
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            System.err.println("Operation interrupted.");
+        }
+    }
+
     private void doWipe() {
         String transport = nextArg();
         if (transport == null) {
@@ -427,7 +474,16 @@
     }
 
     private void doListTransports() {
+        String arg = nextArg();
+
         try {
+            if ("-c".equals(arg)) {
+                for (ComponentName transport : mBmgr.listAllTransportComponents()) {
+                    System.out.println(transport.flattenToShortString());
+                }
+                return;
+            }
+
             String current = mBmgr.getCurrentTransport();
             String[] transports = mBmgr.listAllTransports();
             if (transports == null || transports.length == 0) {
@@ -649,9 +705,9 @@
         System.err.println("       bmgr backup PACKAGE");
         System.err.println("       bmgr enable BOOL");
         System.err.println("       bmgr enabled");
-        System.err.println("       bmgr list transports");
+        System.err.println("       bmgr list transports [-c]");
         System.err.println("       bmgr list sets");
-        System.err.println("       bmgr transport WHICH");
+        System.err.println("       bmgr transport WHICH|-c WHICH_COMPONENT");
         System.err.println("       bmgr restore TOKEN");
         System.err.println("       bmgr restore TOKEN PACKAGE...");
         System.err.println("       bmgr restore PACKAGE");
@@ -673,15 +729,18 @@
         System.err.println("the backup mechanism.");
         System.err.println("");
         System.err.println("The 'list transports' command reports the names of the backup transports");
-        System.err.println("currently available on the device.  These names can be passed as arguments");
+        System.err.println("BackupManager is currently bound to. These names can be passed as arguments");
         System.err.println("to the 'transport' and 'wipe' commands.  The currently active transport");
-        System.err.println("is indicated with a '*' character.");
+        System.err.println("is indicated with a '*' character. If -c flag is used, all available");
+        System.err.println("transport components on the device are listed. These can be used with");
+        System.err.println("the component variant of 'transport' command.");
         System.err.println("");
         System.err.println("The 'list sets' command reports the token and name of each restore set");
         System.err.println("available to the device via the currently active transport.");
         System.err.println("");
         System.err.println("The 'transport' command designates the named transport as the currently");
-        System.err.println("active one.  This setting is persistent across reboots.");
+        System.err.println("active one.  This setting is persistent across reboots. If -c flag is");
+        System.err.println("specified, the following string is treated as a component name.");
         System.err.println("");
         System.err.println("The 'restore' command when given just a restore token initiates a full-system");
         System.err.println("restore operation from the currently active transport.  It will deliver");
diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/cmds/media/src/com/android/commands/media/VolumeCtrl.java
index a171932..1629c6f 100755
--- a/cmds/media/src/com/android/commands/media/VolumeCtrl.java
+++ b/cmds/media/src/com/android/commands/media/VolumeCtrl.java
@@ -42,6 +42,7 @@
 
     // --stream affects --set, --adj or --get options.
     // --show affects --set and --adj options.
+    // --get can be used with --set, --adj or by itself.
     public final static String USAGE = new String(
             "the options are as follows: \n" +
             "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" +
@@ -56,9 +57,8 @@
             "\t\tadb shell media volume --stream 3 --get\n"
             );
 
-    private final static int VOLUME_CONTROL_MODE_SET = 0;
-    private final static int VOLUME_CONTROL_MODE_ADJUST = 1;
-    private final static int VOLUME_CONTROL_MODE_GET = 2;
+    private final static int VOLUME_CONTROL_MODE_SET = 1;
+    private final static int VOLUME_CONTROL_MODE_ADJUST = 2;
 
     private final static String ADJUST_LOWER = "lower";
     private final static String ADJUST_SAME = "same";
@@ -69,9 +69,10 @@
         // Default parameters
         int stream = AudioManager.STREAM_MUSIC;
         int volIndex = 5;
-        int mode = VOLUME_CONTROL_MODE_SET;
+        int mode = 0;
         int adjDir = AudioManager.ADJUST_RAISE;
         boolean showUi = false;
+        boolean doGet = false;
 
         //----------------------------------------
         // read options
@@ -83,7 +84,7 @@
                     showUi = true;
                     break;
                 case "--get":
-                    mode = VOLUME_CONTROL_MODE_GET;
+                    doGet = true;
                     log(LOG_V, "will get volume");
                     break;
                 case "--stream":
@@ -150,15 +151,16 @@
         // Non-interactive test
         final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0;
         final String pack = cmd.getClass().getPackage().getName();
-        if (mode == VOLUME_CONTROL_MODE_GET) {
-            log(LOG_V, "volume is " + audioService.getStreamVolume(stream) +
-                       " in range [" + audioService.getStreamMinVolume(stream) +
-                       ".." + audioService.getStreamMaxVolume(stream) + "]");
-        } else if (mode == VOLUME_CONTROL_MODE_SET) {
+        if (mode == VOLUME_CONTROL_MODE_SET) {
             audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/);
         } else if (mode == VOLUME_CONTROL_MODE_ADJUST) {
             audioService.adjustStreamVolume(stream, adjDir, flag, pack);
         }
+        if (doGet) {
+            log(LOG_V, "volume is " + audioService.getStreamVolume(stream) +
+                       " in range [" + audioService.getStreamMinVolume(stream) +
+                       ".." + audioService.getStreamMaxVolume(stream) + "]");
+        }
     }
 
     //--------------------------------------------
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index ac5fea3..7015381 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -408,7 +408,7 @@
             if (file.isFile()) {
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
                     params.sessionParams.setSize(
                             PackageHelper.calculateInstalledSize(pkgLite, false,
                             params.sessionParams.abiOverride));
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index 95262ab..e2e5a8f 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -276,8 +276,9 @@
          * Run animation based on the frame time.
          * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
          *                  base.
+         * @return if the animation has finished.
          */
-        void doAnimationFrame(long frameTime);
+        boolean doAnimationFrame(long frameTime);
 
         /**
          * This notifies the callback of frame commit time. Frame commit time is the time after
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index c51725a..634dc1fd 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -26,7 +26,7 @@
  * This is the superclass for classes which provide basic support for animations which can be
  * started, ended, and have <code>AnimatorListeners</code> added to them.
  */
-public abstract class Animator implements Cloneable {
+public abstract class Animator implements Cloneable, AnimationHandler.AnimationFrameCallback {
 
     /**
      * The value used to indicate infinite duration (e.g. when Animators repeat infinitely).
@@ -465,11 +465,102 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    public boolean doAnimationFrame(long frameTime) {
+        // TODO: Need to find a better signal than this
+        return getDuration() + getStartDelay() >= frameTime;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void commitAnimationFrame(long frameTime) {}
+
+
+    /**
+     * Internal use only.
+     * This call starts the animation in regular or reverse direction without requiring them to
+     * register frame callbacks. The caller will be responsible for all the subsequent animation
+     * pulses. Specifically, the caller needs to call doAnimationFrame(...) for the animation on
+     * every frame.
+     *
+     * @param inReverse whether the animation should play in reverse direction
+     */
+    void startWithoutPulsing(boolean inReverse) {
+        if (inReverse) {
+            reverse();
+        } else {
+            start();
+        }
+    }
+
+    /**
+     * Internal use only.
+     * Skips the animation value to end/start, depending on whether the play direction is forward
+     * or backward.
+     *
+     * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+     *                  equivalent to skip to start value in a forward playing direction.
+     */
+    void skipToEndValue(boolean inReverse) {}
+
+
+    /**
+     * Internal use only.
+     *
+     * Returns whether the animation has start/end values setup. For most of the animations, this
+     * should always be true. For ObjectAnimators, the start values are setup in the initialization
+     * of the animation.
+     */
+    boolean isInitialized() {
+        return true;
+    }
+
+    /**
+     * Internal use only.
+     */
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
+
+    /**
      * <p>An animation listener receives notifications from an animation.
      * Notifications indicate animation related events, such as the end or the
      * repetition of the animation.</p>
      */
     public static interface AnimatorListener {
+
+        /**
+         * <p>Notifies the start of the animation as well as the animation's overall play direction.
+         * This method's default behavior is to call {@link #onAnimationStart(Animator)}. This
+         * method can be overridden, though not required, to get the additional play direction info
+         * when an animation starts. Skipping calling super when overriding this method results in
+         * {@link #onAnimationStart(Animator)} not getting called.
+         *
+         * @param animation The started animation.
+         * @param isReverse Whether the animation is playing in reverse.
+         */
+        default void onAnimationStart(Animator animation, boolean isReverse) {
+            onAnimationStart(animation);
+        }
+
+        /**
+         * <p>Notifies the end of the animation. This callback is not invoked
+         * for animations with repeat count set to INFINITE.</p>
+         *
+         * <p>This method's default behavior is to call {@link #onAnimationEnd(Animator)}. This
+         * method can be overridden, though not required, to get the additional play direction info
+         * when an animation ends. Skipping calling super when overriding this method results in
+         * {@link #onAnimationEnd(Animator)} not getting called.
+         *
+         * @param animation The animation which reached its end.
+         * @param isReverse Whether the animation is playing in reverse.
+         */
+        default void onAnimationEnd(Animator animation, boolean isReverse) {
+            onAnimationEnd(animation);
+        }
+
         /**
          * <p>Notifies the start of the animation.</p>
          *
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index a904f9d..d5814a3 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -19,11 +19,15 @@
 import android.app.ActivityThread;
 import android.app.Application;
 import android.os.Build;
+import android.os.Looper;
+import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.animation.Animation;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -52,7 +56,7 @@
  * Animation</a> developer guide.</p>
  * </div>
  */
-public final class AnimatorSet extends Animator {
+public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
 
     private static final String TAG = "AnimatorSet";
     /**
@@ -66,7 +70,7 @@
      * Tracks animations currently being played, so that we know what to
      * cancel or end when cancel() or end() is called on this AnimatorSet
      */
-    private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>();
+    private ArrayList<Node> mPlayingSet = new ArrayList<Node>();
 
     /**
      * Contains all nodes, mapped to their respective Animators. When new
@@ -77,6 +81,11 @@
     private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
 
     /**
+     * Contains the start and end events of all the nodes. All these events are sorted in this list.
+     */
+    private ArrayList<AnimationEvent> mEvents = new ArrayList<>();
+
+    /**
      * Set of all nodes created for this AnimatorSet. This list is used upon
      * starting the set, and the nodes are placed in sorted order into the
      * sortedNodes collection.
@@ -84,21 +93,6 @@
     private ArrayList<Node> mNodes = new ArrayList<Node>();
 
     /**
-     * Animator Listener that tracks the lifecycle of each Animator in the set. It will be added
-     * to each Animator before they start and removed after they end.
-     */
-    private AnimatorSetListener mSetListener = new AnimatorSetListener(this);
-
-    /**
-     * Flag indicating that the AnimatorSet has been manually
-     * terminated (by calling cancel() or end()).
-     * This flag is used to avoid starting other animations when currently-playing
-     * child animations of this AnimatorSet end. It also determines whether cancel/end
-     * notifications are sent out via the normal AnimatorSetListener mechanism.
-     */
-    private boolean mTerminated = false;
-
-    /**
      * Tracks whether any change has been made to the AnimatorSet, which is then used to
      * determine whether the dependency graph should be re-constructed.
      */
@@ -131,8 +125,6 @@
     // was set on this AnimatorSet, so it should not be passed down to the children.
     private TimeInterpolator mInterpolator = null;
 
-    // Whether the AnimatorSet can be reversed.
-    private boolean mReversible = true;
     // The total duration of finishing all the Animators in the set.
     private long mTotalDuration = 0;
 
@@ -142,6 +134,46 @@
     // the animator set and immediately end it for N and forward.
     private final boolean mShouldIgnoreEndWithoutStart;
 
+    // In pre-O releases, calling start() doesn't reset all the animators values to start values.
+    // As a result, the start of the animation is inconsistent with what setCurrentPlayTime(0) would
+    // look like on O. Also it is inconsistent with what reverse() does on O, as reverse would
+    // advance all the animations to the right beginning values for before starting to reverse.
+    // From O and forward, we will add an additional step of resetting the animation values (unless
+    // the animation was previously seeked and therefore doesn't start from the beginning).
+    private final boolean mShouldResetValuesAtStart;
+
+    // The time, in milliseconds, when last frame of the animation came in. -1 when the animation is
+    // not running.
+    private long mLastFrameTime = -1;
+
+    // The time, in milliseconds, when the first frame of the animation came in.
+    // -1 when the animation is not running.
+    private long mFirstFrame = -1;
+
+    // The time, in milliseconds, when the first frame of the animation came in.
+    // -1 when the animation is not running.
+    private int mLastEventId = -1;
+
+    // Indicates whether the animation is reversing.
+    private boolean mReversing = false;
+
+    // Indicates whether the animation should register frame callbacks. If false, the animation will
+    // passively wait for an AnimatorSet to pulse it.
+    private boolean mSelfPulse = true;
+
+    // SeekState stores the last seeked play time as well as seek direction.
+    private SeekState mSeekState = new SeekState();
+
+    // Indicates where children animators are all initialized with their start values captured.
+    private boolean mChildrenInitialized = false;
+
+    /**
+     * Set on the next frame after pause() is called, used to calculate a new startTime
+     * or delayStartTime which allows the animator set to continue from the point at which
+     * it was paused. If negative, has not yet been set.
+     */
+    private long mPauseTime = -1;
+
     public AnimatorSet() {
         super();
         mNodeMap.put(mDelayAnim, mRootNode);
@@ -150,10 +182,19 @@
         Application app = ActivityThread.currentApplication();
         if (app == null || app.getApplicationInfo() == null) {
             mShouldIgnoreEndWithoutStart = true;
-        } else if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
-            mShouldIgnoreEndWithoutStart = true;
+            mShouldResetValuesAtStart = false;
         } else {
-            mShouldIgnoreEndWithoutStart = false;
+            if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
+                mShouldIgnoreEndWithoutStart = true;
+            } else {
+                mShouldIgnoreEndWithoutStart = false;
+            }
+
+            if (app.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O) {
+                mShouldResetValuesAtStart = false;
+            } else {
+                mShouldResetValuesAtStart = true;
+            }
         }
     }
 
@@ -206,7 +247,6 @@
             if (items.length == 1) {
                 play(items[0]);
             } else {
-                mReversible = false;
                 for (int i = 0; i < items.length - 1; ++i) {
                     play(items[i]).before(items[i + 1]);
                 }
@@ -225,7 +265,6 @@
             if (items.size() == 1) {
                 play(items.get(0));
             } else {
-                mReversible = false;
                 for (int i = 0; i < items.size() - 1; ++i) {
                     play(items.get(i)).before(items.get(i + 1));
                 }
@@ -350,7 +389,9 @@
     @SuppressWarnings("unchecked")
     @Override
     public void cancel() {
-        mTerminated = true;
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+        }
         if (isStarted()) {
             ArrayList<AnimatorListener> tmpListeners = null;
             if (mListeners != null) {
@@ -360,18 +401,13 @@
                     tmpListeners.get(i).onAnimationCancel(this);
                 }
             }
-            ArrayList<Animator> playingSet = new ArrayList<>(mPlayingSet);
+            ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet);
             int setSize = playingSet.size();
             for (int i = 0; i < setSize; i++) {
-                playingSet.get(i).cancel();
+                playingSet.get(i).mAnimation.cancel();
             }
-            if (tmpListeners != null) {
-                int size = tmpListeners.size();
-                for (int i = 0; i < size; i++) {
-                    tmpListeners.get(i).onAnimationEnd(this);
-                }
-            }
-            mStarted = false;
+            mPlayingSet.clear();
+            endAnimation();
         }
     }
 
@@ -383,50 +419,40 @@
      */
     @Override
     public void end() {
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+        }
         if (mShouldIgnoreEndWithoutStart && !isStarted()) {
             return;
         }
-        mTerminated = true;
         if (isStarted()) {
-            endRemainingAnimations();
-        }
-        if (mListeners != null) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            for (int i = 0; i < tmpListeners.size(); i++) {
-                tmpListeners.get(i).onAnimationEnd(this);
-            }
-        }
-        mStarted = false;
-    }
-
-    /**
-     * Iterate the animations that haven't finished or haven't started, and end them.
-     */
-    private void endRemainingAnimations() {
-        ArrayList<Animator> remainingList = new ArrayList<Animator>(mNodes.size());
-        remainingList.addAll(mPlayingSet);
-
-        int index = 0;
-        while (index < remainingList.size()) {
-            Animator anim = remainingList.get(index);
-            anim.end();
-            index++;
-            Node node = mNodeMap.get(anim);
-            if (node.mChildNodes != null) {
-                int childSize = node.mChildNodes.size();
-                for (int i = 0; i < childSize; i++) {
-                    Node child = node.mChildNodes.get(i);
-                    if (child.mLatestParent != node) {
-                        continue;
+            // Iterate the animations that haven't finished or haven't started, and end them.
+            if (mReversing) {
+                // Between start() and first frame, mLastEventId would be unset (i.e. -1)
+                mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId;
+                for (int j = mLastEventId - 1; j >= 0; j--) {
+                    AnimationEvent event = mEvents.get(j);
+                    if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                        event.mNode.mAnimation.reverse();
+                    } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                        event.mNode.mAnimation.end();
                     }
-                    remainingList.add(child.mAnimation);
+                }
+            } else {
+                for (int j = mLastEventId + 1; j < mEvents.size(); j++) {
+                    AnimationEvent event = mEvents.get(j);
+                    if (event.mEvent == AnimationEvent.ANIMATION_START) {
+                        event.mNode.mAnimation.start();
+                    } else if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                        event.mNode.mAnimation.end();
+                    }
                 }
             }
+            mPlayingSet.clear();
         }
+        endAnimation();
     }
 
-
     /**
      * Returns true if any of the child animations of this AnimatorSet have been started and have
      * not yet ended. Child animations will not be started until the AnimatorSet has gone past
@@ -437,14 +463,12 @@
      */
     @Override
     public boolean isRunning() {
-        int size = mNodes.size();
-        for (int i = 0; i < size; i++) {
-            Node node = mNodes.get(i);
-            if (node != mRootNode && node.mAnimation.isStarted()) {
-                return true;
-            }
+        if (mStartDelay > 0) {
+            return mStarted && !mDelayAnim.isRunning();
+        } else {
+            // No start delay, animation should start right away
+            return mStarted;
         }
-        return false;
     }
 
     @Override
@@ -482,9 +506,6 @@
             return;
         }
         mStartDelay = startDelay;
-        if (mStartDelay > 0) {
-            mReversible = false;
-        }
         if (!mDependencyDirty) {
             // Dependency graph already constructed, update all the nodes' start/end time
             int size = mNodes.size();
@@ -562,40 +583,26 @@
 
     @Override
     public void pause() {
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+        }
         boolean previouslyPaused = mPaused;
         super.pause();
         if (!previouslyPaused && mPaused) {
-            if (mDelayAnim.isStarted()) {
-                // If delay hasn't passed, pause the start delay animator.
-                mDelayAnim.pause();
-            } else {
-                int size = mNodes.size();
-                for (int i = 0; i < size; i++) {
-                    Node node = mNodes.get(i);
-                    if (node != mRootNode) {
-                        node.mAnimation.pause();
-                    }
-                }
-            }
+            mPauseTime = -1;
         }
     }
 
     @Override
     public void resume() {
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+        }
         boolean previouslyPaused = mPaused;
         super.resume();
         if (previouslyPaused && !mPaused) {
-            if (mDelayAnim.isStarted()) {
-                // If start delay hasn't passed, resume the previously paused start delay animator
-                mDelayAnim.resume();
-            } else {
-                int size = mNodes.size();
-                for (int i = 0; i < size; i++) {
-                    Node node = mNodes.get(i);
-                    if (node != mRootNode) {
-                        node.mAnimation.resume();
-                    }
-                }
+            if (mPauseTime >= 0) {
+                addAnimationCallback(0);
             }
         }
     }
@@ -610,9 +617,33 @@
     @SuppressWarnings("unchecked")
     @Override
     public void start() {
-        mTerminated = false;
+        start(false, true);
+    }
+
+    @Override
+    void startWithoutPulsing(boolean inReverse) {
+        start(inReverse, false);
+    }
+
+    private void initAnimation() {
+        if (mInterpolator != null) {
+            for (int i = 0; i < mNodes.size(); i++) {
+                Node node = mNodes.get(i);
+                node.mAnimation.setInterpolator(mInterpolator);
+            }
+        }
+        updateAnimatorsDuration();
+        createDependencyGraph();
+    }
+
+    private void start(boolean inReverse, boolean selfPulse) {
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+        }
         mStarted = true;
+        mSelfPulse = selfPulse;
         mPaused = false;
+        mPauseTime = -1;
 
         int size = mNodes.size();
         for (int i = 0; i < size; i++) {
@@ -621,26 +652,17 @@
             node.mAnimation.setAllowRunningAsynchronously(false);
         }
 
-        if (mInterpolator != null) {
-            for (int i = 0; i < size; i++) {
-                Node node = mNodes.get(i);
-                node.mAnimation.setInterpolator(mInterpolator);
-            }
+        initAnimation();
+        if (inReverse && !canReverse()) {
+            throw new UnsupportedOperationException("Cannot reverse infinite AnimatorSet");
         }
 
-        updateAnimatorsDuration();
-        createDependencyGraph();
+        mReversing = inReverse;
 
         // Now that all dependencies are set up, start the animations that should be started.
-        boolean setIsEmpty = false;
-        if (mStartDelay > 0) {
-            start(mRootNode);
-        } else if (isEmptySet(this)) {
-            // Set is empty or contains only empty animator sets. Skip to end in this case.
-            setIsEmpty = true;
-        } else {
-            // No delay, but there are other animators in the set
-            onChildAnimatorEnded(mDelayAnim);
+        boolean setIsEmpty = isEmptySet(this);
+        if (!setIsEmpty) {
+            startAnimation();
         }
 
         if (mListeners != null) {
@@ -648,12 +670,12 @@
                     (ArrayList<AnimatorListener>) mListeners.clone();
             int numListeners = tmpListeners.size();
             for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this);
+                tmpListeners.get(i).onAnimationStart(this, inReverse);
             }
         }
         if (setIsEmpty) {
             // In the case of empty AnimatorSet, we will trigger the onAnimationEnd() right away.
-            onChildAnimatorEnded(mDelayAnim);
+            end();
         }
     }
 
@@ -690,11 +712,419 @@
         mDelayAnim.setDuration(mStartDelay);
     }
 
-    void start(final Node node) {
-        final Animator anim = node.mAnimation;
-        mPlayingSet.add(anim);
-        anim.addListener(mSetListener);
-        anim.start();
+    @Override
+    void skipToEndValue(boolean inReverse) {
+        if (!isInitialized()) {
+            throw new UnsupportedOperationException("Children must be initialized.");
+        }
+
+        // This makes sure the animation events are sorted an up to date.
+        initAnimation();
+
+        // Calling skip to the end in the sequence that they would be called in a forward/reverse
+        // run, such that the sequential animations modifying the same property would have
+        // the right value in the end.
+        if (inReverse) {
+            for (int i = mEvents.size() - 1; i >= 0; i--) {
+                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                    mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
+                }
+            }
+        } else {
+            for (int i = 0; i < mEvents.size(); i++) {
+                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
+                    mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
+                }
+            }
+        }
+    }
+
+    /**
+     * Internal only.
+     *
+     * This method sets the animation values based on the play time. It also fast forward or
+     * backward all the child animations progress accordingly.
+     *
+     * This method is also responsible for calling
+     * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
+     * as needed, based on the last play time and current play time.
+     */
+    @Override
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+        if (currentPlayTime < 0 || lastPlayTime < 0) {
+            throw new UnsupportedOperationException("Error: Play time should never be negative.");
+        }
+        // TODO: take into account repeat counts and repeat callback when repeat is implemented.
+        // Clamp currentPlayTime and lastPlayTime
+
+        // TODO: Make this more efficient
+
+        // Convert the play times to the forward direction.
+        if (inReverse) {
+            if (getTotalDuration() == DURATION_INFINITE) {
+                throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
+                        + " duration");
+            }
+            long duration = getTotalDuration() - mStartDelay;
+            currentPlayTime = Math.min(currentPlayTime, duration);
+            currentPlayTime = duration - currentPlayTime;
+            lastPlayTime = duration - lastPlayTime;
+            inReverse = false;
+        }
+        // Skip all values to start, and iterate mEvents to get animations to the right fraction.
+        skipToStartValue(false);
+
+        ArrayList<Node> unfinishedNodes = new ArrayList<>();
+        // Assumes forward playing from here on.
+        for (int i = 0; i < mEvents.size(); i++) {
+            AnimationEvent event = mEvents.get(i);
+            if (event.getTime() > currentPlayTime) {
+                break;
+            }
+
+            // This animation started prior to the current play time, and won't finish before the
+            // play time, add to the unfinished list.
+            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                if (event.mNode.mEndTime == DURATION_INFINITE
+                        || event.mNode.mEndTime > currentPlayTime) {
+                    unfinishedNodes.add(event.mNode);
+                }
+            }
+            // For animations that do finish before the play time, end them in the sequence that
+            // they would in a normal run.
+            if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                // Skip to the end of the animation.
+                event.mNode.mAnimation.skipToEndValue(false);
+            }
+        }
+
+        // Seek unfinished animation to the right time.
+        for (int i = 0; i < unfinishedNodes.size(); i++) {
+            Node node = unfinishedNodes.get(i);
+            long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
+            node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
+        }
+    }
+
+    @Override
+    boolean isInitialized() {
+        if (mChildrenInitialized) {
+            return true;
+        }
+
+        boolean allInitialized = true;
+        for (int i = 0; i < mNodes.size(); i++) {
+            if (!mNodes.get(i).mAnimation.isInitialized()) {
+                allInitialized = false;
+                break;
+            }
+        }
+        mChildrenInitialized = allInitialized;
+        return mChildrenInitialized;
+    }
+
+    private void skipToStartValue(boolean inReverse) {
+        skipToEndValue(!inReverse);
+    }
+
+    /**
+     * Sets the position of the animation to the specified point in time. This time should
+     * be between 0 and the total duration of the animation, including any repetition. If
+     * the animation has not yet been started, then it will not advance forward after it is
+     * set to this time; it will simply set the time to this value and perform any appropriate
+     * actions based on that time. If the animation is already running, then setCurrentPlayTime()
+     * will set the current playing time to this value and continue playing from that point.
+     *
+     * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+     *                 Unless the animation is reversing, the playtime is considered the time since
+     *                 the end of the start delay of the AnimatorSet in a forward playing direction.
+     *
+     */
+    public void setCurrentPlayTime(long playTime) {
+        if (mReversing && getTotalDuration() == DURATION_INFINITE) {
+            // Should never get here
+            throw new UnsupportedOperationException("Error: Cannot seek in reverse in an infinite"
+                    + " AnimatorSet");
+        }
+
+        if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
+                || playTime < 0) {
+            throw new UnsupportedOperationException("Error: Play time should always be in between"
+                    + "0 and duration.");
+        }
+
+        initAnimation();
+
+        if (!isStarted()) {
+            if (mReversing) {
+                throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+                        + " should not be set when AnimatorSet is not started.");
+            }
+            if (!mSeekState.isActive()) {
+                findLatestEventIdForTime(0);
+                // Set all the values to start values.
+                initChildren();
+                skipToStartValue(mReversing);
+                mSeekState.setPlayTime(0, mReversing);
+            }
+            animateBasedOnPlayTime(playTime, 0, mReversing);
+            mSeekState.setPlayTime(playTime, mReversing);
+        } else {
+            // If the animation is running, just set the seek time and wait until the next frame
+            // (i.e. doAnimationFrame(...)) to advance the animation.
+            mSeekState.setPlayTime(playTime, mReversing);
+        }
+    }
+
+    private void initChildren() {
+        if (!isInitialized()) {
+            mChildrenInitialized = true;
+            // Forcefully initialize all children based on their end time, so that if the start
+            // value of a child is dependent on a previous animation, the animation will be
+            // initialized after the the previous animations have been advanced to the end.
+            skipToEndValue(false);
+        }
+    }
+
+    /**
+     * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
+     *                  base.
+     * @return
+     * @hide
+     */
+    @Override
+    public boolean doAnimationFrame(long frameTime) {
+        if (mLastFrameTime < 0) {
+            mFirstFrame = mLastFrameTime = frameTime;
+        }
+
+        // Handle pause/resume
+        if (mPaused) {
+            // Note: Child animations don't receive pause events. Since it's never a contract that
+            // the child animators will be paused when set is paused, this is unlikely to be an
+            // issue.
+            mPauseTime = frameTime;
+            removeAnimationCallback();
+            return false;
+        } else if (mPauseTime > 0) {
+                // Offset by the duration that the animation was paused
+            mFirstFrame += (frameTime - mPauseTime);
+            mPauseTime = -1;
+        }
+
+        // Continue at seeked position
+        if (mSeekState.isActive()) {
+            mSeekState.updateSeekDirection(mReversing);
+            mFirstFrame = frameTime - mSeekState.getPlayTime() - mStartDelay;
+            mSeekState.reset();
+        }
+
+        // This playTime includes the start delay.
+        long playTime = frameTime - mFirstFrame;
+
+        // 1. Pulse the animators that will start or end in this frame
+        // 2. Pulse the animators that will finish in a later frame
+        int latestId = findLatestEventIdForTime(playTime);
+        int startId = mLastEventId;
+
+        handleAnimationEvents(startId, latestId, playTime);
+
+        mLastEventId = latestId;
+
+        // Pump a frame to the on-going animators
+        for (int i = 0; i < mPlayingSet.size(); i++) {
+            Node node = mPlayingSet.get(i);
+            if (!node.mEnded) {
+                node.mEnded = node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node));
+            }
+        }
+
+        // Remove all the finished anims
+        for (int i = mPlayingSet.size() - 1; i >= 0; i--) {
+            if (mPlayingSet.get(i).mEnded) {
+                mPlayingSet.remove(i);
+            }
+        }
+
+        mLastFrameTime = frameTime;
+        if (mPlayingSet.isEmpty()) {
+            boolean finished;
+            if (mReversing) {
+                // Make sure there's no more END event before current event id and after start delay
+                finished = mLastEventId <= 3;
+            } else {
+                // Make sure there's no more START event before current event id:
+                finished = (mLastEventId == mEvents.size() - 1);
+            }
+            if (finished) {
+                endAnimation();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * When playing forward, we call start() at the animation's scheduled start time, and make sure
+     * to pump a frame at the animation's scheduled end time.
+     *
+     * When playing in reverse, we should reverse the animation when we hit animation's end event,
+     * and expect the animation to end at the its delay ended event, rather than start event.
+     */
+    private void handleAnimationEvents(int startId, int latestId, long playTime) {
+        if (mReversing) {
+            startId = startId == -1 ? mEvents.size() : startId;
+            for (int i = startId - 1; i >= latestId; i--) {
+                AnimationEvent event = mEvents.get(i);
+                Node node = event.mNode;
+                if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                    mPlayingSet.add(event.mNode);
+                    node.mAnimation.startWithoutPulsing(true);
+                    node.mAnimation.doAnimationFrame(0);
+                } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
+                    // end event:
+                    node.mEnded =
+                            node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node));
+                }
+            }
+        } else {
+            for (int i = startId + 1; i <= latestId; i++) {
+                AnimationEvent event = mEvents.get(i);
+                Node node = event.mNode;
+                if (event.mEvent == AnimationEvent.ANIMATION_START) {
+                    mPlayingSet.add(event.mNode);
+                    node.mAnimation.startWithoutPulsing(false);
+                    node.mAnimation.doAnimationFrame(0);
+                } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
+                    // start event:
+                    node.mEnded =
+                            node.mAnimation.doAnimationFrame(getPlayTimeForNode(playTime, node));
+                }
+            }
+        }
+    }
+
+    private long getPlayTimeForNode(long overallPlayTime, Node node) {
+        return getPlayTimeForNode(overallPlayTime, node, mReversing);
+    }
+
+    private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
+        if (inReverse) {
+            overallPlayTime = getTotalDuration() - overallPlayTime;
+            return node.mEndTime - overallPlayTime;
+        } else {
+            return overallPlayTime - node.mStartTime;
+        }
+    }
+
+    private void startAnimation() {
+        // Register animation callback
+        addAnimationCallback(mStartDelay);
+
+        if (mSeekState.getPlayTimeNormalized() == 0 && mReversing) {
+            // Maintain old behavior, if seeked to 0 then call reverse, we'll treat the case
+            // the same as no seeking at all.
+            mSeekState.reset();
+        }
+        // Set the child animators to the right end:
+        if (mShouldResetValuesAtStart) {
+            if (mReversing || isInitialized()) {
+                skipToEndValue(!mReversing);
+            } else {
+                // If not all children are initialized and play direction is forward
+                for (int i = mEvents.size() - 1; i >= 0; i--) {
+                    if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                        Animator anim = mEvents.get(i).mNode.mAnimation;
+                        // Only reset the animations that have been initialized to start value,
+                        // so that if they are defined without a start value, they will get the
+                        // values set at the right time (i.e. the next animation run)
+                        if (anim.isInitialized()) {
+                            anim.skipToEndValue(true);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
+            long playTime;
+            // If no delay, we need to call start on the first animations to be consistent with old
+            // behavior.
+            if (mSeekState.isActive()) {
+                mSeekState.updateSeekDirection(mReversing);
+                playTime = mSeekState.getPlayTime();
+            } else {
+                playTime = 0;
+            }
+            int toId = findLatestEventIdForTime(playTime);
+            handleAnimationEvents(-1, toId, playTime);
+            mLastEventId = toId;
+        }
+    }
+
+    private int findLatestEventIdForTime(long currentPlayTime) {
+        int size = mEvents.size();
+        int latestId = mLastEventId;
+        // Call start on the first animations now to be consistent with the old behavior
+        if (mReversing) {
+            currentPlayTime = getTotalDuration() - currentPlayTime;
+            mLastEventId = mLastEventId == -1 ? size : mLastEventId;
+            for (int j = mLastEventId - 1; j >= 0; j--) {
+                AnimationEvent event = mEvents.get(j);
+                if (event.getTime() >= currentPlayTime) {
+                    latestId = j;
+                }
+            }
+        } else {
+            for (int i = mLastEventId + 1; i < size; i++) {
+                AnimationEvent event = mEvents.get(i);
+                if (event.getTime() <= currentPlayTime) {
+                    latestId = i;
+                }
+            }
+        }
+        return latestId;
+    }
+
+    private void endAnimation() {
+        mStarted = false;
+        mLastFrameTime = -1;
+        mFirstFrame = -1;
+        mLastEventId = -1;
+        mPaused = false;
+        mPauseTime = -1;
+        mSeekState.reset();
+        mPlayingSet.clear();
+
+        // No longer receive callbacks
+        removeAnimationCallback();
+        // Call end listener
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this, mReversing);
+            }
+        }
+        mSelfPulse = true;
+        mReversing = false;
+    }
+
+    private void removeAnimationCallback() {
+        if (!mSelfPulse) {
+            return;
+        }
+        AnimationHandler handler = AnimationHandler.getInstance();
+        handler.removeCallback(this);
+    }
+
+    private void addAnimationCallback(long delay) {
+        if (!mSelfPulse) {
+            return;
+        }
+        AnimationHandler handler = AnimationHandler.getInstance();
+        handler.addAnimationFrameCallback(this, delay);
     }
 
     @Override
@@ -709,13 +1139,20 @@
          * and will populate any appropriate lists, when it is started.
          */
         final int nodeCount = mNodes.size();
-        anim.mTerminated = false;
         anim.mStarted = false;
-        anim.mPlayingSet = new ArrayList<Animator>();
+        anim.mLastFrameTime = -1;
+        anim.mFirstFrame = -1;
+        anim.mLastEventId = -1;
+        anim.mPaused = false;
+        anim.mPauseTime = -1;
+        anim.mSeekState = new SeekState();
+        anim.mSelfPulse = true;
+        anim.mPlayingSet = new ArrayList<Node>();
         anim.mNodeMap = new ArrayMap<Animator, Node>();
         anim.mNodes = new ArrayList<Node>(nodeCount);
-        anim.mReversible = mReversible;
-        anim.mSetListener = new AnimatorSetListener(anim);
+        anim.mEvents = new ArrayList<AnimationEvent>();
+        anim.mReversing = false;
+        anim.mDependencyDirty = true;
 
         // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
         // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
@@ -727,17 +1164,6 @@
             node.mTmpClone = nodeClone;
             anim.mNodes.add(nodeClone);
             anim.mNodeMap.put(nodeClone.mAnimation, nodeClone);
-
-            // clear out any listeners that were set up by the AnimatorSet
-            final ArrayList<AnimatorListener> cloneListeners = nodeClone.mAnimation.getListeners();
-            if (cloneListeners != null) {
-                for (int i = cloneListeners.size() - 1; i >= 0; i--) {
-                    final AnimatorListener listener = cloneListeners.get(i);
-                    if (listener instanceof AnimatorSetListener) {
-                        cloneListeners.remove(i);
-                    }
-                }
-            }
         }
 
         anim.mRootNode = mRootNode.mTmpClone;
@@ -771,89 +1197,6 @@
     }
 
 
-    private static class AnimatorSetListener implements AnimatorListener {
-
-        private AnimatorSet mAnimatorSet;
-
-        AnimatorSetListener(AnimatorSet animatorSet) {
-            mAnimatorSet = animatorSet;
-        }
-
-        public void onAnimationCancel(Animator animation) {
-
-            if (!mAnimatorSet.mTerminated) {
-                // Listeners are already notified of the AnimatorSet canceling in cancel().
-                // The logic below only kicks in when animations end normally
-                if (mAnimatorSet.mPlayingSet.size() == 0) {
-                    ArrayList<AnimatorListener> listeners = mAnimatorSet.mListeners;
-                    if (listeners != null) {
-                        int numListeners = listeners.size();
-                        for (int i = 0; i < numListeners; ++i) {
-                            listeners.get(i).onAnimationCancel(mAnimatorSet);
-                        }
-                    }
-                }
-            }
-        }
-
-        @SuppressWarnings("unchecked")
-        public void onAnimationEnd(Animator animation) {
-            animation.removeListener(this);
-            mAnimatorSet.mPlayingSet.remove(animation);
-            mAnimatorSet.onChildAnimatorEnded(animation);
-        }
-
-        // Nothing to do
-        public void onAnimationRepeat(Animator animation) {
-        }
-
-        // Nothing to do
-        public void onAnimationStart(Animator animation) {
-        }
-
-    }
-
-    private void onChildAnimatorEnded(Animator animation) {
-        Node animNode = mNodeMap.get(animation);
-        animNode.mEnded = true;
-
-        if (!mTerminated) {
-            List<Node> children = animNode.mChildNodes;
-            // Start children animations, if any.
-            int childrenSize = children == null ? 0 : children.size();
-            for (int i = 0; i < childrenSize; i++) {
-                if (children.get(i).mLatestParent == animNode) {
-                    start(children.get(i));
-                }
-            }
-            // Listeners are already notified of the AnimatorSet ending in cancel() or
-            // end(); the logic below only kicks in when animations end normally
-            boolean allDone = true;
-            // Traverse the tree and find if there's any unfinished node
-            int size = mNodes.size();
-            for (int i = 0; i < size; i++) {
-                if (!mNodes.get(i).mEnded) {
-                    allDone = false;
-                    break;
-                }
-            }
-            if (allDone) {
-                mStarted = false;
-                mPaused = false;
-                // If this was the last child animation to end, then notify listeners that this
-                // AnimatorSet has ended
-                if (mListeners != null) {
-                    ArrayList<AnimatorListener> tmpListeners =
-                            (ArrayList<AnimatorListener>) mListeners.clone();
-                    int numListeners = tmpListeners.size();
-                    for (int i = 0; i < numListeners; ++i) {
-                        tmpListeners.get(i).onAnimationEnd(this);
-                    }
-                }
-            }
-        }
-    }
-
     /**
      * AnimatorSet is only reversible when the set contains no sequential animation, and no child
      * animators have a start delay.
@@ -861,32 +1204,21 @@
      */
     @Override
     public boolean canReverse() {
-        if (!mReversible)  {
-            return false;
-        }
-        // Loop to make sure all the Nodes can reverse.
-        int size = mNodes.size();
-        for (int i = 0; i < size; i++) {
-            Node node = mNodes.get(i);
-            if (!node.mAnimation.canReverse() || node.mAnimation.getStartDelay() > 0) {
-                return false;
-            }
-        }
-        return true;
+        return getTotalDuration() != DURATION_INFINITE;
     }
 
     /**
-     * @hide
+     * Plays the AnimatorSet in reverse. If the animation has been seeked to a specific play time
+     * using {@link #setCurrentPlayTime(long)}, it will play backwards from the point seeked when
+     * reverse was called. Otherwise, then it will start from the end and play backwards. This
+     * behavior is only set for the current animation; future playing of the animation will use the
+     * default behavior of playing forward.
+     * <p>
+     * Note: reverse is not supported for infinite AnimatorSet.
      */
     @Override
     public void reverse() {
-        if (canReverse()) {
-            int size = mNodes.size();
-            for (int i = 0; i < size; i++) {
-                Node node = mNodes.get(i);
-                node.mAnimation.reverse();
-            }
-        }
+        start(true, true);
     }
 
     @Override
@@ -993,18 +1325,61 @@
         mRootNode.mEndTime = mDelayAnim.getDuration();
         updatePlayTime(mRootNode, visited);
 
-        long maxEndTime = 0;
-        for (int i = 0; i < size; i++) {
+        sortAnimationEvents();
+        mTotalDuration = mEvents.get(mEvents.size() - 1).getTime();
+    }
+
+    private void sortAnimationEvents() {
+        // Sort the list of events in ascending order of their time
+        // Create the list including the delay animation.
+        mEvents.clear();
+        for (int i = 0; i < mNodes.size(); i++) {
             Node node = mNodes.get(i);
-            node.mTotalDuration = node.mAnimation.getTotalDuration();
-            if (node.mEndTime == DURATION_INFINITE) {
-                maxEndTime = DURATION_INFINITE;
-                break;
-            } else {
-                maxEndTime = node.mEndTime > maxEndTime ? node.mEndTime : maxEndTime;
-            }
+            mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_START));
+            mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_DELAY_ENDED));
+            mEvents.add(new AnimationEvent(node, AnimationEvent.ANIMATION_END));
         }
-        mTotalDuration = maxEndTime;
+        mEvents.sort(new Comparator<AnimationEvent>() {
+            @Override
+            public int compare(AnimationEvent e1, AnimationEvent e2) {
+                long t1 = e1.getTime();
+                long t2 = e2.getTime();
+                if (t1 == t2) {
+                    if (e1.mNode == e2.mNode) {
+                        // For the same animation, start event has to happen before end.
+                        return e1.mEvent - e2.mEvent;
+                    }
+                    // For different animation, end events need to happen before start, to ensure
+                    // sequential animations finish the previous one before starting the next one.
+                    return e2.mEvent - e1.mEvent;
+                }
+                if (t2 == DURATION_INFINITE) {
+                    return -1;
+                }
+                if (t1 == DURATION_INFINITE) {
+                    return 1;
+                }
+                // When neither event happens at INFINITE time:
+                return (int) (t1 - t2);
+            }
+        });
+
+        if (mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_START
+                || mEvents.get(mEvents.size() - 1).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+            throw new UnsupportedOperationException(
+                    "Something went wrong, the last event is not an end event");
+        }
+        if (mEvents.get(1).mEvent != AnimationEvent.ANIMATION_DELAY_ENDED
+                || mEvents.get(1).mNode != mRootNode) {
+            throw new UnsupportedOperationException(
+                    "Sorting went bad, the root node's start delay end event should always be at"
+                            + " index 1");
+        }
+        if (mEvents.get(2).mEvent != AnimationEvent.ANIMATION_END
+                || mEvents.get(2).mNode != mRootNode) {
+            throw new UnsupportedOperationException(
+                    "Sorting went bad, the start delay end event should always be at index 2");
+        }
     }
 
     /**
@@ -1235,6 +1610,95 @@
     }
 
     /**
+     * This class is a wrapper around a node and an event for the animation corresponding to the
+     * node. The 3 types of events represent the start of an animation, the end of a start delay of
+     * an animation, and the end of an animation. When playing forward (i.e. in the non-reverse
+     * direction), start event marks when start() should be called, and end event corresponds to
+     * when the animation should finish. When playing in reverse, start delay will not be a part
+     * of the animation. Therefore, reverse() is called at the end event, and animation should end
+     * at the delay ended event.
+     */
+    private static class AnimationEvent {
+        static final int ANIMATION_START = 0;
+        static final int ANIMATION_DELAY_ENDED = 1;
+        static final int ANIMATION_END = 2;
+        final Node mNode;
+        final int mEvent;
+
+        AnimationEvent(Node node, int event) {
+            mNode = node;
+            mEvent = event;
+        }
+
+        long getTime() {
+            if (mEvent == ANIMATION_START) {
+                return mNode.mStartTime;
+            } else if (mEvent == ANIMATION_DELAY_ENDED) {
+                return mNode.mStartTime + mNode.mAnimation.getStartDelay();
+            } else {
+                return mNode.mEndTime;
+            }
+        }
+
+        public String toString() {
+            String eventStr = mEvent == ANIMATION_START ? "start" : (
+                    mEvent == ANIMATION_DELAY_ENDED ? "delay ended" : "end");
+            return eventStr + " " + mNode.mAnimation.toString();
+        }
+    }
+
+    private class SeekState {
+        private long mPlayTime = -1;
+        private boolean mSeekingInReverse = false;
+        void reset() {
+            mPlayTime = -1;
+            mSeekingInReverse = false;
+        }
+
+        void setPlayTime(long playTime, boolean inReverse) {
+            // TODO: This can be simplified.
+
+            // Clamp the play time
+            if (getTotalDuration() != DURATION_INFINITE) {
+                mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+            }
+            mPlayTime = Math.max(0, mPlayTime);
+            mSeekingInReverse = inReverse;
+        }
+
+        void updateSeekDirection(boolean inReverse) {
+            // Change seek direction without changing the overall fraction
+            if (inReverse && getTotalDuration() == DURATION_INFINITE) {
+                throw new UnsupportedOperationException("Error: Cannot reverse infinite animator"
+                        + " set");
+            }
+            if (mPlayTime >= 0) {
+                if (inReverse != mSeekingInReverse) {
+                    mPlayTime = getTotalDuration() - mStartDelay - mPlayTime;
+                }
+            }
+        }
+
+        long getPlayTime() {
+            return mPlayTime;
+        }
+
+        /**
+         * Returns the playtime assuming the animation is forward playing
+         */
+        long getPlayTimeNormalized() {
+            if (mReversing) {
+                return getTotalDuration() - mStartDelay - mPlayTime;
+            }
+            return mPlayTime;
+        }
+
+        boolean isActive() {
+            return mPlayTime != -1;
+        }
+    }
+
+    /**
      * The <code>Builder</code> object is a utility class to facilitate adding animations to a
      * <code>AnimatorSet</code> along with the relationships between the various animations. The
      * intention of the <code>Builder</code> methods, along with the {@link
@@ -1328,7 +1792,6 @@
          * {@link AnimatorSet#play(Animator)} method ends.
          */
         public Builder before(Animator anim) {
-            mReversible = false;
             Node node = getNodeForAnimation(anim);
             mCurrentNode.addChild(node);
             return this;
@@ -1343,7 +1806,6 @@
          * {@link AnimatorSet#play(Animator)} method to play.
          */
         public Builder after(Animator anim) {
-            mReversible = false;
             Node node = getNodeForAnimation(anim);
             mCurrentNode.addParent(node);
             return this;
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 4707bed..1e1f155 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -992,6 +992,11 @@
     }
 
     @Override
+    boolean isInitialized() {
+        return mInitialized;
+    }
+
+    @Override
     public ObjectAnimator clone() {
         final ObjectAnimator anim = (ObjectAnimator) super.clone();
         return anim;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f0fc8af..470523f 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -24,6 +24,7 @@
 import android.util.AndroidRuntimeException;
 import android.util.Log;
 import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.LinearInterpolator;
 
@@ -67,7 +68,7 @@
  * </div>
  */
 @SuppressWarnings("unchecked")
-public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
+public class ValueAnimator extends Animator {
     private static final String TAG = "ValueAnimator";
     private static final boolean DEBUG = false;
 
@@ -90,7 +91,7 @@
      *
      * Whenever mStartTime is set, you must also update mStartTimeCommitted.
      */
-    long mStartTime;
+    long mStartTime = -1;
 
     /**
      * When true, the start time has been firmly committed as a chosen reference point in
@@ -152,7 +153,13 @@
     /**
      * Tracks the time (in milliseconds) when the last frame arrived.
      */
-    private long mLastFrameTime = 0;
+    private long mLastFrameTime = -1;
+
+    /**
+     * Tracks the time (in milliseconds) when the first frame arrived. Note the frame may arrive
+     * during the start delay.
+     */
+    private long mFirstFrameTime = -1;
 
     /**
      * Additional playing state to indicate whether an animator has been start()'d. There is
@@ -212,6 +219,12 @@
     private int mRepeatMode = RESTART;
 
     /**
+     * Whether or not the animator should register for its own animation callback to receive
+     * animation pulse.
+     */
+    private boolean mSelfPulse = true;
+
+    /**
      * The time interpolator to be used. The elapsed fraction of the animation will be passed
      * through this interpolator to calculate the interpolated fraction, which is then used to
      * calculate the animated values.
@@ -628,7 +641,7 @@
             mSeekFraction = fraction;
         }
         mOverallFraction = fraction;
-        final float currentIterationFraction = getCurrentIterationFraction(fraction);
+        final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
         animateValue(currentIterationFraction);
     }
 
@@ -654,11 +667,11 @@
      * should be played backwards. E.g. When the animation is played backwards in an iteration,
      * the fraction for that iteration will go from 1f to 0f.
      */
-    private float getCurrentIterationFraction(float fraction) {
+    private float getCurrentIterationFraction(float fraction, boolean inReverse) {
         fraction = clampFraction(fraction);
         int iteration = getCurrentIteration(fraction);
         float currentFraction = fraction - iteration;
-        return shouldPlayBackward(iteration) ? 1f - currentFraction : currentFraction;
+        return shouldPlayBackward(iteration, inReverse) ? 1f - currentFraction : currentFraction;
     }
 
     /**
@@ -682,18 +695,18 @@
      * whether the entire animation is being reversed, 2) repeat mode applied to the current
      * iteration.
      */
-    private boolean shouldPlayBackward(int iteration) {
+    private boolean shouldPlayBackward(int iteration, boolean inReverse) {
         if (iteration > 0 && mRepeatMode == REVERSE &&
                 (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
             // if we were seeked to some other iteration in a reversing animator,
             // figure out the correct direction to start playing based on the iteration
-            if (mReversing) {
+            if (inReverse) {
                 return (iteration % 2) == 0;
             } else {
                 return (iteration % 2) != 0;
             }
         } else {
-            return mReversing;
+            return inReverse;
         }
     }
 
@@ -965,7 +978,7 @@
                     (ArrayList<AnimatorListener>) mListeners.clone();
             int numListeners = tmpListeners.size();
             for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this);
+                tmpListeners.get(i).onAnimationStart(this, mReversing);
             }
         }
         mStartListenersCalled = true;
@@ -984,11 +997,12 @@
      *
      * @param playBackwards Whether the ValueAnimator should start playing in reverse.
      */
-    private void start(boolean playBackwards) {
+    private void start(boolean playBackwards, boolean selfPulse) {
         if (Looper.myLooper() == null) {
             throw new AndroidRuntimeException("Animators may only be run on Looper threads");
         }
         mReversing = playBackwards;
+        mSelfPulse = selfPulse;
         // Special case: reversing from seek-to-0 should act as if not seeked at all.
         if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
             if (mRepeatCount == INFINITE) {
@@ -1006,11 +1020,11 @@
         // Resets mLastFrameTime when start() is called, so that if the animation was running,
         // calling start() would put the animation in the
         // started-but-not-yet-reached-the-first-frame phase.
-        mLastFrameTime = 0;
-        AnimationHandler animationHandler = AnimationHandler.getInstance();
-        animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
+        mLastFrameTime = -1;
+        mFirstFrameTime = -1;
+        addAnimationCallback((long) (mStartDelay * sDurationScale));
 
-        if (mStartDelay == 0 || mSeekFraction >= 0) {
+        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
             // If there's no start delay, init the animation and notify start listeners right away
             // to be consistent with the previous behavior. Otherwise, postpone this until the first
             // frame after the start delay.
@@ -1026,9 +1040,13 @@
         }
     }
 
+    void startWithoutPulsing(boolean inReverse) {
+        start(inReverse, false);
+    }
+
     @Override
     public void start() {
-        start(false);
+        start(false, true);
     }
 
     @Override
@@ -1073,7 +1091,7 @@
         } else if (!mInitialized) {
             initAnimation();
         }
-        animateValue(shouldPlayBackward(mRepeatCount) ? 0f : 1f);
+        animateValue(shouldPlayBackward(mRepeatCount, mReversing) ? 0f : 1f);
         endAnimation();
     }
 
@@ -1086,8 +1104,7 @@
         if (mPaused && !mResumed) {
             mResumed = true;
             if (mPauseTime > 0) {
-                AnimationHandler handler = AnimationHandler.getInstance();
-                handler.addAnimationFrameCallback(this, 0);
+                addAnimationCallback(0);
             }
         }
         super.resume();
@@ -1133,7 +1150,7 @@
             mReversing = !mReversing;
             end();
         } else {
-            start(true);
+            start(true, true);
         }
     }
 
@@ -1153,8 +1170,7 @@
         if (mAnimationEndRequested) {
             return;
         }
-        AnimationHandler handler = AnimationHandler.getInstance();
-        handler.removeCallback(this);
+        removeAnimationCallback();
 
         mAnimationEndRequested = true;
         mPaused = false;
@@ -1166,16 +1182,18 @@
         mRunning = false;
         mStarted = false;
         mStartListenersCalled = false;
+        mLastFrameTime = -1;
+        mFirstFrameTime = -1;
         mReversing = false;
-        mLastFrameTime = 0;
         if (notify && mListeners != null) {
             ArrayList<AnimatorListener> tmpListeners =
                     (ArrayList<AnimatorListener>) mListeners.clone();
             int numListeners = tmpListeners.size();
             for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationEnd(this);
+                tmpListeners.get(i).onAnimationEnd(this, mReversing);
             }
         }
+        mReversing = false;
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                     System.identityHashCode(this));
@@ -1211,7 +1229,7 @@
      * is called (or after start delay if any), which may be before the animation loop starts.
      */
     private boolean isPulsingInternal() {
-        return mLastFrameTime > 0;
+        return mLastFrameTime >= 0;
     }
 
     /**
@@ -1276,24 +1294,116 @@
                 done = true;
             }
             mOverallFraction = clampFraction(fraction);
-            float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
+            float currentIterationFraction = getCurrentIterationFraction(
+                    mOverallFraction, mReversing);
             animateValue(currentIterationFraction);
         }
         return done;
     }
 
     /**
+     * Internal use only.
+     *
+     * This method does not modify any fields of the animation. It should be called when seeking
+     * in an AnimatorSet. When the last play time and current play time are of different repeat
+     * iterations,
+     * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)}
+     * will be called.
+     */
+    @Override
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+        if (currentPlayTime < 0 || lastPlayTime < 0) {
+            throw new UnsupportedOperationException("Error: Play time should never be negative.");
+        }
+
+        initAnimation();
+        // Check whether repeat callback is needed only when repeat count is non-zero
+        if (mRepeatCount > 0) {
+            int iteration = (int) (currentPlayTime / mDuration);
+            int lastIteration = (int) (lastPlayTime / mDuration);
+
+            // Clamp iteration to [0, mRepeatCount]
+            iteration = Math.min(iteration, mRepeatCount);
+            lastIteration = Math.min(lastIteration, mRepeatCount);
+
+            if (iteration != lastIteration) {
+                if (mListeners != null) {
+                    int numListeners = mListeners.size();
+                    for (int i = 0; i < numListeners; ++i) {
+                        mListeners.get(i).onAnimationRepeat(this);
+                    }
+                }
+            }
+        }
+
+        if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
+            skipToEndValue(inReverse);
+        } else {
+            // Find the current fraction:
+            float fraction = currentPlayTime / (float) mDuration;
+            fraction = getCurrentIterationFraction(fraction, inReverse);
+            animateValue(fraction);
+        }
+    }
+
+    /**
+     * Internal use only.
+     * Skips the animation value to end/start, depending on whether the play direction is forward
+     * or backward.
+     *
+     * @param inReverse whether the end value is based on a reverse direction. If yes, this is
+     *                  equivalent to skip to start value in a forward playing direction.
+     */
+    void skipToEndValue(boolean inReverse) {
+        initAnimation();
+        float endFraction = inReverse ? 0f : 1f;
+        if (mRepeatCount % 2 == 1 && mRepeatMode == REVERSE) {
+            // This would end on fraction = 0
+            endFraction = 0f;
+        }
+        animateValue(endFraction);
+    }
+
+    /**
      * Processes a frame of the animation, adjusting the start time if needed.
      *
      * @param frameTime The frame time.
      * @return true if the animation has ended.
      * @hide
      */
-    public final void doAnimationFrame(long frameTime) {
-        AnimationHandler handler = AnimationHandler.getInstance();
-        if (mLastFrameTime == 0) {
+    public final boolean doAnimationFrame(long frameTime) {
+        if (!mRunning && mStartTime < 0) {
+            // First frame during delay
+            mStartTime = frameTime + mStartDelay;
+        }
+
+        // Handle pause/resume
+        if (mPaused) {
+            mPauseTime = frameTime;
+            removeAnimationCallback();
+            return false;
+        } else if (mResumed) {
+            mResumed = false;
+            if (mPauseTime > 0) {
+                // Offset by the duration that the animation was paused
+                mStartTime += (frameTime - mPauseTime);
+            }
+        }
+
+        if (!mRunning) {
+            // If not running, that means the animation is in the start delay phase. In the case of
+            // reversing, we want to run start delay in the end.
+            if (mStartTime > frameTime) {
+                // During start delay
+                return false;
+            } else {
+                // Start delay has passed.
+                mRunning = true;
+            }
+        }
+
+        if (mLastFrameTime < 0) {
             // First frame
-            handler.addOneShotCommitCallback(this);
             if (mStartDelay > 0) {
                 startAnimation();
             }
@@ -1307,19 +1417,6 @@
             mStartTimeCommitted = false; // allow start time to be compensated for jank
         }
         mLastFrameTime = frameTime;
-        if (mPaused) {
-            mPauseTime = frameTime;
-            handler.removeCallback(this);
-            return;
-        } else if (mResumed) {
-            mResumed = false;
-            if (mPauseTime > 0) {
-                // Offset by the duration that the animation was paused
-                mStartTime += (frameTime - mPauseTime);
-                mStartTimeCommitted = false; // allow start time to be compensated for jank
-            }
-            handler.addOneShotCommitCallback(this);
-        }
         // The frame time might be before the start time during the first frame of
         // an animation.  The "current time" must always be on or after the start
         // time to avoid animating frames at negative time intervals.  In practice, this
@@ -1330,6 +1427,31 @@
         if (finished) {
             endAnimation();
         }
+        return finished;
+    }
+
+    private void addOneShotCommitCallback() {
+        if (!mSelfPulse) {
+            return;
+        }
+        AnimationHandler handler = AnimationHandler.getInstance();
+        handler.addOneShotCommitCallback(this);
+    }
+
+    private void removeAnimationCallback() {
+        if (!mSelfPulse) {
+            return;
+        }
+        AnimationHandler handler = AnimationHandler.getInstance();
+        handler.removeCallback(this);
+    }
+
+    private void addAnimationCallback(long delay) {
+        if (!mSelfPulse) {
+            return;
+        }
+        AnimationHandler handler = AnimationHandler.getInstance();
+        handler.addAnimationFrameCallback(this, delay);
     }
 
     /**
@@ -1384,11 +1506,12 @@
         anim.mPaused = false;
         anim.mResumed = false;
         anim.mStartListenersCalled = false;
-        anim.mStartTime = 0;
+        anim.mStartTime = -1;
         anim.mStartTimeCommitted = false;
         anim.mAnimationEndRequested = false;
-        anim.mPauseTime = 0;
-        anim.mLastFrameTime = 0;
+        anim.mPauseTime = -1;
+        anim.mLastFrameTime = -1;
+        anim.mFirstFrameTime = -1;
         anim.mOverallFraction = 0;
         anim.mCurrentFraction = 0;
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 556d7ad..df4c4af 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -78,8 +78,6 @@
 import android.service.autofill.IAutoFillAppCallback;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.transition.Scene;
@@ -792,8 +790,6 @@
 
     private VoiceInteractor mVoiceInteractor;
 
-    private TextAssistant mTextAssistant;
-
     private CharSequence mTitle;
     private int mTitleColor = 0;
 
@@ -1398,24 +1394,6 @@
     }
 
     /**
-     * Sets the default {@link TextAssistant} for {@link android.widget.TextView}s in this Activity.
-     */
-    public void setTextAssistant(TextAssistant textAssistant) {
-        mTextAssistant = textAssistant;
-    }
-
-    /**
-     * Returns the default {@link TextAssistant} for {@link android.widget.TextView}s
-     * in this Activity.
-     */
-    public TextAssistant getTextAssistant() {
-        if (mTextAssistant != null) {
-            return mTextAssistant;
-        }
-        return getSystemService(TextClassificationManager.class);
-    }
-
-    /**
      * This is called for activities that set launchMode to "singleTop" in
      * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
      * flag when calling {@link #startActivity}.  In either case, when the
@@ -3130,7 +3108,7 @@
      */
     @Override
     public void enterPictureInPictureModeIfPossible() {
-        if (mActivityInfo.resizeMode == ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE) {
+        if (mActivityInfo.supportsPictureInPicture()) {
             enterPictureInPictureMode();
         }
     }
@@ -6814,6 +6792,8 @@
         }
         mWindowManager = mWindow.getWindowManager();
         mCurrentConfig = config;
+
+        mWindow.setColorMode(info.colorMode);
     }
 
     /** @hide */
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7e9bf56..823436a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -698,6 +698,13 @@
         }
 
         /**
+         * Returns true if the input stack is affected by drag resizing.
+         */
+        public static boolean isStackAffectedByDragResizing(int stackId) {
+            return isStaticStack(stackId) && stackId != PINNED_STACK_ID;
+        }
+
+        /**
          * Returns true if the windows of tasks being moved to the target stack from the source
          * stack should be replaced, meaning that window manager will keep the old window around
          * until the new is ready.
@@ -1482,7 +1489,7 @@
          * True if the task can go in the docked stack.
          * @hide
          */
-        public boolean isDockable;
+        public boolean supportsSplitScreenMultiWindow;
 
         /**
          * The resize mode of the task. See {@link ActivityInfo#resizeMode}.
@@ -1533,7 +1540,7 @@
             } else {
                 dest.writeInt(0);
             }
-            dest.writeInt(isDockable ? 1 : 0);
+            dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
             dest.writeInt(resizeMode);
         }
 
@@ -1557,7 +1564,7 @@
             numActivities = source.readInt();
             bounds = source.readInt() > 0 ?
                     Rect.CREATOR.createFromParcel(source) : null;
-            isDockable = source.readInt() == 1;
+            supportsSplitScreenMultiWindow = source.readInt() == 1;
             resizeMode = source.readInt();
         }
 
@@ -1745,7 +1752,7 @@
          * True if the task can go in the docked stack.
          * @hide
          */
-        public boolean isDockable;
+        public boolean supportsSplitScreenMultiWindow;
 
         /**
          * The resize mode of the task. See {@link ActivityInfo#resizeMode}.
@@ -1775,7 +1782,7 @@
                     Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
             dest.writeInt(numActivities);
             dest.writeInt(numRunning);
-            dest.writeInt(isDockable ? 1 : 0);
+            dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
             dest.writeInt(resizeMode);
         }
 
@@ -1792,7 +1799,7 @@
             description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
             numActivities = source.readInt();
             numRunning = source.readInt();
-            isDockable = source.readInt() != 0;
+            supportsSplitScreenMultiWindow = source.readInt() != 0;
             resizeMode = source.readInt();
         }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d814ddc..34eaa0b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2616,9 +2616,10 @@
                     r.activityInfo.targetActivity);
         }
 
+        ContextImpl appContext = createBaseContextForActivity(r);
         Activity activity = null;
         try {
-            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
+            java.lang.ClassLoader cl = appContext.getClassLoader();
             activity = mInstrumentation.newActivity(
                     cl, component.getClassName(), r.intent);
             StrictMode.incrementExpectedActivityCount(activity.getClass());
@@ -2647,7 +2648,6 @@
                     + ", dir=" + r.packageInfo.getAppDir());
 
             if (activity != null) {
-                Context appContext = createBaseContextForActivity(r, activity);
                 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                 Configuration config = new Configuration(mCompatConfiguration);
                 if (r.overrideConfig != null) {
@@ -2661,6 +2661,7 @@
                     r.mPendingRemoveWindow = null;
                     r.mPendingRemoveWindowManager = null;
                 }
+                appContext.setOuterContext(activity);
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstances, config,
@@ -2736,8 +2737,8 @@
         return activity;
     }
 
-    private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
-        int displayId = Display.DEFAULT_DISPLAY;
+    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
+        final int displayId;
         try {
             displayId = ActivityManager.getService().getActivityDisplayId(r.token);
         } catch (RemoteException e) {
@@ -2745,9 +2746,7 @@
         }
 
         ContextImpl appContext = ContextImpl.createActivityContext(
-                this, r.packageInfo, r.token, displayId, r.overrideConfig);
-        appContext.setOuterContext(activity);
-        Context baseContext = appContext;
+                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
 
         final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
         // For debugging purposes, if the activity's package name contains the value of
@@ -2760,12 +2759,12 @@
                 if (id != Display.DEFAULT_DISPLAY) {
                     Display display =
                             dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));
-                    baseContext = appContext.createDisplayContext(display);
+                    appContext = (ContextImpl) appContext.createDisplayContext(display);
                     break;
                 }
             }
         }
-        return baseContext;
+        return appContext;
     }
 
     private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
@@ -3119,9 +3118,16 @@
 
         IActivityManager mgr = ActivityManager.getService();
 
+        Application app;
         BroadcastReceiver receiver;
+        ContextImpl context;
         try {
-            java.lang.ClassLoader cl = packageInfo.getClassLoader();
+            app = packageInfo.makeApplication(false, mInstrumentation);
+            context = (ContextImpl) app.getBaseContext();
+            if (data.info.splitName != null) {
+                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
+            }
+            java.lang.ClassLoader cl = context.getClassLoader();
             data.intent.setExtrasClassLoader(cl);
             data.intent.prepareToEnterProcess();
             data.setExtrasClassLoader(cl);
@@ -3136,8 +3142,6 @@
         }
 
         try {
-            Application app = packageInfo.makeApplication(false, mInstrumentation);
-
             if (localLOGV) Slog.v(
                 TAG, "Performing receive of " + data.intent
                 + ": app=" + app
@@ -3146,7 +3150,6 @@
                 + ", comp=" + data.intent.getComponent().toShortString()
                 + ", dir=" + packageInfo.getAppDir());
 
-            ContextImpl context = (ContextImpl)app.getBaseContext();
             sCurrentBroadcastIntent.set(data.intent);
             receiver.setPendingResult(data);
             receiver.onReceive(context.getReceiverRestrictedContext(),
@@ -6031,6 +6034,15 @@
                       info.name);
                 return null;
             }
+
+            if (info.splitName != null) {
+                try {
+                    c = c.createContextForSplit(info.splitName);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
             try {
                 final java.lang.ClassLoader cl = c.getClassLoader();
                 localProvider = (ContentProvider)cl.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9cc13ab..603126b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -245,8 +245,10 @@
     public static final int OP_READ_PHONE_NUMBER = 65;
     /** @hide Request package installs through package installer */
     public static final int OP_REQUEST_INSTALL_PACKAGES = 66;
+    /** @hide Enter picture-in-picture when hidden. */
+    public static final int OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
     /** @hide */
-    public static final int _NUM_OP = 67;
+    public static final int _NUM_OP = 68;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -464,6 +466,7 @@
             OP_AUDIO_ACCESSIBILITY_VOLUME,
             OP_READ_PHONE_NUMBER,
             OP_REQUEST_INSTALL_PACKAGES,
+            OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
     };
 
     /**
@@ -538,6 +541,7 @@
             null, // OP_AUDIO_ACCESSIBILITY_VOLUME
             OPSTR_READ_PHONE_NUMBER,
             null, // OP_REQUEST_INSTALL_PACKAGES
+            null,
     };
 
     /**
@@ -612,6 +616,7 @@
             "AUDIO_ACCESSIBILITY_VOLUME",
             "READ_PHONE_NUMBER",
             "REQUEST_INSTALL_PACKAGES",
+            "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
     };
 
     /**
@@ -686,6 +691,7 @@
             null, // no permission for changing accessibility volume
             Manifest.permission.READ_PHONE_NUMBER,
             Manifest.permission.REQUEST_INSTALL_PACKAGES,
+            null, // no permission for entering picture-in-picture on hide
     };
 
     /**
@@ -761,6 +767,7 @@
             UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
             null, // READ_PHONE_NUMBER
             null, // REQUEST_INSTALL_PACKAGES
+            null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
@@ -835,6 +842,7 @@
             false, // AUDIO_ACCESSIBILITY_VOLUME
             false, // READ_PHONE_NUMBER
             false, // REQUEST_INSTALL_PACKAGES
+            false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
@@ -908,6 +916,7 @@
             AppOpsManager.MODE_ALLOWED,  // OP_AUDIO_ACCESSIBILITY_VOLUME
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
+            AppOpsManager.MODE_ALLOWED,  // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
@@ -985,6 +994,7 @@
             false, // OP_AUDIO_ACCESSIBILITY_VOLUME
             false,
             false, // OP_REQUEST_INSTALL_PACKAGES
+            false, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d37888d..9db2b92 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -32,6 +32,7 @@
 import android.content.ReceiverCallNotAllowedException;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -58,21 +59,27 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.IStorageManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
+import dalvik.system.PathClassLoader;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -80,6 +87,8 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 
 class ReceiverRestrictedContext extends ContextWrapper {
@@ -147,6 +156,7 @@
 
     final ActivityThread mMainThread;
     final LoadedApk mPackageInfo;
+    private ClassLoader mClassLoader;
 
     private final IBinder mActivityToken;
 
@@ -272,8 +282,7 @@
 
     @Override
     public ClassLoader getClassLoader() {
-        return mPackageInfo != null ?
-                mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
+        return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader());
     }
 
     @Override
@@ -1887,7 +1896,7 @@
                 pi.getResDir(),
                 pi.getSplitResDirs(),
                 pi.getOverlayDirs(),
-                pi.getApplicationInfo().sharedLibraryFiles,
+                pi.getSharedLibraries(),
                 displayId,
                 overrideConfig,
                 compatInfo,
@@ -1901,7 +1910,8 @@
                 flags | CONTEXT_REGISTER_PACKAGE);
         if (pi != null) {
             ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
-                    new UserHandle(UserHandle.getUserId(application.uid)), flags);
+                    new UserHandle(UserHandle.getUserId(application.uid)), flags,
+                    null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
@@ -1930,13 +1940,15 @@
         if (packageName.equals("system") || packageName.equals("android")) {
             // The system resources are loaded in every application, so we can safely copy
             // the context without reloading Resources.
-            return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags);
+            return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, user, flags,
+                    null);
         }
 
         LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
         if (pi != null) {
-            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags);
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, user, flags,
+                    null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
@@ -1954,13 +1966,42 @@
     }
 
     @Override
+    public Context createContextForSplit(String splitName) throws NameNotFoundException {
+        if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
+            // All Splits are always loaded.
+            return this;
+        }
+
+        final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
+        final String[] paths = mPackageInfo.getSplitPaths(splitName);
+
+        final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
+                mActivityToken, mUser, mFlags, classLoader);
+
+        final int displayId = mDisplay != null
+                ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+        context.mResources = ResourcesManager.getInstance().getResources(
+                mActivityToken,
+                mPackageInfo.getResDir(),
+                paths,
+                mPackageInfo.getOverlayDirs(),
+                mPackageInfo.getApplicationInfo().sharedLibraryFiles,
+                displayId,
+                null,
+                mPackageInfo.getCompatibilityInfo(),
+                classLoader);
+        return context;
+    }
+
+    @Override
     public Context createConfigurationContext(Configuration overrideConfiguration) {
         if (overrideConfiguration == null) {
             throw new IllegalArgumentException("overrideConfiguration must not be null");
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags);
+                mUser, mFlags, mClassLoader);
 
         final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
         context.mResources = createResources(mActivityToken, mPackageInfo, displayId,
@@ -1975,7 +2016,7 @@
         }
 
         ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags);
+                mUser, mFlags, mClassLoader);
 
         final int displayId = display.getDisplayId();
         context.mResources = createResources(mActivityToken, mPackageInfo, displayId, null,
@@ -1988,14 +2029,16 @@
     public Context createDeviceProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
                 | Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
-        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
+                mClassLoader);
     }
 
     @Override
     public Context createCredentialProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
                 | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
-        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags,
+                mClassLoader);
     }
 
     @Override
@@ -2082,8 +2125,9 @@
 
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
-        context.mResources = packageInfo.getResources(mainThread);
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+                null);
+        context.mResources = packageInfo.getResources();
         context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
                 context.mResourcesManager.getDisplayMetrics());
         return context;
@@ -2091,18 +2135,35 @@
 
     static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0);
-        context.mResources = packageInfo.getResources(mainThread);
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+                null);
+        context.mResources = packageInfo.getResources();
         return context;
     }
 
     static ContextImpl createActivityContext(ActivityThread mainThread,
-            LoadedApk packageInfo, IBinder activityToken, int displayId,
+            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
             Configuration overrideConfiguration) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
 
+        String[] splitDirs = packageInfo.getSplitResDirs();
+        ClassLoader classLoader = packageInfo.getClassLoader();
+
+        if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
+            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
+            try {
+                classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
+                splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
+            } catch (NameNotFoundException e) {
+                // Nothing above us can handle a NameNotFoundException, better crash.
+                throw new RuntimeException(e);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+            }
+        }
+
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
-                0);
+                0, classLoader);
 
         // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
         displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
@@ -2117,20 +2178,21 @@
         // will be rebased upon.
         context.mResources = resourcesManager.createBaseActivityResources(activityToken,
                 packageInfo.getResDir(),
-                packageInfo.getSplitResDirs(),
+                splitDirs,
                 packageInfo.getOverlayDirs(),
-                packageInfo.getApplicationInfo().sharedLibraryFiles,
+                packageInfo.getSharedLibraries(),
                 displayId,
                 overrideConfiguration,
                 compatInfo,
-                packageInfo.getClassLoader());
+                classLoader);
         context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                 context.mResources.getDisplayAdjustments());
         return context;
     }
 
     private ContextImpl(ContextImpl container, ActivityThread mainThread,
-            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags) {
+            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
+            ClassLoader classLoader) {
         mOuterContext = this;
 
         // If creator didn't specify which storage to use, use the default
@@ -2155,6 +2217,7 @@
         mUser = user;
 
         mPackageInfo = packageInfo;
+        mClassLoader = classLoader;
         mResourcesManager = ResourcesManager.getInstance();
 
         if (container != null) {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index f909af0..740af9c 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,6 +47,8 @@
             in Notification notification, inout int[] idReceived, int userId);
     void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
 
+    void setShowBadge(String pkg, int uid, boolean showBadge);
+    boolean canShowBadge(String pkg, int uid);
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     boolean areNotificationsEnabledForPackage(String pkg, int uid);
     boolean areNotificationsEnabled(String pkg);
@@ -73,8 +75,6 @@
 
     void snoozeNotificationUntilContextFromListener(in INotificationListener token, String key, String snoozeCriterionId);
     void snoozeNotificationUntilFromListener(in INotificationListener token, String key, long until);
-    void snoozeNotificationFromListener(in INotificationListener token, String key);
-    void unsnoozeNotificationFromListener(in INotificationListener token, String key);
 
     void requestBindListener(in ComponentName component);
     void requestUnbindListener(in INotificationListener token);
@@ -84,6 +84,7 @@
     void setNotificationsShownFromListener(in INotificationListener token, in String[] keys);
 
     ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim);
+    ParceledListSlice getSnoozedNotificationsFromListener(in INotificationListener token, int trim);
     void requestHintsFromListener(in INotificationListener token, int hints);
     int getHintsFromListener(in INotificationListener token);
     void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
@@ -98,6 +99,7 @@
     void updateNotificationChannelFromAssistant(in INotificationListener token, String pkg, in NotificationChannel channel);
     void deleteNotificationChannelFromAssistant(in INotificationListener token, String pkg, String channelId);
     ParceledListSlice getNotificationChannelsFromAssistant(in INotificationListener token, String pkg);
+    void unsnoozeNotificationFromAssistant(in INotificationListener token, String key);
 
     ComponentName getEffectsSuppressor();
     boolean matchesCallFilter(in Bundle extras);
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 848464c..7f0b6fb 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -58,11 +58,16 @@
     void setTheme(String theme);
 
     /**
-     * Gets whith theme overlays to use within /vendor/overlay.
+     * Gets which theme overlays to use within /vendor/overlay.
      */
     String getTheme();
 
     /**
+     * Gets the themes available in /vendor/overlay.
+     */
+    String[] getAvailableThemes();
+
+    /**
      * Tells if UI mode is locked or not.
      */
     boolean isUiModeLocked();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 4ab0743..17f5edd 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -27,9 +27,11 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.split.SplitDependencyLoaderHelper;
 import android.content.res.AssetManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Resources;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -41,23 +43,22 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.ErrnoException;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
+import com.android.internal.util.ArrayUtils;
+
 import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMRuntime;
 
 import java.io.File;
-import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
@@ -65,13 +66,12 @@
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.Objects;
 
-import libcore.io.IoUtils;
-
 final class IntentReceiverLeaked extends AndroidRuntimeException {
     public IntentReceiverLeaked(String msg) {
         super(msg);
@@ -97,8 +97,6 @@
     private ApplicationInfo mApplicationInfo;
     private String mAppDir;
     private String mResDir;
-    private String[] mSplitAppDirs;
-    private String[] mSplitResDirs;
     private String[] mOverlayDirs;
     private String[] mSharedLibraries;
     private String mDataDir;
@@ -116,14 +114,18 @@
     private ClassLoader mClassLoader;
     private Application mApplication;
 
+    private String[] mSplitNames;
+    private String[] mSplitAppDirs;
+    private String[] mSplitResDirs;
+
     private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
-        = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+        = new ArrayMap<>();
     private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
-        = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+        = new ArrayMap<>();
     private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
-        = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+        = new ArrayMap<>();
     private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
-        = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+        = new ArrayMap<>();
 
     int mClientCount = 0;
 
@@ -300,9 +302,18 @@
         synchronized (this) {
             createOrUpdateClassLoaderLocked(addedPaths);
             if (mResources != null) {
-                mResources = mActivityThread.getTopLevelResources(mResDir, mSplitResDirs,
-                        mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
-                        this);
+                final String[] splitPaths;
+                try {
+                    splitPaths = getSplitPaths(null);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // This should NEVER fail.
+                    throw new AssertionError("null split not found");
+                }
+
+                mResources = ResourcesManager.getInstance().getResources(null, mResDir,
+                        splitPaths, mOverlayDirs, mSharedLibraries,
+                        Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                        getClassLoader());
             }
         }
     }
@@ -313,8 +324,6 @@
         mApplicationInfo = aInfo;
         mAppDir = aInfo.sourceDir;
         mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
-        mSplitAppDirs = aInfo.splitSourceDirs;
-        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
         mOverlayDirs = aInfo.resourceDirs;
         mSharedLibraries = aInfo.sharedLibraryFiles;
         mDataDir = aInfo.dataDir;
@@ -322,19 +331,28 @@
         mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir);
         mDeviceProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.deviceProtectedDataDir);
         mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.credentialProtectedDataDir);
+
+        mSplitNames = aInfo.splitNames;
+        mSplitAppDirs = aInfo.splitSourceDirs;
+        mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
+
+        if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
+            mSplitLoader = new SplitDependencyLoader(aInfo.splitDependencies);
+        }
     }
 
     public static void makePaths(ActivityThread activityThread, ApplicationInfo aInfo,
             List<String> outZipPaths, List<String> outLibPaths) {
         final String appDir = aInfo.sourceDir;
-        final String[] splitAppDirs = aInfo.splitSourceDirs;
         final String libDir = aInfo.nativeLibraryDir;
         final String[] sharedLibraries = aInfo.sharedLibraryFiles;
 
         outZipPaths.clear();
         outZipPaths.add(appDir);
-        if (splitAppDirs != null) {
-            Collections.addAll(outZipPaths, splitAppDirs);
+
+        // Do not load all available splits if the app requested isolated split loading.
+        if (aInfo.splitSourceDirs != null && !aInfo.requestsIsolatedSplitLoading()) {
+            Collections.addAll(outZipPaths, aInfo.splitSourceDirs);
         }
 
         if (outLibPaths != null) {
@@ -367,13 +385,18 @@
                     || appDir.equals(instrumentedAppDir)) {
                 outZipPaths.clear();
                 outZipPaths.add(instrumentationAppDir);
-                if (instrumentationSplitAppDirs != null) {
-                    Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
-                }
-                if (!instrumentationAppDir.equals(instrumentedAppDir)) {
-                    outZipPaths.add(instrumentedAppDir);
-                    if (instrumentedSplitAppDirs != null) {
-                        Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+
+                // Only add splits if the app did not request isolated split loading.
+                if (!aInfo.requestsIsolatedSplitLoading()) {
+                    if (instrumentationSplitAppDirs != null) {
+                        Collections.addAll(outZipPaths, instrumentationSplitAppDirs);
+                    }
+
+                    if (!instrumentationAppDir.equals(instrumentedAppDir)) {
+                        outZipPaths.add(instrumentedAppDir);
+                        if (instrumentedSplitAppDirs != null) {
+                            Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
+                        }
                     }
                 }
 
@@ -399,7 +422,7 @@
             // will be added to zipPaths that shouldn't be part of the library path.
             if (aInfo.primaryCpuAbi != null) {
                 // Add fake libs into the library search path if we target prior to N.
-                if (aInfo.targetSdkVersion <= 23) {
+                if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) {
                     outLibPaths.add("/system/fake-libs" +
                         (VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : ""));
                 }
@@ -434,6 +457,116 @@
         }
     }
 
+    private class SplitDependencyLoader
+            extends SplitDependencyLoaderHelper<PackageManager.NameNotFoundException> {
+        private String[] mCachedBaseResourcePath;
+        private final String[][] mCachedResourcePaths;
+        private final ClassLoader[] mCachedSplitClassLoaders;
+
+        SplitDependencyLoader(SparseIntArray dependencies) {
+            super(dependencies);
+            mCachedResourcePaths = new String[mSplitNames.length][];
+            mCachedSplitClassLoaders = new ClassLoader[mSplitNames.length];
+        }
+
+        @Override
+        protected boolean isSplitCached(int splitIdx) {
+            if (splitIdx != -1) {
+                return mCachedSplitClassLoaders[splitIdx] != null;
+            }
+            return mClassLoader != null && mCachedBaseResourcePath != null;
+        }
+
+        private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
+            for (int i = 0; i < mSplitNames.length; i++) {
+                if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
+                    outAssetPaths.add(mSplitResDirs[i]);
+                }
+            }
+        }
+
+        @Override
+        protected void constructSplit(int splitIdx, int parentSplitIdx) throws
+                PackageManager.NameNotFoundException {
+            final ArrayList<String> splitPaths = new ArrayList<>();
+            if (splitIdx == -1) {
+                createOrUpdateClassLoaderLocked(null);
+                addAllConfigSplits(null, splitPaths);
+                mCachedBaseResourcePath = splitPaths.toArray(new String[splitPaths.size()]);
+                return;
+            }
+
+            final ClassLoader parent;
+            if (parentSplitIdx == -1) {
+                // The parent is the base APK, so use its ClassLoader as parent
+                // and its configuration splits as part of our own too.
+                parent = mClassLoader;
+                Collections.addAll(splitPaths, mCachedBaseResourcePath);
+            } else {
+                parent = mCachedSplitClassLoaders[parentSplitIdx];
+                Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
+            }
+
+            mCachedSplitClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
+                    mSplitAppDirs[splitIdx], getTargetSdkVersion(), false, null, null, parent);
+
+            splitPaths.add(mSplitResDirs[splitIdx]);
+            addAllConfigSplits(mSplitNames[splitIdx], splitPaths);
+            mCachedResourcePaths[splitIdx] = splitPaths.toArray(new String[splitPaths.size()]);
+        }
+
+        private int ensureSplitLoaded(String splitName)
+                throws PackageManager.NameNotFoundException {
+            final int idx;
+            if (splitName == null) {
+                idx = -1;
+            } else {
+                idx = Arrays.binarySearch(mSplitNames, splitName);
+                if (idx < 0) {
+                    throw new PackageManager.NameNotFoundException(
+                            "Split name '" + splitName + "' is not installed");
+                }
+            }
+
+            loadDependenciesForSplit(idx);
+            return idx;
+        }
+
+        ClassLoader getClassLoaderForSplit(String splitName)
+                throws PackageManager.NameNotFoundException {
+            final int idx = ensureSplitLoaded(splitName);
+            if (idx < 0) {
+                return mClassLoader;
+            }
+            return mCachedSplitClassLoaders[idx];
+        }
+
+        String[] getSplitPathsForSplit(String splitName)
+                throws PackageManager.NameNotFoundException {
+            final int idx = ensureSplitLoaded(splitName);
+            if (idx < 0) {
+                return mCachedBaseResourcePath;
+            }
+            return mCachedResourcePaths[idx];
+        }
+    }
+
+    private SplitDependencyLoader mSplitLoader;
+
+    ClassLoader getSplitClassLoader(String splitName) throws PackageManager.NameNotFoundException {
+        if (mSplitLoader == null) {
+            return mClassLoader;
+        }
+        return mSplitLoader.getClassLoaderForSplit(splitName);
+    }
+
+    String[] getSplitPaths(String splitName) throws PackageManager.NameNotFoundException {
+        if (mSplitLoader == null) {
+            return mSplitResDirs;
+        }
+        return mSplitLoader.getSplitPathsForSplit(splitName);
+    }
+
     private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
         if (mPackageName.equals("android")) {
             // Note: This branch is taken for system server and we don't need to setup
@@ -790,6 +923,10 @@
         return mOverlayDirs;
     }
 
+    public String[] getSharedLibraries() {
+        return mSharedLibraries;
+    }
+
     public String getDataDir() {
         return mDataDir;
     }
@@ -806,14 +943,24 @@
         return mCredentialProtectedDataDirFile;
     }
 
-    public AssetManager getAssets(ActivityThread mainThread) {
-        return getResources(mainThread).getAssets();
+    public AssetManager getAssets() {
+        return getResources().getAssets();
     }
 
-    public Resources getResources(ActivityThread mainThread) {
+    public Resources getResources() {
         if (mResources == null) {
-            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
-                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
+            final String[] splitPaths;
+            try {
+                splitPaths = getSplitPaths(null);
+            } catch (PackageManager.NameNotFoundException e) {
+                // This should never fail.
+                throw new AssertionError("null split not found");
+            }
+
+            mResources = ResourcesManager.getInstance().getResources(null, mResDir,
+                    splitPaths, mOverlayDirs, mSharedLibraries,
+                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
+                    getClassLoader());
         }
         return mResources;
     }
@@ -870,8 +1017,7 @@
         }
 
         // Rewrite the R 'constants' for all library apks.
-        SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
-                .getAssignedPackageIdentifiers();
+        SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
         final int N = packageIdentifiers.size();
         for (int i = 0; i < N; i++) {
             final int id = packageIdentifiers.keyAt(i);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 601dfce..34d2039 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -46,6 +46,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.text.BidiFormatter;
 import android.text.SpannableStringBuilder;
@@ -70,7 +71,6 @@
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.NotificationColorUtil;
-import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -972,6 +972,21 @@
     public static final String EXTRA_MESSAGES = "android.messages";
 
     /**
+     * {@link #extras} key: an array of
+     * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic}
+     * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a
+     * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable
+     * array of bundles.
+     */
+    public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
+
+    /**
+     * {@link #extras} key: whether the notification should be colorized as
+     * supplied to {@link Builder#setColorized(boolean)}}.
+     */
+    public static final String EXTRA_COLORIZED = "android.colorized";
+
+    /**
      * {@link #extras} key: the user that built the notification.
      *
      * @hide
@@ -1022,6 +1037,7 @@
     private Icon mLargeIcon;
 
     private String mChannelId;
+    private long mTimeout;
 
     /**
      * Structure to encapsulate a named action that can be shown as part of this notification.
@@ -1051,7 +1067,7 @@
         private final Bundle mExtras;
         private Icon mIcon;
         private final RemoteInput[] mRemoteInputs;
-        private boolean mAllowGeneratedReplies = false;
+        private boolean mAllowGeneratedReplies = true;
 
         /**
          * Small icon representing the action.
@@ -1093,7 +1109,7 @@
          */
         @Deprecated
         public Action(int icon, CharSequence title, PendingIntent intent) {
-            this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, false);
+            this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
         }
 
         /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
@@ -1166,7 +1182,7 @@
             private final Icon mIcon;
             private final CharSequence mTitle;
             private final PendingIntent mIntent;
-            private boolean mAllowGeneratedReplies;
+            private boolean mAllowGeneratedReplies = true;
             private final Bundle mExtras;
             private ArrayList<RemoteInput> mRemoteInputs;
 
@@ -1188,7 +1204,7 @@
              * @param intent the {@link PendingIntent} to fire when users trigger this action
              */
             public Builder(Icon icon, CharSequence title, PendingIntent intent) {
-                this(icon, title, intent, new Bundle(), null, false);
+                this(icon, title, intent, new Bundle(), null, true);
             }
 
             /**
@@ -1260,7 +1276,7 @@
              * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
              * otherwise
              * @return this object for method chaining
-             * The default value is {@code false}
+             * The default value is {@code true}
              */
             public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
                 mAllowGeneratedReplies = allowGeneratedReplies;
@@ -1766,6 +1782,7 @@
         if (parcel.readInt() != 0) {
             mChannelId = parcel.readString();
         }
+        mTimeout = parcel.readLong();
     }
 
     @Override
@@ -1872,6 +1889,7 @@
         that.color = this.color;
 
         that.mChannelId = this.mChannelId;
+        that.mTimeout = this.mTimeout;
 
         if (!heavy) {
             that.lightenPayload(); // will clean out extras
@@ -2128,6 +2146,7 @@
         } else {
             parcel.writeInt(0);
         }
+        parcel.writeLong(mTimeout);
     }
 
     /**
@@ -2325,6 +2344,13 @@
     }
 
     /**
+     * Returns the time at which this notification should be canceled, if it's not canceled already.
+     */
+    public long getTimeout() {
+        return mTimeout;
+    }
+
+    /**
      * The small icon representing this notification in the status bar and content view.
      *
      * @return the small icon representing this notification.
@@ -2407,6 +2433,9 @@
 
         private static final int MAX_ACTION_BUTTONS = 3;
 
+        private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
+                SystemProperties.getBoolean("notifications.only_title", true);
+
         private Context mContext;
         private Notification mN;
         private Bundle mUserExtras = new Bundle();
@@ -2414,6 +2443,8 @@
         private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
         private ArrayList<String> mPersonList = new ArrayList<String>();
         private NotificationColorUtil mColorUtil;
+        private boolean mIsLegacy;
+        private boolean mIsLegacyInitialized;
         private boolean mColorUtilInited = false;
 
         /**
@@ -2432,6 +2463,10 @@
          * so make sure to call {@link StandardTemplateParams#reset()} before using it.
          */
         StandardTemplateParams mParams = new StandardTemplateParams();
+        private int mTextColorsAreForBackground = COLOR_INVALID;
+        private int mPrimaryTextColor = COLOR_INVALID;
+        private int mSecondaryTextColor = COLOR_INVALID;
+        private int mActionBarColor = COLOR_INVALID;
 
         /**
          * Constructs a new Builder with the defaults:
@@ -2516,7 +2551,7 @@
         private NotificationColorUtil getColorUtil() {
             if (!mColorUtilInited) {
                 mColorUtilInited = true;
-                if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
+                if (isLegacy() || isColorized()) {
                     mColorUtil = NotificationColorUtil.getInstance(mContext);
                 }
             }
@@ -2532,6 +2567,15 @@
         }
 
         /**
+         * Specifies the time at which this notification should be canceled, if it is not already
+         * canceled.
+         */
+        public Builder setTimeout(long when) {
+            mN.mTimeout = when;
+            return this;
+        }
+
+        /**
          * Add a timestamp pertaining to the notification (usually the time the event occurred).
          *
          * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
@@ -2992,6 +3036,22 @@
         }
 
         /**
+         * Set whether this notification should be colorized. When set, the color set with
+         * {@link #setColor(int)} will be used as the background color of this notification.
+         * <p>
+         * The coloring will only be applied if the notification is ongoing.
+         * This should only be used for high priority ongoing tasks like navigation, an ongoing
+         * call, or other similarly high-priority events for the user.
+         *
+         * @see Builder#setOngoing(boolean)
+         * @see Builder#setColor(int)
+         */
+        public Builder setColorized(boolean colorize) {
+            mN.extras.putBoolean(EXTRA_COLORIZED, colorize);
+            return this;
+        }
+
+        /**
          * Set this flag if you would only like the sound, vibrate
          * and ticker to be played if the notification is not already showing.
          *
@@ -3356,6 +3416,10 @@
             if (profileBadge != null) {
                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
+                if (isColorized()) {
+                    contentView.setDrawableParameters(R.id.profile_badge, false, -1,
+                            getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1);
+                }
             }
         }
 
@@ -3413,13 +3477,14 @@
             resetStandardTemplate(contentView);
 
             final Bundle ex = mN.extras;
-
+            updateBackgroundColor(contentView);
             bindNotificationHeader(contentView, p.ambient);
             bindLargeIcon(contentView);
             boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
             if (p.title != null) {
                 contentView.setViewVisibility(R.id.title, View.VISIBLE);
                 contentView.setTextViewText(R.id.title, p.title);
+                setTextViewColorPrimary(contentView, R.id.title);
                 contentView.setViewLayoutWidth(R.id.title, showProgress
                         ? ViewGroup.LayoutParams.WRAP_CONTENT
                         : ViewGroup.LayoutParams.MATCH_PARENT);
@@ -3428,6 +3493,7 @@
                 int textId = showProgress ? com.android.internal.R.id.text_line_1
                         : com.android.internal.R.id.text;
                 contentView.setTextViewText(textId, p.text);
+                setTextViewColorSecondary(contentView, textId);
                 contentView.setViewVisibility(textId, View.VISIBLE);
             }
 
@@ -3436,6 +3502,52 @@
             return contentView;
         }
 
+        private void setTextViewColorPrimary(RemoteViews contentView, int id) {
+            ensureColors();
+            contentView.setTextColor(id, mPrimaryTextColor);
+        }
+
+        private int getPrimaryTextColor() {
+            ensureColors();
+            return mPrimaryTextColor;
+        }
+
+        private int getActionBarColor() {
+            ensureColors();
+            return mActionBarColor;
+        }
+
+        private void setTextViewColorSecondary(RemoteViews contentView, int id) {
+            ensureColors();
+            contentView.setTextColor(id, mSecondaryTextColor);
+        }
+
+        private void ensureColors() {
+            int backgroundColor = getBackgroundColor();
+            if (mPrimaryTextColor == COLOR_INVALID
+                    || mSecondaryTextColor == COLOR_INVALID
+                    || mActionBarColor == COLOR_INVALID
+                    || mTextColorsAreForBackground != backgroundColor) {
+                mTextColorsAreForBackground = backgroundColor;
+                mPrimaryTextColor = NotificationColorUtil.resolvePrimaryColor(
+                        mContext, backgroundColor);
+                mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(
+                        mContext, backgroundColor);
+                mActionBarColor = NotificationColorUtil.resolveActionBarColor(backgroundColor);
+            }
+        }
+
+        private void updateBackgroundColor(RemoteViews contentView) {
+            if (isColorized()) {
+                contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor",
+                        getBackgroundColor());
+            } else {
+                // Clear it!
+                contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource",
+                        0);
+            }
+        }
+
         /**
          * @param remoteView the remote view to update the minheight in
          * @param hasMinHeight does it have a mimHeight
@@ -3502,15 +3614,17 @@
         }
 
         private void bindExpandButton(RemoteViews contentView) {
-            contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveContrastColor(),
+            int color = isColorized() ? getPrimaryTextColor() : resolveContrastColor();
+            contentView.setDrawableParameters(R.id.expand_button, false, -1, color,
                     PorterDuff.Mode.SRC_ATOP, -1);
             contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
-                    resolveContrastColor());
+                    color);
         }
 
         private void bindHeaderChronometerAndTime(RemoteViews contentView) {
             if (showsTimeOrChronometer()) {
                 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
+                setTextViewColorSecondary(contentView, R.id.time_divider);
                 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
                     contentView.setLong(R.id.chronometer, "setBase",
@@ -3518,9 +3632,11 @@
                     contentView.setBoolean(R.id.chronometer, "setStarted", true);
                     boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN);
                     contentView.setChronometerCountDown(R.id.chronometer, countsDown);
+                    setTextViewColorSecondary(contentView, R.id.chronometer);
                 } else {
                     contentView.setViewVisibility(R.id.time, View.VISIBLE);
                     contentView.setLong(R.id.time, "setTime", mN.when);
+                    setTextViewColorSecondary(contentView, R.id.time);
                 }
             } else {
                 // We still want a time to be set but gone, such that we can show and hide it
@@ -3543,8 +3659,10 @@
             if (headerText != null) {
                 // TODO: Remove the span entirely to only have the string with propper formating.
                 contentView.setTextViewText(R.id.header_text, processLegacyText(headerText));
+                setTextViewColorSecondary(contentView, R.id.header_text);
                 contentView.setViewVisibility(R.id.header_text, View.VISIBLE);
                 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE);
+                setTextViewColorSecondary(contentView, R.id.header_text_divider);
             }
         }
 
@@ -3583,8 +3701,12 @@
         }
         private void bindHeaderAppName(RemoteViews contentView, boolean ambient) {
             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
-            contentView.setTextColor(R.id.app_name_text,
-                    ambient ? resolveAmbientColor() : resolveContrastColor());
+            if (isColorized()) {
+                setTextViewColorPrimary(contentView, R.id.app_name_text);
+            } else {
+                contentView.setTextColor(R.id.app_name_text,
+                        ambient ? resolveAmbientColor() : resolveContrastColor());
+            }
         }
 
         private void bindSmallIcon(RemoteViews contentView, boolean ambient) {
@@ -3642,6 +3764,11 @@
                 big.setViewVisibility(R.id.actions, View.VISIBLE);
                 if (p.ambient) {
                     big.setInt(R.id.actions, "setBackgroundColor", Color.TRANSPARENT);
+                } else if (isColorized()) {
+                    big.setInt(R.id.actions, "setBackgroundColor", getActionBarColor());
+                } else {
+                    big.setInt(R.id.actions, "setBackgroundColor", mContext.getColor(
+                            R.color.notification_action_list));
                 }
                 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target,
                         R.dimen.notification_action_list_height);
@@ -3663,15 +3790,18 @@
                     && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) {
                 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE);
                 big.setTextViewText(R.id.notification_material_reply_text_1, replyText[0]);
+                setTextViewColorSecondary(big, R.id.notification_material_reply_text_1);
 
                 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) {
                     big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE);
                     big.setTextViewText(R.id.notification_material_reply_text_2, replyText[1]);
+                    setTextViewColorSecondary(big, R.id.notification_material_reply_text_2);
 
                     if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) {
                         big.setViewVisibility(
                                 R.id.notification_material_reply_text_3, View.VISIBLE);
                         big.setTextViewText(R.id.notification_material_reply_text_3, replyText[2]);
+                        setTextViewColorSecondary(big, R.id.notification_material_reply_text_3);
                     }
                 }
             }
@@ -3731,7 +3861,7 @@
             } else if (mActions.size() != 0) {
                 result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
             }
-            adaptNotificationHeaderForBigContentView(result);
+            makeHeaderExpanded(result);
             return result;
         }
 
@@ -3766,7 +3896,12 @@
             }
         }
 
-        private void adaptNotificationHeaderForBigContentView(RemoteViews result) {
+        /**
+         * Adapt the Notification header if this view is used as an expanded view.
+         *
+         * @hide
+         */
+        public static void makeHeaderExpanded(RemoteViews result) {
             if (result != null) {
                 result.setBoolean(R.id.notification_header, "setExpanded", true);
             }
@@ -3826,7 +3961,57 @@
             return publicView;
         }
 
+        /**
+         * Construct a content view for the display when low - priority
+         *
+         * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
+         *                          a new subtext is created consisting of the content of the
+         *                          notification.
+         * @hide
+         */
+        public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
+            int color = mN.color;
+            mN.color = COLOR_DEFAULT;
+            CharSequence summary = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+            if (!useRegularSubtext || TextUtils.isEmpty(summary)) {
+                CharSequence newSummary = createSummaryText();
+                if (!TextUtils.isEmpty(newSummary)) {
+                    mN.extras.putCharSequence(EXTRA_SUB_TEXT, newSummary);
+                }
+            }
+            RemoteViews header = makeNotificationHeader();
+            if (summary != null) {
+                mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
+            } else {
+                mN.extras.remove(EXTRA_SUB_TEXT);
+            }
+            mN.color = color;
+            return header;
+        }
 
+        private CharSequence createSummaryText() {
+            CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE);
+            if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) {
+                return titleText;
+            }
+            SpannableStringBuilder summary = new SpannableStringBuilder();
+            if (titleText == null) {
+                titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+            }
+            BidiFormatter bidi = BidiFormatter.getInstance();
+            if (titleText != null) {
+                summary.append(bidi.unicodeWrap(titleText));
+            }
+            CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT);
+            if (titleText != null && contentText != null) {
+                summary.append(bidi.unicodeWrap(mContext.getText(
+                        R.string.notification_header_divider_symbol_with_spaces)));
+            }
+            if (contentText != null) {
+                summary.append(bidi.unicodeWrap(contentText));
+            }
+            return summary;
+        }
 
         private RemoteViews generateActionButton(Action action, boolean emphazisedMode,
                 boolean oddAction, boolean ambient) {
@@ -3842,6 +4027,7 @@
             if (action.mRemoteInputs != null) {
                 button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
             }
+            // TODO: handle emphasized mode / actions right
             if (emphazisedMode) {
                 // change the background bgColor
                 int bgColor = mContext.getColor(oddAction ? R.color.notification_action_list
@@ -3857,16 +4043,19 @@
                     title = ensureColorSpanContrast(title, bgColor, outResultColor);
                 }
                 button.setTextViewText(R.id.action0, title);
+                setTextViewColorPrimary(button, R.id.action0);
                 if (outResultColor != null && outResultColor[0] != null) {
                     // We need to set the text color as well since changing a text to uppercase
                     // clears its spans.
                     button.setTextColor(R.id.action0, outResultColor[0]);
-                } else if (mN.color != COLOR_DEFAULT) {
+                } else if (mN.color != COLOR_DEFAULT && !isColorized()) {
                     button.setTextColor(R.id.action0,resolveContrastColor());
                 }
             } else {
                 button.setTextViewText(R.id.action0, processLegacyText(action.title));
-                if (mN.color != COLOR_DEFAULT) {
+                if (isColorized()) {
+                    setTextViewColorPrimary(button, R.id.action0);
+                } else if (mN.color != COLOR_DEFAULT) {
                     button.setTextColor(R.id.action0,
                             ambient ? resolveAmbientColor() : resolveContrastColor());
                 }
@@ -3985,11 +4174,16 @@
          *         doesn't create material notifications by itself) app.
          */
         private boolean isLegacy() {
-            return getColorUtil() != null;
+            if (!mIsLegacyInitialized) {
+                mIsLegacy = mContext.getApplicationInfo().targetSdkVersion
+                        < Build.VERSION_CODES.LOLLIPOP;
+                mIsLegacyInitialized = true;
+            }
+            return mIsLegacy;
         }
 
         private CharSequence processLegacyText(CharSequence charSequence) {
-            if (isLegacy()) {
+            if (isLegacy() || isColorized()) {
                 return getColorUtil().invertCharSequenceColors(charSequence);
             } else {
                 return charSequence;
@@ -4251,6 +4445,37 @@
         private int getActionTombstoneLayoutResource() {
             return R.layout.notification_material_action_tombstone;
         }
+
+        private int getBackgroundColor() {
+            if (isColorized()) {
+                return mN.color;
+            } else {
+                return COLOR_DEFAULT;
+            }
+        }
+
+        private boolean isColorized() {
+            return mN.isColorized();
+        }
+    }
+
+    /**
+     * @return whether this notification is ongoing and can't be dismissed by the user.
+     */
+    private boolean isOngoing() {
+        final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE
+                | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
+        return (flags & ongoingFlags) != 0;
+    }
+
+    /**
+     * @return true if this notification is colorized. This also factors in wheather the
+     * notification is ongoing.
+     *
+     * @hide
+     */
+    public boolean isColorized() {
+        return extras.getBoolean(EXTRA_COLORIZED) && isOngoing();
     }
 
     private boolean hasLargeIcon() {
@@ -4584,6 +4809,7 @@
             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
             if (mSummaryTextSet) {
                 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
+                mBuilder.setTextViewColorSecondary(contentView, R.id.text);
                 contentView.setViewVisibility(R.id.text, View.VISIBLE);
             }
             mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon());
@@ -4739,6 +4965,7 @@
         static void applyBigTextContentView(Builder builder,
                 RemoteViews contentView, CharSequence bigTextText) {
             contentView.setTextViewText(R.id.big_text, bigTextText);
+            builder.setTextViewColorSecondary(contentView, R.id.big_text);
             contentView.setViewVisibility(R.id.big_text,
                     TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
             contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines(builder));
@@ -4789,6 +5016,7 @@
         CharSequence mUserDisplayName;
         CharSequence mConversationTitle;
         List<Message> mMessages = new ArrayList<>();
+        List<Message> mHistoricMessages = new ArrayList<>();
 
         MessagingStyle() {
         }
@@ -4845,15 +5073,15 @@
          * @return this object for method chaining
          */
         public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
-            mMessages.add(new Message(text, timestamp, sender));
-            if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
-                mMessages.remove(0);
-            }
-            return this;
+            return addMessage(new Message(text, timestamp, sender));
         }
 
         /**
          * Adds a {@link Message} for display in this notification.
+         *
+         * <p>The messages should be added in chronologic order, i.e. the oldest first,
+         * the newest last.
+         *
          * @param message The {@link Message} to be displayed
          * @return this object for method chaining
          */
@@ -4866,6 +5094,27 @@
         }
 
         /**
+         * Adds a {@link Message} for historic context in this notification.
+         *
+         * <p>Messages should be added as historic if they are not the main subject of the
+         * notification but may give context to a conversation. The system may choose to present
+         * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}.
+         *
+         * <p>The messages should be added in chronologic order, i.e. the oldest first,
+         * the newest last.
+         *
+         * @param message The historic {@link Message} to be added
+         * @return this object for method chaining
+         */
+        public MessagingStyle addHistoricMessage(Message message) {
+            mHistoricMessages.add(message);
+            if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
+                mHistoricMessages.remove(0);
+            }
+            return this;
+        }
+
+        /**
          * Gets the list of {@code Message} objects that represent the notification
          */
         public List<Message> getMessages() {
@@ -4873,6 +5122,13 @@
         }
 
         /**
+         * Gets the list of historic {@code Message}s in the notification.
+         */
+        public List<Message> getHistoricMessages() {
+            return mHistoricMessages;
+        }
+
+        /**
          * @hide
          */
         @Override
@@ -4887,6 +5143,9 @@
             if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
                     Message.getBundleArrayForMessages(mMessages));
             }
+            if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES,
+                    Message.getBundleArrayForMessages(mHistoricMessages));
+            }
 
             fixTitleAndTextExtras(extras);
         }
@@ -4926,11 +5185,16 @@
             super.restoreFromExtras(extras);
 
             mMessages.clear();
+            mHistoricMessages.clear();
             mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
-            Parcelable[] parcelables = extras.getParcelableArray(EXTRA_MESSAGES);
-            if (parcelables != null && parcelables instanceof Parcelable[]) {
-                mMessages = Message.getMessagesFromBundleArray(parcelables);
+            Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+            if (messages != null && messages instanceof Parcelable[]) {
+                mMessages = Message.getMessagesFromBundleArray(messages);
+            }
+            Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
+            if (histMessages != null && histMessages instanceof Parcelable[]) {
+                mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
             }
         }
 
@@ -4945,7 +5209,7 @@
                     : (m == null) ? null : m.mSender;
             CharSequence text = (m == null)
                     ? null
-                    : mConversationTitle != null ? makeMessageLine(m) : m.mText;
+                    : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
 
             return mBuilder.applyStandardTemplateWithActions(mBuilder.getBaseLayoutResource(),
                     mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
@@ -4983,7 +5247,7 @@
                 CharSequence text;
                 if (hasTitle) {
                     bigTitle = title;
-                    text = makeMessageLine(mMessages.get(0));
+                    text = makeMessageLine(mMessages.get(0), mBuilder);
                 } else {
                     bigTitle = mMessages.get(0).mSender;
                     text = mMessages.get(0).mText;
@@ -5015,13 +5279,13 @@
 
             int contractedChildId = View.NO_ID;
             Message contractedMessage = findLatestIncomingMessage();
-            int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
-            while (firstMessage + i < mMessages.size() && i < rowIds.length) {
-                Message m = mMessages.get(firstMessage + i);
+            int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
+                    - (rowIds.length - mMessages.size()));
+            while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
+                Message m = mHistoricMessages.get(firstHistoricMessage + i);
                 int rowId = rowIds[i];
 
-                contentView.setViewVisibility(rowId, View.VISIBLE);
-                contentView.setTextViewText(rowId, makeMessageLine(m));
+                contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
 
                 if (contractedMessage == m) {
                     contractedChildId = rowId;
@@ -5029,23 +5293,52 @@
 
                 i++;
             }
+
+            int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
+            while (firstMessage + i < mMessages.size() && i < rowIds.length) {
+                Message m = mMessages.get(firstMessage + i);
+                int rowId = rowIds[i];
+
+                contentView.setViewVisibility(rowId, View.VISIBLE);
+                contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
+                mBuilder.setTextViewColorSecondary(contentView, rowId);
+
+                if (contractedMessage == m) {
+                    contractedChildId = rowId;
+                }
+
+                i++;
+            }
+            // Clear the remaining views for reapply. Ensures that historic message views can
+            // reliably be identified as being GONE and having non-null text.
+            while (i < rowIds.length) {
+                int rowId = rowIds[i];
+                contentView.setTextViewText(rowId, null);
+                i++;
+            }
+
             // Record this here to allow transformation between the contracted and expanded views.
             contentView.setInt(R.id.notification_messaging, "setContractedChildId",
                     contractedChildId);
             return contentView;
         }
 
-        private CharSequence makeMessageLine(Message m) {
+        private CharSequence makeMessageLine(Message m, Builder builder) {
             BidiFormatter bidi = BidiFormatter.getInstance();
             SpannableStringBuilder sb = new SpannableStringBuilder();
+            boolean colorize = builder.isColorized();
             if (TextUtils.isEmpty(m.mSender)) {
                 CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
                 sb.append(bidi.unicodeWrap(replyName),
-                        makeFontColorSpan(mBuilder.resolveContrastColor()),
+                        makeFontColorSpan(colorize
+                                ? builder.getPrimaryTextColor()
+                                : mBuilder.resolveContrastColor()),
                         0 /* flags */);
             } else {
                 sb.append(bidi.unicodeWrap(m.mSender),
-                        makeFontColorSpan(Color.BLACK),
+                        makeFontColorSpan(colorize
+                                ? builder.getPrimaryTextColor()
+                                : Color.BLACK),
                         0 /* flags */);
             }
             CharSequence text = m.mText == null ? "" : m.mText;
@@ -5064,7 +5357,7 @@
                     : (m == null) ? null : m.mSender;
             CharSequence text = (m == null)
                     ? null
-                    : mConversationTitle != null ? makeMessageLine(m) : m.mText;
+                    : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
 
             return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
                     mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
@@ -5355,6 +5648,7 @@
                 if (!TextUtils.isEmpty(str)) {
                     contentView.setViewVisibility(rowIds[i], View.VISIBLE);
                     contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
+                    mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
                     contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
                     handleInboxImageMargin(contentView, rowIds[i], first);
                     if (first) {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 56ef791..be5f80a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -122,6 +122,7 @@
     private static final int DEFAULT_IMPORTANCE =
             NotificationManager.IMPORTANCE_UNSPECIFIED;
     private static final boolean DEFAULT_DELETED = false;
+    private static final boolean DEFAULT_SHOW_BADGE = true;
 
     private final String mId;
     private CharSequence mName;
@@ -133,7 +134,7 @@
     private long[] mVibration;
     private int mUserLockedFields;
     private boolean mVibrationEnabled;
-    private boolean mShowBadge;
+    private boolean mShowBadge = DEFAULT_SHOW_BADGE;
     private boolean mDeleted = DEFAULT_DELETED;
 
     /**
@@ -368,6 +369,8 @@
     /**
      * Returns whether notifications posted to this channel can appear as badges in a Launcher
      * application.
+     *
+     * Note that badging may be disabled for other reasons.
      */
     public boolean canShowBadge() {
         return mShowBadge;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a37f22b..5a75a67 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -112,6 +112,7 @@
 import android.os.storage.StorageManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
+import android.service.autofill.IAutoFillManagerService;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.telecom.TelecomManager;
@@ -119,7 +120,6 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.FontManager;
-import android.text.TextClassificationManager;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
@@ -127,7 +127,9 @@
 import android.view.WindowManagerImpl;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.CaptioningManager;
+import android.view.autofill.AutoFillManager;
 import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
 import android.view.textservice.TextServicesManager;
 
 import com.android.internal.app.IAppOpsService;
@@ -228,10 +230,10 @@
             }});
 
         registerService(Context.TEXT_CLASSIFICATION_SERVICE, TextClassificationManager.class,
-                new StaticServiceFetcher<TextClassificationManager>() {
+                new CachedServiceFetcher<TextClassificationManager>() {
             @Override
-            public TextClassificationManager createService() {
-                return new TextClassificationManager();
+            public TextClassificationManager createService(ContextImpl ctx) {
+                return new TextClassificationManager(ctx);
             }});
 
         registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
@@ -804,6 +806,14 @@
                         IBinder b = ServiceManager.getServiceOrThrow(Context.FONT_SERVICE);
                         return new FontManager(IFontManager.Stub.asInterface(b));
                     }});
+        registerService(Context.AUTO_FILL_MANAGER_SERVICE, AutoFillManager.class,
+                new CachedServiceFetcher<AutoFillManager>() {
+            @Override
+            public AutoFillManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                IBinder b = ServiceManager.getServiceOrThrow(Context.AUTO_FILL_MANAGER_SERVICE);
+                IAutoFillManagerService service = IAutoFillManagerService.Stub.asInterface(b);
+                return new AutoFillManager(ctx, service);
+            }});
     }
 
     /**
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 2572a20..af41a7d 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -271,6 +271,21 @@
     }
 
     /**
+     * Gets the valid inputs to {@link #setTheme(String)}.
+     * @hide
+     */
+    public String[] getAvailableThemes() {
+        if (mService != null) {
+            try {
+                return mService.getAvailableThemes();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the currently configured night mode.
      * <p>
      * May be one of:
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index aa56be6..0ff8550 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1091,6 +1091,30 @@
     public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
 
     /**
+     * Constant to indicate the feature of disabling the camera. Used as argument to
+     * {@link #createAdminSupportIntent(String)}.
+     * @see #setCameraDisabled(ComponentName, boolean)
+     */
+    public static final String POLICY_DISABLE_CAMERA = "policy_disable_camera";
+
+    /**
+     * Constant to indicate the feature of disabling screen captures. Used as argument to
+     * {@link #createAdminSupportIntent(String)}.
+     * @see #setScreenCaptureDisabled(ComponentName, boolean)
+     */
+    public static final String POLICY_DISABLE_SCREEN_CAPTURE = "policy_disable_screen_capture";
+
+    /**
+     * A String indicating a specific restricted feature. Can be a user restriction from the
+     * {@link UserManager}, e.g. {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the values
+     * {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+     * @see #createAdminSupportIntent(String)
+     * @hide
+     */
+    @TestApi
+    public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
+
+    /**
      * Activity action: have the user enter a new password. This activity should
      * be launched after using {@link #setPasswordQuality(ComponentName, int)},
      * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the user
@@ -1132,6 +1156,23 @@
             = "android.app.action.SHOW_DEVICE_MONITORING_DIALOG";
 
     /**
+     * Broadcast Action: Sent after application delegation scopes are changed. The new list of
+     * delegation scopes will be sent in an extra identified by the {@link #EXTRA_DELEGATION_SCOPES}
+     * key.
+     *
+     * <p class=”note”> Note: This is a protected intent that can only be sent by the system.</p>
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED =
+            "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+
+    /**
+     * A list of Strings corresponding to the delegation scopes given to an app in the
+     * {@link #ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED} broadcast.
+     */
+    public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
+
+    /**
      * Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
      * the parent profile to access intents sent from the managed profile.
      * That is, when an app in the managed profile calls
@@ -1194,6 +1235,53 @@
     public static final int PERMISSION_GRANT_STATE_DENIED = 2;
 
     /**
+     * Delegation of certificate installation and management. This scope grants access to the
+     * {@link #getInstalledCaCerts}, {@link #hasCaCertInstalled}, {@link #installCaCert},
+     * {@link #uninstallCaCert}, {@link #uninstallAllUserCaCerts} and {@link #installKeyPair} APIs.
+     */
+    public static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+
+    /**
+     * Delegation of application restrictions management. This scope grants access to the
+     * {@link #setApplicationRestrictions} and {@link #getApplicationRestrictions} APIs.
+     */
+    public static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+
+    /**
+     * Delegation of application uninstall block. This scope grants access to the
+     * {@link #setUninstallBlocked} API.
+     */
+    public static final String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+
+    /**
+     * Delegation of permission policy and permission grant state. This scope grants access to the
+     * {@link #setPermissionPolicy}, {@link #getPermissionGrantState},
+     * and {@link #setPermissionGrantState} APIs.
+     */
+    public static final String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
+
+    /**
+     * Delegation of package access state. This scope grants access to the
+     * {@link #isApplicationHidden}, {@link #setApplicationHidden}, {@link #isPackageSuspended}, and
+     * {@link #setPackagesSuspended} APIs.
+     */
+    public static final String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+
+    /**
+     * Delegation for enabling system apps. This scope grants access to the {@link #enableSystemApp}
+     * API.
+     */
+    public static final String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+
+    /**
+     * Delegation of management of uninstalled packages. This scope grants access to the
+     * {@code #setKeepUninstalledPackages} and {@code #getKeepUninstalledPackages} APIs.
+     * @hide
+     */
+    public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES =
+            "delegation-keep-uninstalled-packages";
+
+    /**
      * No management for current user in-effect. This is the default.
      * @hide
      */
@@ -3246,6 +3334,10 @@
     /**
      * Installs the given certificate as a user CA.
      *
+     * The caller must be a profile or device owner on that user, or a delegate package given the
+     * {@link #DELEGATION_CERT_INSTALL} scope via {@link #setDelegatedScopes}; otherwise a
+     * security exception will be thrown.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *              {@code null} if calling from a delegated certificate installer.
      * @param certBuffer encoded form of the certificate to install.
@@ -3254,12 +3346,14 @@
      *         interrupted, true otherwise.
      * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
      *         owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_CERT_INSTALL
      */
     public boolean installCaCert(@Nullable ComponentName admin, byte[] certBuffer) {
         throwIfParentInstance("installCaCert");
         if (mService != null) {
             try {
-                return mService.installCaCert(admin, certBuffer);
+                return mService.installCaCert(admin, mContext.getPackageName(), certBuffer);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -3270,18 +3364,24 @@
     /**
      * Uninstalls the given certificate from trusted user CAs, if present.
      *
+     * The caller must be a profile or device owner on that user, or a delegate package given the
+     * {@link #DELEGATION_CERT_INSTALL} scope via {@link #setDelegatedScopes}; otherwise a
+     * security exception will be thrown.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *              {@code null} if calling from a delegated certificate installer.
      * @param certBuffer encoded form of the certificate to remove.
      * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
      *         owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_CERT_INSTALL
      */
     public void uninstallCaCert(@Nullable ComponentName admin, byte[] certBuffer) {
         throwIfParentInstance("uninstallCaCert");
         if (mService != null) {
             try {
                 final String alias = getCaCertAlias(certBuffer);
-                mService.uninstallCaCerts(admin, new String[] {alias});
+                mService.uninstallCaCerts(admin, mContext.getPackageName(), new String[] {alias});
             } catch (CertificateException e) {
                 Log.w(TAG, "Unable to parse certificate", e);
             } catch (RemoteException e) {
@@ -3306,7 +3406,7 @@
         throwIfParentInstance("getInstalledCaCerts");
         if (mService != null) {
             try {
-                mService.enforceCanManageCaCerts(admin);
+                mService.enforceCanManageCaCerts(admin, mContext.getPackageName());
                 final TrustedCertificateStore certStore = new TrustedCertificateStore();
                 for (String alias : certStore.userAliases()) {
                     try {
@@ -3335,8 +3435,8 @@
         throwIfParentInstance("uninstallAllUserCaCerts");
         if (mService != null) {
             try {
-                mService.uninstallCaCerts(admin, new TrustedCertificateStore().userAliases()
-                        .toArray(new String[0]));
+                mService.uninstallCaCerts(admin, mContext.getPackageName(),
+                        new TrustedCertificateStore().userAliases() .toArray(new String[0]));
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
@@ -3356,7 +3456,7 @@
         throwIfParentInstance("hasCaCertInstalled");
         if (mService != null) {
             try {
-                mService.enforceCanManageCaCerts(admin);
+                mService.enforceCanManageCaCerts(admin, mContext.getPackageName());
                 return getCaCertAlias(certBuffer) != null;
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
@@ -3388,6 +3488,8 @@
      * @return {@code true} if the keys were installed, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
      *         owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_CERT_INSTALL
      */
     public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
             @NonNull Certificate cert, @NonNull String alias) {
@@ -3419,6 +3521,8 @@
      * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
      *         owner.
      * @see android.security.KeyChain#getCertificateChain
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_CERT_INSTALL
      */
     public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
             @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
@@ -3431,8 +3535,8 @@
             }
             final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
                     .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
-            return mService.installKeyPair(admin, pkcs8Key, pemCert, pemChain, alias,
-                    requestAccess);
+            return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
+                    pemChain, alias, requestAccess);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
@@ -3453,11 +3557,13 @@
      * @return {@code true} if the private key alias no longer exists, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
      *         owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_CERT_INSTALL
      */
     public boolean removeKeyPair(@Nullable ComponentName admin, @NonNull String alias) {
         throwIfParentInstance("removeKeyPair");
         try {
-            return mService.removeKeyPair(admin, alias);
+            return mService.removeKeyPair(admin, mContext.getPackageName(), alias);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3493,7 +3599,11 @@
      * @param installerPackage The package name of the certificate installer which will be given
      *            access. If {@code null} is given the current package will be cleared.
      * @throws SecurityException if {@code admin} is not a device or a profile owner.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #setDelegatedScopes}
+     * with the {@link #DELEGATION_CERT_INSTALL} scope instead.
      */
+    @Deprecated
     public void setCertInstallerPackage(@NonNull ComponentName admin, @Nullable String
             installerPackage) throws SecurityException {
         throwIfParentInstance("setCertInstallerPackage");
@@ -3507,14 +3617,19 @@
     }
 
     /**
-     * Called by a profile owner or device owner to retrieve the certificate installer for the user.
-     * null if none is set.
+     * Called by a profile owner or device owner to retrieve the certificate installer for the user,
+     * or {@code null} if none is set. If there are multiple delegates this function will return one
+     * of them.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @return The package name of the current delegated certificate installer, or {@code null} if
      *         none is set.
      * @throws SecurityException if {@code admin} is not a device or a profile owner.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatePackages}
+     * with the {@link #DELEGATION_CERT_INSTALL} scope instead.
      */
+    @Deprecated
     public @Nullable String getCertInstallerPackage(@NonNull ComponentName admin)
             throws SecurityException {
         throwIfParentInstance("getCertInstallerPackage");
@@ -3529,6 +3644,83 @@
     }
 
     /**
+     * Called by a profile owner or device owner to grant access to privileged APIs to another app.
+     * Granted APIs are determined by {@code scopes}, which is a list of the {@code DELEGATION_*}
+     * constants.
+     * <p>
+     * Delegated scopes are a per-user state. The delegated access is persistent until it is later
+     * cleared by calling this method with an empty {@code scopes} list or uninstalling the
+     * {@code delegatePackage}.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param delegatePackage The package name of the app which will be given access.
+     * @param scopes The groups of privileged APIs whose access should be granted to
+     *            {@code delegatedPackage}.
+     * @throws SecurityException if {@code admin} is not a device or a profile owner.
+     */
+     public void setDelegatedScopes(@NonNull ComponentName admin, @NonNull String delegatePackage,
+            @NonNull List<String> scopes) {
+        throwIfParentInstance("setDelegatedScopes");
+        if (mService != null) {
+            try {
+                mService.setDelegatedScopes(admin, delegatePackage, scopes);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Called by a profile owner or device owner to retrieve a list of the scopes given to a
+     * delegate package. Other apps can use this method to retrieve their own delegated scopes by
+     * passing {@code null} for {@code admin} and their own package name as
+     * {@code delegatedPackage}.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is {@code delegatedPackage}.
+     * @param delegatedPackage The package name of the app whose scopes should be retrieved.
+     * @return A list containing the scopes given to {@code delegatedPackage}.
+     * @throws SecurityException if {@code admin} is not a device or a profile owner.
+     */
+     @NonNull
+     public List<String> getDelegatedScopes(@NonNull ComponentName admin,
+             @NonNull String delegatedPackage) {
+         throwIfParentInstance("getDelegatedScopes");
+         if (mService != null) {
+             try {
+                 return mService.getDelegatedScopes(admin, delegatedPackage);
+             } catch (RemoteException e) {
+                 throw e.rethrowFromSystemServer();
+             }
+         }
+         return null;
+    }
+
+    /**
+     * Called by a profile owner or device owner to retrieve a list of delegate packages that were
+     * granted a delegation scope.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param delegationScope The scope whose delegates should be retrieved.
+     * @return A list of package names of the current delegated packages for
+               {@code delegationScope}.
+     * @throws SecurityException if {@code admin} is not a device or a profile owner.
+     */
+     @Nullable
+     public List<String> getDelegatePackages(@NonNull ComponentName admin,
+             @NonNull String delegationScope) {
+        throwIfParentInstance("getDelegatePackages");
+        if (mService != null) {
+            try {
+                return mService.getDelegatePackages(admin, delegationScope);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Called by a device or profile owner to configure an always-on VPN connection through a
      * specific application for the current user.
      *
@@ -4433,7 +4625,9 @@
     }
 
     /**
-     * Called by device or profile owners to suspend packages for this user.
+     * Called by device or profile owners to suspend packages for this user. This function can be
+     * called by a device owner, profile owner, or by a delegate given the
+     * {@link #DELEGATION_PACKAGE_ACCESS} scope via {@link #setDelegatedScopes}.
      * <p>
      * A suspended package will not be able to start activities. Its notifications will be hidden,
      * it will not show up in recents, will not be able to show toasts or dialogs or ring the
@@ -4443,20 +4637,24 @@
      * package will no longer be suspended. The admin can block this by using
      * {@link #setUninstallBlocked}.
      *
-     * @param admin The name of the admin component to check.
+     * @param admin The name of the admin component to check, or {@code null} if the caller is a
+     *            package access delegate.
      * @param packageNames The package names to suspend or unsuspend.
      * @param suspended If set to {@code true} than the packages will be suspended, if set to
      *            {@code false} the packages will be unsuspended.
      * @return an array of package names for which the suspended status is not set as requested in
      *         this method.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PACKAGE_ACCESS
      */
     public @NonNull String[] setPackagesSuspended(@NonNull ComponentName admin,
             @NonNull String[] packageNames, boolean suspended) {
         throwIfParentInstance("setPackagesSuspended");
         if (mService != null) {
             try {
-                return mService.setPackagesSuspended(admin, packageNames, suspended);
+                return mService.setPackagesSuspended(admin, mContext.getPackageName(), packageNames,
+                        suspended);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
@@ -4465,21 +4663,26 @@
     }
 
     /**
-     * Called by device or profile owners to determine if a package is suspended.
+     * Determine if a package is suspended. This function can be called by a device owner, profile
+     * owner, or by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+     * {@link #setDelegatedScopes}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is a package access delegate.
      * @param packageName The name of the package to retrieve the suspended status of.
      * @return {@code true} if the package is suspended or {@code false} if the package is not
      *         suspended, could not be found or an error occurred.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @throws NameNotFoundException if the package could not be found.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PACKAGE_ACCESS
      */
     public boolean isPackageSuspended(@NonNull ComponentName admin, String packageName)
             throws NameNotFoundException {
         throwIfParentInstance("isPackageSuspended");
         if (mService != null) {
             try {
-                return mService.isPackageSuspended(admin, packageName);
+                return mService.isPackageSuspended(admin, mContext.getPackageName(), packageName);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             } catch (IllegalArgumentException ex) {
@@ -4686,7 +4889,11 @@
      *            APIs. If {@code null} is given the current package will be cleared.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @throws NameNotFoundException if {@code packageName} is not found
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #setDelegatedScopes}
+     * with the {@link #DELEGATION_APP_RESTRICTIONS} scope instead.
      */
+    @Deprecated
     public void setApplicationRestrictionsManagingPackage(@NonNull ComponentName admin,
             @Nullable String packageName) throws NameNotFoundException {
         throwIfParentInstance("setApplicationRestrictionsManagingPackage");
@@ -4703,14 +4910,20 @@
 
     /**
      * Called by a profile owner or device owner to retrieve the application restrictions managing
-     * package for the current user, or {@code null} if none is set.
+     * package for the current user, or {@code null} if none is set. If there are multiple
+     * delegates this function will return one of them.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @return The package name allowed to manage application restrictions on the current user, or
      *         {@code null} if none is set.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatePackages}
+     * with the {@link #DELEGATION_APP_RESTRICTIONS} scope instead.
      */
-    public @Nullable String getApplicationRestrictionsManagingPackage(
+    @Deprecated
+    @Nullable
+    public String getApplicationRestrictionsManagingPackage(
             @NonNull ComponentName admin) {
         throwIfParentInstance("getApplicationRestrictionsManagingPackage");
         if (mService != null) {
@@ -4730,12 +4943,17 @@
      *
      * <p>This is done by comparing the calling Linux uid with the uid of the package specified by
      * that method.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatedScopes}
+     * instead.
      */
+    @Deprecated
     public boolean isCallerApplicationRestrictionsManagingPackage() {
         throwIfParentInstance("isCallerApplicationRestrictionsManagingPackage");
         if (mService != null) {
             try {
-                return mService.isCallerApplicationRestrictionsManagingPackage();
+                return mService.isCallerApplicationRestrictionsManagingPackage(
+                        mContext.getPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -4747,8 +4965,8 @@
      * Sets the application restrictions for a given target application running in the calling user.
      * <p>
      * The caller must be a profile or device owner on that user, or the package allowed to manage
-     * application restrictions via {@link #setApplicationRestrictionsManagingPackage}; otherwise a
-     * security exception will be thrown.
+     * application restrictions via {@link #setDelegatedScopes} with the
+     * {@link #DELEGATION_APP_RESTRICTIONS} scope; otherwise a security exception will be thrown.
      * <p>
      * The provided {@link Bundle} consists of key-value pairs, where the types of values may be:
      * <ul>
@@ -4775,7 +4993,8 @@
      * @param settings A {@link Bundle} to be parsed by the receiving application, conveying a new
      *            set of active restrictions.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
-     * @see #setApplicationRestrictionsManagingPackage
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_APP_RESTRICTIONS
      * @see UserManager#KEY_RESTRICTIONS_PENDING
      */
     @WorkerThread
@@ -4784,7 +5003,8 @@
         throwIfParentInstance("setApplicationRestrictions");
         if (mService != null) {
             try {
-                mService.setApplicationRestrictions(admin, packageName, settings);
+                mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName,
+                        settings);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5332,19 +5552,24 @@
     }
 
     /**
-     * Called by a device owner to get the list of apps to keep around as APKs even if no user has
-     * currently installed it.
+     * Get the list of apps to keep around as APKs even if no user has currently installed it. This
+     * function can be called by a device owner or by a delegate given the
+     * {@link #DELEGATION_KEEP_UNINSTALLED_PACKAGES} scope via {@link #setDelegatedScopes}.
+     * <p>
+     * Please note that packages returned in this method are not automatically pre-cached.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is a keep uninstalled packages delegate.
      * @return List of package names to keep cached.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_KEEP_UNINSTALLED_PACKAGES
      * @hide
      */
-    public @Nullable List<String> getKeepUninstalledPackages(@NonNull ComponentName admin) {
+    public @Nullable List<String> getKeepUninstalledPackages(@Nullable ComponentName admin) {
         throwIfParentInstance("getKeepUninstalledPackages");
         if (mService != null) {
             try {
-                return mService.getKeepUninstalledPackages(admin);
+                return mService.getKeepUninstalledPackages(admin, mContext.getPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5353,23 +5578,27 @@
     }
 
     /**
-     * Called by a device owner to set a list of apps to keep around as APKs even if no user has
-     * currently installed it.
+     * Set a list of apps to keep around as APKs even if no user has currently installed it. This
+     * function can be called by a device owner or by a delegate given the
+     * {@link #DELEGATION_KEEP_UNINSTALLED_PACKAGES} scope via {@link #setDelegatedScopes}.
      *
      * <p>Please note that setting this policy does not imply that specified apps will be
      * automatically pre-cached.</p>
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is a keep uninstalled packages delegate.
      * @param packageNames List of package names to keep cached.
      * @throws SecurityException if {@code admin} is not a device owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_KEEP_UNINSTALLED_PACKAGES
      * @hide
      */
-    public void setKeepUninstalledPackages(@NonNull ComponentName admin,
+    public void setKeepUninstalledPackages(@Nullable ComponentName admin,
             @NonNull List<String> packageNames) {
         throwIfParentInstance("setKeepUninstalledPackages");
         if (mService != null) {
             try {
-                mService.setKeepUninstalledPackages(admin, packageNames);
+                mService.setKeepUninstalledPackages(admin, mContext.getPackageName(), packageNames);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5523,8 +5752,8 @@
      * user.
      * <p>
      * The caller must be a profile or device owner on that user, or the package allowed to manage
-     * application restrictions via {@link #setApplicationRestrictionsManagingPackage}; otherwise a
-     * security exception will be thrown.
+     * application restrictions via {@link #setDelegatedScopes} with the
+     * {@link #DELEGATION_APP_RESTRICTIONS} scope; otherwise a security exception will be thrown.
      *
      * <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread
      *
@@ -5535,7 +5764,8 @@
      *         {@link DevicePolicyManager#setApplicationRestrictions} was called, or an empty
      *         {@link Bundle} if no restrictions have been set.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
-     * @see #setApplicationRestrictionsManagingPackage
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_APP_RESTRICTIONS
      */
     @WorkerThread
     public @NonNull Bundle getApplicationRestrictions(
@@ -5543,7 +5773,8 @@
         throwIfParentInstance("getApplicationRestrictions");
         if (mService != null) {
             try {
-                return mService.getApplicationRestrictions(admin, packageName);
+                return mService.getApplicationRestrictions(admin, mContext.getPackageName(),
+                        packageName);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5620,22 +5851,55 @@
     }
 
     /**
-     * Called by profile or device owners to hide or unhide packages. When a package is hidden it is
-     * unavailable for use, but the data and actual package file remain.
+     * Called by any app to display a support dialog when a feature was disabled by an admin.
+     * This returns an intent that can be used with {@link Context#startActivity(Intent)} to
+     * display the dialog. It will tell the user that the feature indicated by {@code restriction}
+     * was disabled by an admin, and include a link for more information. The default content of
+     * the dialog can be changed by the restricting admin via
+     * {@link #setShortSupportMessage(ComponentName, CharSequence)}. If the restriction is not
+     * set (i.e. the feature is available), then the return value will be {@code null}.
+     * @param restriction Indicates for which feature the dialog should be displayed. Can be a
+     *            user restriction from {@link UserManager}, e.g.
+     *            {@link UserManager#DISALLOW_ADJUST_VOLUME}, or one of the constants
+     *            {@link #POLICY_DISABLE_CAMERA} or {@link #POLICY_DISABLE_SCREEN_CAPTURE}.
+     * @return Intent An intent to be used to start the dialog-activity if the restriction is
+     *            set by an admin, or null if the restriction does not exist or no admin set it.
+     */
+    public Intent createAdminSupportIntent(@NonNull String restriction) {
+        throwIfParentInstance("createAdminSupportIntent");
+        if (mService != null) {
+            try {
+                return mService.createAdminSupportIntent(restriction);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
+     * actual package file remain. This function can be called by a device owner, profile owner, or
+     * by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+     * {@link #setDelegatedScopes}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is a package access delegate.
      * @param packageName The name of the package to hide or unhide.
      * @param hidden {@code true} if the package should be hidden, {@code false} if it should be
      *            unhidden.
      * @return boolean Whether the hidden setting of the package was successfully updated.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PACKAGE_ACCESS
      */
     public boolean setApplicationHidden(@NonNull ComponentName admin, String packageName,
             boolean hidden) {
         throwIfParentInstance("setApplicationHidden");
         if (mService != null) {
             try {
-                return mService.setApplicationHidden(admin, packageName, hidden);
+                return mService.setApplicationHidden(admin, mContext.getPackageName(), packageName,
+                        hidden);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5644,18 +5908,23 @@
     }
 
     /**
-     * Called by profile or device owners to determine if a package is hidden.
+     * Determine if a package is hidden. This function can be called by a device owner, profile
+     * owner, or by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+     * {@link #setDelegatedScopes}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is a package access delegate.
      * @param packageName The name of the package to retrieve the hidden status of.
      * @return boolean {@code true} if the package is hidden, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PACKAGE_ACCESS
      */
     public boolean isApplicationHidden(@NonNull ComponentName admin, String packageName) {
         throwIfParentInstance("isApplicationHidden");
         if (mService != null) {
             try {
-                return mService.isApplicationHidden(admin, packageName);
+                return mService.isApplicationHidden(admin, mContext.getPackageName(), packageName);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5664,18 +5933,22 @@
     }
 
     /**
-     * Called by profile or device owners to re-enable a system app that was disabled by default
-     * when the user was initialized.
+     * Re-enable a system app that was disabled by default when the user was initialized. This
+     * function can be called by a device owner, profile owner, or by a delegate given the
+     * {@link #DELEGATION_ENABLE_SYSTEM_APP} scope via {@link #setDelegatedScopes}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is an enable system app delegate.
      * @param packageName The package to be re-enabled in the calling profile.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PACKAGE_ACCESS
      */
     public void enableSystemApp(@NonNull ComponentName admin, String packageName) {
         throwIfParentInstance("enableSystemApp");
         if (mService != null) {
             try {
-                mService.enableSystemApp(admin, packageName);
+                mService.enableSystemApp(admin, mContext.getPackageName(), packageName);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5683,20 +5956,24 @@
     }
 
     /**
-     * Called by profile or device owners to re-enable system apps by intent that were disabled by
-     * default when the user was initialized.
+     * Re-enable system apps by intent that were disabled by default when the user was initialized.
+     * This function can be called by a device owner, profile owner, or by a delegate given the
+     * {@link #DELEGATION_ENABLE_SYSTEM_APP} scope via {@link #setDelegatedScopes}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if the caller is an enable system app delegate.
      * @param intent An intent matching the app(s) to be installed. All apps that resolve for this
      *            intent will be re-enabled in the calling profile.
      * @return int The number of activities that matched the intent and were installed.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PACKAGE_ACCESS
      */
     public int enableSystemApp(@NonNull ComponentName admin, Intent intent) {
         throwIfParentInstance("enableSystemApp");
         if (mService != null) {
             try {
-                return mService.enableSystemAppWithIntent(admin, intent);
+                return mService.enableSystemAppWithIntent(admin, mContext.getPackageName(), intent);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -5974,19 +6251,25 @@
     }
 
     /**
-     * Called by profile or device owners to change whether a user can uninstall a package.
+     * Change whether a user can uninstall a package. This function can be called by a device owner,
+     * profile owner, or by a delegate given the {@link #DELEGATION_BLOCK_UNINSTALL} scope via
+     * {@link #setDelegatedScopes}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *             {@code null} if the caller is a block uninstall delegate.
      * @param packageName package to change.
      * @param uninstallBlocked true if the user shouldn't be able to uninstall the package.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_BLOCK_UNINSTALL
      */
-    public void setUninstallBlocked(@NonNull ComponentName admin, String packageName,
+    public void setUninstallBlocked(@Nullable ComponentName admin, String packageName,
             boolean uninstallBlocked) {
         throwIfParentInstance("setUninstallBlocked");
         if (mService != null) {
             try {
-                mService.setUninstallBlocked(admin, packageName, uninstallBlocked);
+                mService.setUninstallBlocked(admin, mContext.getPackageName(), packageName,
+                    uninstallBlocked);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
@@ -6272,12 +6555,14 @@
     }
 
     /**
-     * Called by profile or device owners to set the default response for future runtime permission
-     * requests by applications. The policy can allow for normal operation which prompts the user to
-     * grant a permission, or can allow automatic granting or denying of runtime permission requests
-     * by an application. This also applies to new permissions declared by app updates. When a
-     * permission is denied or granted this way, the effect is equivalent to setting the permission
-     * grant state via {@link #setPermissionGrantState}.
+     * Set the default response for future runtime permission requests by applications. This
+     * function can be called by a device owner, profile owner, or by a delegate given the
+     * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
+     * The policy can allow for normal operation which prompts the user to grant a permission, or
+     * can allow automatic granting or denying of runtime permission requests by an application.
+     * This also applies to new permissions declared by app updates. When a permission is denied or
+     * granted this way, the effect is equivalent to setting the permission * grant state via
+     * {@link #setPermissionGrantState}.
      * <p/>
      * As this policy only acts on runtime permission requests, it only applies to applications
      * built with a {@code targetSdkVersion} of {@link android.os.Build.VERSION_CODES#M} or later.
@@ -6287,11 +6572,13 @@
      *            {@link #PERMISSION_POLICY_AUTO_GRANT} and {@link #PERMISSION_POLICY_AUTO_DENY}.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @see #setPermissionGrantState
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PERMISSION_GRANT
      */
     public void setPermissionPolicy(@NonNull ComponentName admin, int policy) {
         throwIfParentInstance("setPermissionPolicy");
         try {
-            mService.setPermissionPolicy(admin, policy);
+            mService.setPermissionPolicy(admin, mContext.getPackageName(), policy);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -6300,6 +6587,7 @@
     /**
      * Returns the current runtime permission policy set by the device or profile owner. The
      * default is {@link #PERMISSION_POLICY_PROMPT}.
+     *
      * @param admin Which profile or device owner this request is associated with.
      * @return the current policy for future permission requests.
      */
@@ -6319,7 +6607,8 @@
      * cannot manage it through the UI, and {@link #PERMISSION_GRANT_STATE_GRANTED granted} in which
      * the permission is granted and the user cannot manage it through the UI. This might affect all
      * permissions in a group that the runtime permission belongs to. This method can only be called
-     * by a profile or device owner.
+     * by a profile owner, device owner, or a delegate given the
+     * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
      * <p/>
      * Setting the grant state to {@link #PERMISSION_GRANT_STATE_DEFAULT default} does not revoke
      * the permission. It retains the previous grant, if any.
@@ -6338,21 +6627,27 @@
      * @see #PERMISSION_GRANT_STATE_DENIED
      * @see #PERMISSION_GRANT_STATE_DEFAULT
      * @see #PERMISSION_GRANT_STATE_GRANTED
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PERMISSION_GRANT
      */
     public boolean setPermissionGrantState(@NonNull ComponentName admin, String packageName,
             String permission, int grantState) {
         throwIfParentInstance("setPermissionGrantState");
         try {
-            return mService.setPermissionGrantState(admin, packageName, permission, grantState);
+            return mService.setPermissionGrantState(admin, mContext.getPackageName(), packageName,
+                    permission, grantState);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Returns the current grant state of a runtime permission for a specific application.
+     * Returns the current grant state of a runtime permission for a specific application. This
+     * function can be called by a device owner, profile owner, or by a delegate given the
+     * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
      *
-     * @param admin Which profile or device owner this request is associated with.
+     * @param admin Which profile or device owner this request is associated with, or {@code null}
+     *            if the caller is a permission grant delegate.
      * @param packageName The application to check the grant state for.
      * @param permission The permission to check for.
      * @return the current grant state specified by device policy. If the profile or device owner
@@ -6367,12 +6662,15 @@
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @see #setPermissionGrantState(ComponentName, String, String, int)
      * @see PackageManager#checkPermission(String, String)
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_PERMISSION_GRANT
      */
     public int getPermissionGrantState(@Nullable ComponentName admin, String packageName,
             String permission) {
         throwIfParentInstance("getPermissionGrantState");
         try {
-            return mService.getPermissionGrantState(admin, packageName, permission);
+            return mService.getPermissionGrantState(admin, mContext.getPackageName(), packageName,
+                    permission);
         } 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 80ef557..79fe10e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -150,20 +150,24 @@
     void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo);
     CharSequence getDeviceOwnerLockScreenInfo();
 
-    String[] setPackagesSuspended(in ComponentName admin, in String[] packageNames, boolean suspended);
-    boolean isPackageSuspended(in ComponentName admin, String packageName);
+    String[] setPackagesSuspended(in ComponentName admin, in String callerPackage, in String[] packageNames, boolean suspended);
+    boolean isPackageSuspended(in ComponentName admin, in String callerPackage, String packageName);
 
-    boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
-    void uninstallCaCerts(in ComponentName admin, in String[] aliases);
-    void enforceCanManageCaCerts(in ComponentName admin);
+    boolean installCaCert(in ComponentName admin, String callerPackage, in byte[] certBuffer);
+    void uninstallCaCerts(in ComponentName admin, String callerPackage, in String[] aliases);
+    void enforceCanManageCaCerts(in ComponentName admin, in String callerPackage);
     boolean approveCaCert(in String alias, int userHandle, boolean approval);
     boolean isCaCertApproved(in String alias, int userHandle);
 
-    boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer,
-            in byte[] certChainBuffer, String alias, boolean requestAccess);
-    boolean removeKeyPair(in ComponentName who, String alias);
+    boolean installKeyPair(in ComponentName who, in String callerPackage, in byte[] privKeyBuffer,
+            in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess);
+    boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
     void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
 
+    void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
+    List<String> getDelegatedScopes(in ComponentName who, String delegatePackage);
+    List<String> getDelegatePackages(in ComponentName who, String scope);
+
     void setCertInstallerPackage(in ComponentName who, String installerPackage);
     String getCertInstallerPackage(in ComponentName who);
 
@@ -173,11 +177,11 @@
     void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
     void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
 
-    void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
-    Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+    void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings);
+    Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName);
     boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName);
     String getApplicationRestrictionsManagingPackage(in ComponentName admin);
-    boolean isCallerApplicationRestrictionsManagingPackage();
+    boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage);
 
     void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
     ComponentName getRestrictionsProvider(int userHandle);
@@ -197,15 +201,16 @@
     List getPermittedInputMethodsForCurrentUser();
     boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
 
-    boolean setApplicationHidden(in ComponentName admin, in String packageName, boolean hidden);
-    boolean isApplicationHidden(in ComponentName admin, in String packageName);
+    Intent createAdminSupportIntent(in String restriction);
+    boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden);
+    boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName);
 
     UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags);
     boolean removeUser(in ComponentName who, in UserHandle userHandle);
     boolean switchUser(in ComponentName who, in UserHandle userHandle);
 
-    void enableSystemApp(in ComponentName admin, in String packageName);
-    int enableSystemAppWithIntent(in ComponentName admin, in Intent intent);
+    void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName);
+    int enableSystemAppWithIntent(in ComponentName admin, in String callerPackage, in Intent intent);
 
     void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled);
     String[] getAccountTypesWithManagementDisabled();
@@ -223,7 +228,7 @@
 
     void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userId);
 
-    void setUninstallBlocked(in ComponentName admin, in String packageName, boolean uninstallBlocked);
+    void setUninstallBlocked(in ComponentName admin, in String callerPackage, in String packageName, boolean uninstallBlocked);
     boolean isUninstallBlocked(in ComponentName admin, in String packageName);
 
     void setCrossProfileCallerIdDisabled(in ComponentName who, boolean disabled);
@@ -267,15 +272,15 @@
     void notifyPendingSystemUpdate(in SystemUpdateInfo info);
     SystemUpdateInfo getPendingSystemUpdate(in ComponentName admin);
 
-    void setPermissionPolicy(in ComponentName admin, int policy);
+    void setPermissionPolicy(in ComponentName admin, in String callerPackage, int policy);
     int  getPermissionPolicy(in ComponentName admin);
-    boolean setPermissionGrantState(in ComponentName admin, String packageName,
+    boolean setPermissionGrantState(in ComponentName admin, in String callerPackage, String packageName,
             String permission, int grantState);
-    int getPermissionGrantState(in ComponentName admin, String packageName, String permission);
+    int getPermissionGrantState(in ComponentName admin, in String callerPackage, String packageName, String permission);
     boolean isProvisioningAllowed(String action, String packageName);
     int checkProvisioningPreCondition(String action, String packageName);
-    void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
-    List<String> getKeepUninstalledPackages(in ComponentName admin);
+    void setKeepUninstalledPackages(in ComponentName admin, in String callerPackage, in List<String> packageList);
+    List<String> getKeepUninstalledPackages(in ComponentName admin, in String callerPackage);
     boolean isManagedProfile(in ComponentName admin);
     boolean isSystemOnlyUser(in ComponentName admin);
     String getWifiMacAddress(in ComponentName admin);
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 540683d..f0abe33 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -17,6 +17,7 @@
 package android.app.backup;
 
 import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
@@ -157,6 +158,25 @@
     @SystemApi
     public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
 
+
+    /**
+     * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)}
+     * if the requested transport is unavailable.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_TRANSPORT_UNAVAILABLE = -1;
+
+    /**
+     * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)} if the
+     * requested transport is not a valid BackupTransport.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ERROR_TRANSPORT_INVALID = -2;
+
     private Context mContext;
     private static IBackupManager sService;
 
@@ -390,17 +410,20 @@
     }
 
     /**
-     * Specify the current backup transport.  Callers must hold the
-     * android.permission.BACKUP permission to use this method.
+     * Specify the current backup transport.
+     *
+     * <p> Callers must hold the android.permission.BACKUP permission to use this method.
      *
      * @param transport The name of the transport to select.  This should be one
-     *   of the names returned by {@link #listAllTransports()}.
+     *   of the names returned by {@link #listAllTransports()}. This is the String returned by
+     *   {@link BackupTransport#name()} for the particular transport.
      * @return The name of the previously selected transport.  If the given transport
      *   name is not one of the currently available transports, no change is made to
      *   the current transport setting and the method returns null.
      *
      * @hide
      */
+    @Deprecated
     @SystemApi
     public String selectBackupTransport(String transport) {
         checkServiceBinder();
@@ -415,6 +438,34 @@
     }
 
     /**
+     * Specify the current backup transport and get notified when the transport is ready to be used.
+     * This method is async because BackupManager might need to bind to the specified transport
+     * which is in a separate process.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @param transport ComponentName of the service hosting the transport. This is different from
+     *                  the transport's name that is returned by {@link BackupTransport#name()}.
+     * @param listener A listener object to get a callback on the transport being selected.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void selectBackupTransport(ComponentName transport,
+            SelectBackupTransportCallback listener) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                SelectTransportListenerWrapper wrapper = listener == null ?
+                        null : new SelectTransportListenerWrapper(mContext, listener);
+                sService.selectBackupTransportAsync(transport, wrapper);
+            } catch (RemoteException e) {
+                Log.e(TAG, "selectBackupTransportAsync() couldn't connect");
+            }
+        }
+    }
+
+    /**
      * Schedule an immediate backup attempt for all pending key/value updates.  This
      * is primarily intended for transports to use when they detect a suitable
      * opportunity for doing a backup pass.  If there are no pending updates to
@@ -598,4 +649,35 @@
                 mHandler.obtainMessage(MSG_FINISHED, status, 0));
         }
     }
+
+    private class SelectTransportListenerWrapper extends ISelectBackupTransportCallback.Stub {
+
+        private final Handler mHandler;
+        private final SelectBackupTransportCallback mListener;
+
+        SelectTransportListenerWrapper(Context context, SelectBackupTransportCallback listener) {
+            mHandler = new Handler(context.getMainLooper());
+            mListener = listener;
+        }
+
+        @Override
+        public void onSuccess(final String transportName) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onSuccess(transportName);
+                }
+            });
+        }
+
+        @Override
+        public void onFailure(final int reason) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onFailure(reason);
+                }
+            });
+        }
+    }
 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index fe23c28..1657e2e 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -19,8 +19,10 @@
 import android.app.backup.IBackupObserver;
 import android.app.backup.IFullBackupRestoreObserver;
 import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
 import android.os.ParcelFileDescriptor;
 import android.content.Intent;
+import android.content.ComponentName;
 
 /**
  * Direct interface to the Backup Manager Service that applications invoke on.  The only
@@ -217,6 +219,8 @@
      */
     String[] listAllTransports();
 
+    ComponentName[] listAllTransportComponents();
+
     /**
      * Retrieve the list of whitelisted transport components.  Callers do </i>not</i> need
      * any special permission.
@@ -238,6 +242,21 @@
     String selectBackupTransport(String transport);
 
     /**
+     * Specify the current backup transport and get notified when the transport is ready to be used.
+     * This method is async because BackupManager might need to bind to the specified transport
+     * which is in a separate process.
+     *
+     * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+     *
+     * @param transport ComponentName of the service hosting the transport. This is different from
+     *                  the transport's name that is returned by {@link BackupTransport#name()}.
+     * @param listener A listener object to get a callback on the transport being selected.
+     *
+     * @hide
+     */
+    void selectBackupTransportAsync(in ComponentName transport, ISelectBackupTransportCallback listener);
+
+    /**
      * Get the configuration Intent, if any, from the given transport.  Callers must
      * hold the android.permission.BACKUP permission in order to use this method.
      *
diff --git a/core/java/android/app/backup/ISelectBackupTransportCallback.aidl b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl
new file mode 100644
index 0000000..5de7c5e
--- /dev/null
+++ b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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 android.app.backup;
+
+/**
+ * Callback class for receiving success or failure callbacks on selecting a backup transport. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+oneway interface ISelectBackupTransportCallback {
+
+    /**
+     * Called when BackupManager has successfully bound to the requested transport.
+     *
+     * @param transportName Name of the selected transport. This is the String returned by
+     *        {@link BackupTransport#name()}.
+     */
+    void onSuccess(String transportName);
+
+    /**
+     * Called when BackupManager fails to bind to the requested transport.
+     *
+     * @param reason Error code denoting reason for failure.
+     */
+    void onFailure(int reason);
+}
diff --git a/core/java/android/app/backup/SelectBackupTransportCallback.java b/core/java/android/app/backup/SelectBackupTransportCallback.java
new file mode 100644
index 0000000..0c8a0dc
--- /dev/null
+++ b/core/java/android/app/backup/SelectBackupTransportCallback.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.app.backup;
+
+import android.annotation.SystemApi;
+
+/**
+ * Callback class for receiving success or failure callbacks on selecting a backup transport. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SelectBackupTransportCallback {
+
+    /**
+     * Called when BackupManager has successfully bound to the requested transport.
+     *
+     * @param transportName Name of the selected transport. This is the String returned by
+     *        {@link BackupTransport#name()}.
+     */
+    public void onSuccess(String transportName){}
+
+    /**
+     * Called when BackupManager fails to bind to the requested transport.
+     *
+     * @param reason Error code denoting reason for failure.
+     */
+    public void onFailure(int reason){}
+}
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 7ed827c..3a87cb3 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -414,7 +414,11 @@
             return (Boolean) value;
         } catch (ClassCastException e) {
             if (value instanceof CharSequence) {
-                return Boolean.valueOf(value.toString());
+                // Note that we also check against 1 here because SQLite's internal representation
+                // for booleans is an integer with a value of 0 or 1. Without this check, boolean
+                // values obtained via DatabaseUtils#cursorRowToContentValues will always return
+                // false.
+                return Boolean.valueOf(value.toString()) || "1".equals(value);
             } else if (value instanceof Number) {
                 return ((Number) value).intValue() != 0;
             } else {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f41d7f2..f00f605 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -34,9 +34,7 @@
 import android.annotation.UserIdInt;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
-import android.app.LoadedApk;
 import android.app.Notification;
-import android.app.admin.DevicePolicyManager;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
@@ -64,6 +62,7 @@
 import android.view.DisplayAdjustments;
 import android.view.ViewDebug;
 import android.view.WindowManager;
+import android.view.textclassifier.TextClassificationManager;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -3348,10 +3347,10 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
-     * {@link android.text.TextClassificationManager} for text classification services.
+     * {@link TextClassificationManager} for text classification services.
      *
      * @see #getSystemService
-     * @see android.text.TextClassificationManager
+     * @see TextClassificationManager
      */
     public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
 
@@ -4254,6 +4253,20 @@
             int flags) throws PackageManager.NameNotFoundException;
 
     /**
+     * Return a new Context object for the given split name. The new Context has a ClassLoader and
+     * Resources object that can access the split's and all of its dependencies' code/resources.
+     * Each call to this method returns a new instance of a Context object;
+     * Context objects are not shared, however common state (ClassLoader, other Resources for
+     * the same split) may be so the Context itself can be fairly lightweight.
+     *
+     * @param splitName The name of the split to include, as declared in the split's
+     *                  <code>AndroidManifest.xml</code>.
+     * @return A {@link Context} with the given split's code and/or resources loaded.
+     */
+    public abstract Context createContextForSplit(String splitName)
+            throws PackageManager.NameNotFoundException;
+
+    /**
      * Get the userId associated with this context
      * @return user id
      *
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index b131ecc..546bfc4 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -16,7 +16,6 @@
 
 package android.content;
 
-import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
@@ -826,6 +825,13 @@
 
     /** @hide */
     @Override
+    public Context createContextForSplit(String splitName)
+            throws PackageManager.NameNotFoundException {
+        return mBase.createContextForSplit(splitName);
+    }
+
+    /** @hide */
+    @Override
     public int getUserId() {
         return mBase.getUserId();
     }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c550094..44e106e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1628,6 +1628,19 @@
     public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
 
     /**
+     * Activity Action: Launch application uninstaller.
+     * <p>
+     * Input: The data must be a package: URI whose scheme specific part is
+     * the package name of the current installed package to be uninstalled.
+     * You can optionally supply {@link #EXTRA_RETURN_RESULT}.
+     * <p>
+     * Output: Nothing.
+     * </p>
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CLEAR_PACKAGE = "android.intent.action.CLEAR_PACKAGE";
+
+    /**
      * Specify whether the package should be uninstalled for all users.
      * @hide because these should not be part of normal application flow.
      */
@@ -3966,6 +3979,12 @@
     public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN";
 
     /**
+     * The version code of the app to install components from.
+     * @hide
+     */
+    public static final String EXTRA_VERSION_CODE = "android.intent.extra.VERSION_CODE";
+
+    /**
      * A Bundle forming a mapping of potential target package names to different extras Bundles
      * to add to the default intent extras in {@link #EXTRA_INTENT} when used with
      * {@link #ACTION_CHOOSER}. Each key should be a package name. The package need not
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 4bd091d..92cb709 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -177,10 +177,14 @@
      */
     public static final int RESIZE_MODE_RESIZEABLE = 2;
     /**
-     * Activity is resizeable and supported picture-in-picture mode.
+     * Activity is resizeable and supported picture-in-picture mode.  This flag is now deprecated
+     * since activities do not need to be resizeable to support picture-in-picture.
+     * See {@link #FLAG_SUPPORTS_PICTURE_IN_PICTURE}.
+     *
      * @hide
+     * @deprecated
      */
-    public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE = 3;
+    public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED = 3;
     /**
      * Activity does not support resizing, but we are forcing it to be resizeable. Only affects
      * certain pre-N apps where we force them to be resizeable.
@@ -220,6 +224,44 @@
     public String requestedVrComponent;
 
     /**
+     * Value for {@link #colorMode} indicating that the activity should use the
+     * default color mode (sRGB, low dynamic range).
+     *
+     * @see android.R.attr#colorMode
+     */
+    public static final int COLOR_MODE_DEFAULT = 0;
+    /**
+     * Value of {@link #colorMode} indicating that the activity should use a
+     * wide color gamut if the presentation display supports it.
+     *
+     * @see android.R.attr#colorMode
+     */
+    public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1;
+    /**
+     * Value of {@link #colorMode} indicating that the activity should use a
+     * high dynamic range if the presentation display supports it.
+     *
+     * @see android.R.attr#colorMode
+     */
+    public static final int COLOR_MODE_HDR = 2;
+
+    /** @hide */
+    @IntDef({
+        COLOR_MODE_DEFAULT,
+        COLOR_MODE_WIDE_COLOR_GAMUT,
+        COLOR_MODE_HDR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ColorMode {}
+
+    /**
+     * The color mode requested by this activity. The target display may not be
+     * able to honor the request.
+     */
+    @ColorMode
+    public int colorMode = COLOR_MODE_DEFAULT;
+
+    /**
      * Bit in {@link #flags} indicating whether this activity is able to
      * run in multiple processes.  If
      * true, the system may instantiate it in the some process as the
@@ -369,6 +411,13 @@
     public static final int FLAG_VISIBLE_TO_EPHEMERAL = 0x100000;
 
     /**
+     * Bit in {@link #flags} indicating if the activity supports picture-in-picture mode.
+     * See {@link android.R.attr#supportsPictureInPicture}.
+     * @hide
+     */
+    public static final int FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x200000;
+
+    /**
      * @hide Bit in {@link #flags}: If set, this component will only be seen
      * by the system user.  Only works with broadcast receivers.  Set from the
      * android.R.attr#systemUserOnly attribute.
@@ -566,7 +615,7 @@
                     CONFIG_SMALLEST_SCREEN_SIZE,
                     CONFIG_DENSITY,
                     CONFIG_LAYOUT_DIRECTION,
-                    CONFIG_COLORIMETRY,
+                    CONFIG_COLOR_MODE,
                     CONFIG_FONT_SCALE,
             })
     @Retention(RetentionPolicy.SOURCE)
@@ -675,7 +724,7 @@
      * can itself handle the change to the display color gamut or dynamic
      * range. Set from the {@link android.R.attr#configChanges} attribute.
      */
-    public static final int CONFIG_COLORIMETRY = 0x4000;
+    public static final int CONFIG_COLOR_MODE = 0x4000;
     /**
      * Bit in {@link #configChanges} that indicates that the activity
      * can itself handle asset path changes.  Set from the {@link android.R.attr#configChanges}
@@ -713,7 +762,7 @@
         Configuration.NATIVE_CONFIG_SMALLEST_SCREEN_SIZE,   // SMALLEST SCREEN SIZE
         Configuration.NATIVE_CONFIG_DENSITY,                // DENSITY
         Configuration.NATIVE_CONFIG_LAYOUTDIR,              // LAYOUT DIRECTION
-        Configuration.NATIVE_CONFIG_COLORIMETRY,            // COLORIMETRY
+        Configuration.NATIVE_CONFIG_COLOR_MODE,             // COLOR_MODE
     };
 
     /**
@@ -770,7 +819,7 @@
      * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
      * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
      * {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION} and
-     * {@link #CONFIG_COLORIMETRY}.
+     * {@link #CONFIG_COLOR_MODE}.
      * Set from the {@link android.R.attr#configChanges} attribute.
      */
     public int configChanges;
@@ -873,6 +922,7 @@
         resizeMode = orig.resizeMode;
         requestedVrComponent = orig.requestedVrComponent;
         rotationAnimation = orig.rotationAnimation;
+        colorMode = orig.colorMode;
     }
 
     /**
@@ -926,10 +976,17 @@
                 || screenOrientation == SCREEN_ORIENTATION_USER_PORTRAIT;
     }
 
+    /**
+     * Returns true if the activity supports picture-in-picture.
+     * @hide
+     */
+    public boolean supportsPictureInPicture() {
+        return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0;
+    }
+
     /** @hide */
     public static boolean isResizeableMode(int mode) {
         return mode == RESIZE_MODE_RESIZEABLE
-                || mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE
                 || mode == RESIZE_MODE_FORCE_RESIZEABLE
                 || mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
                 || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
@@ -953,8 +1010,6 @@
                 return "RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION";
             case RESIZE_MODE_RESIZEABLE:
                 return "RESIZE_MODE_RESIZEABLE";
-            case RESIZE_MODE_RESIZEABLE_AND_PIPABLE:
-                return "RESIZE_MODE_RESIZEABLE_AND_PIPABLE";
             case RESIZE_MODE_FORCE_RESIZEABLE:
                 return "RESIZE_MODE_FORCE_RESIZEABLE";
             case RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY:
@@ -1055,6 +1110,7 @@
         dest.writeInt(resizeMode);
         dest.writeString(requestedVrComponent);
         dest.writeInt(rotationAnimation);
+        dest.writeInt(colorMode);
     }
 
     public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1090,6 +1146,7 @@
         resizeMode = source.readInt();
         requestedVrComponent = source.readString();
         rotationAnimation = source.readInt();
+        colorMode = source.readInt();
     }
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 04ab239..3d9ba96 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -31,6 +31,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Printer;
+import android.util.SparseIntArray;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -558,6 +559,14 @@
     public static final int PRIVATE_FLAG_STATIC_SHARED_LIBRARY = 1 << 13;
 
     /**
+     * Value for {@linl #privateFlags}: When set, the application will only have its splits loaded
+     * if they are required to load a component. Splits can be loaded on demand using the
+     * {@link Context#createContextForSplit(String)} API.
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_ISOLATED_SPLIT_LOADING = 1 << 14;
+
+    /**
      * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
      * {@hide}
      */
@@ -607,8 +616,12 @@
     public String publicSourceDir;
 
     /**
-     * Full paths to zero or more split APKs that, when combined with the base
-     * APK defined in {@link #sourceDir}, form a complete application.
+     * The names of all installed split APKs, ordered lexicographically.
+     */
+    public String[] splitNames;
+
+    /**
+     * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
      */
     public String[] splitSourceDirs;
 
@@ -616,14 +629,35 @@
      * Full path to the publicly available parts of {@link #splitSourceDirs},
      * including resources and manifest. This may be different from
      * {@link #splitSourceDirs} if an application is forward locked.
+     *
+     * @see #splitSourceDirs
      */
     public String[] splitPublicSourceDirs;
 
     /**
-     * Full paths to the locations of extra resource packages this application
-     * uses. This field is only used if there are extra resource packages,
-     * otherwise it is null.
-     * 
+     * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+     *
+     * Available since platform version O.
+     *
+     * Only populated if the application opts in to isolated split loading via the
+     * {@link android.R.attr.isolatedSplits} attribute in the &lt;manifest&gt; tag of the app's
+     * AndroidManifest.xml.
+     *
+     * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
+     * and {@link #splitPublicSourceDirs} arrays.
+     * Each key represents a split and its value is its parent split.
+     * Cycles do not exist because they are illegal and screened for during installation.
+     *
+     * May be null if no splits are installed, or if no dependencies exist between them.
+     * @hide
+     */
+    public SparseIntArray splitDependencies;
+
+    /**
+     * Full paths to the locations of extra resource packages (runtime overlays)
+     * this application uses. This field is only used if there are extra resource
+     * packages, otherwise it is null.
+     *
      * {@hide}
      */
     public String[] resourceDirs;
@@ -1058,8 +1092,10 @@
         scanPublicSourceDir = orig.scanPublicSourceDir;
         sourceDir = orig.sourceDir;
         publicSourceDir = orig.publicSourceDir;
+        splitNames = orig.splitNames;
         splitSourceDirs = orig.splitSourceDirs;
         splitPublicSourceDirs = orig.splitPublicSourceDirs;
+        splitDependencies = orig.splitDependencies;
         nativeLibraryDir = orig.nativeLibraryDir;
         secondaryNativeLibraryDir = orig.secondaryNativeLibraryDir;
         nativeLibraryRootDir = orig.nativeLibraryRootDir;
@@ -1098,6 +1134,7 @@
         return 0;
     }
 
+    @SuppressWarnings("unchecked")
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         super.writeToParcel(dest, parcelableFlags);
         dest.writeString(taskAffinity);
@@ -1115,8 +1152,10 @@
         dest.writeString(scanPublicSourceDir);
         dest.writeString(sourceDir);
         dest.writeString(publicSourceDir);
+        dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
+        dest.writeSparseIntArray(splitDependencies);
         dest.writeString(nativeLibraryDir);
         dest.writeString(secondaryNativeLibraryDir);
         dest.writeString(nativeLibraryRootDir);
@@ -1155,6 +1194,7 @@
         }
     };
 
+    @SuppressWarnings("unchecked")
     private ApplicationInfo(Parcel source) {
         super(source);
         taskAffinity = source.readString();
@@ -1172,8 +1212,10 @@
         scanPublicSourceDir = source.readString();
         sourceDir = source.readString();
         publicSourceDir = source.readString();
+        splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
+        splitDependencies = source.readSparseIntArray();
         nativeLibraryDir = source.readString();
         secondaryNativeLibraryDir = source.readString();
         nativeLibraryRootDir = source.readString();
@@ -1363,6 +1405,15 @@
     }
 
     /**
+     * Returns true if the app has declared in its manifest that it wants its split APKs to be
+     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
+     * @hide
+     */
+    public boolean requestsIsolatedSplitLoading() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
+    }
+
+    /**
      * @hide
      */
     public boolean isStaticSharedLibrary() {
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 5cd15dd..53be953 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -45,6 +45,12 @@
     public String processName;
 
     /**
+     * The name of the split in which this component is declared.
+     * Null if the component was declared in the base APK.
+     */
+    public String splitName;
+
+    /**
      * A string resource identifier (in the package's resources) containing
      * a user-readable description of the component.  From the "description"
      * attribute or, if not set, 0.
@@ -53,7 +59,7 @@
     
     /**
      * Indicates whether or not this component may be instantiated.  Note that this value can be
-     * overriden by the one in its parent {@link ApplicationInfo}.
+     * overridden by the one in its parent {@link ApplicationInfo}.
      */
     public boolean enabled = true;
 
@@ -83,6 +89,7 @@
         super(orig);
         applicationInfo = orig.applicationInfo;
         processName = orig.processName;
+        splitName = orig.splitName;
         descriptionRes = orig.descriptionRes;
         enabled = orig.enabled;
         exported = orig.exported;
@@ -163,6 +170,9 @@
         if (processName != null && !packageName.equals(processName)) {
             pw.println(prefix + "processName=" + processName);
         }
+        if (splitName != null) {
+            pw.println(prefix + "splitName=" + splitName);
+        }
         pw.println(prefix + "enabled=" + enabled + " exported=" + exported
                 + " directBootAware=" + directBootAware);
         if (descriptionRes != 0) {
@@ -195,6 +205,7 @@
             applicationInfo.writeToParcel(dest, parcelableFlags);
         }
         dest.writeString(processName);
+        dest.writeString(splitName);
         dest.writeInt(descriptionRes);
         dest.writeInt(enabled ? 1 : 0);
         dest.writeInt(exported ? 1 : 0);
@@ -208,6 +219,7 @@
             applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
         }
         processName = source.readString();
+        splitName = source.readString();
         descriptionRes = source.readInt();
         enabled = (source.readInt() != 0);
         exported = (source.readInt() != 0);
diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java
index f620088..1d7b8f2 100644
--- a/core/java/android/content/pm/EphemeralResolveInfo.java
+++ b/core/java/android/content/pm/EphemeralResolveInfo.java
@@ -43,9 +43,11 @@
     private final String mPackageName;
     /** The filters used to match domain */
     private final List<EphemeralIntentFilter> mFilters;
+    /** The version code of the app that this class resolves to */
+    private final int mVersionCode;
     /** Filters only for legacy clients */
     @Deprecated
-    private List<IntentFilter> mLegacyFilters;
+    private final List<IntentFilter> mLegacyFilters;
 
     @Deprecated
     public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName,
@@ -59,10 +61,17 @@
         mFilters.add(new EphemeralIntentFilter(packageName, filters));
         mLegacyFilters = new ArrayList<IntentFilter>(filters.size());
         mLegacyFilters.addAll(filters);
+        mVersionCode = -1;
+    }
+
+    @Deprecated
+    public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
+            @Nullable List<EphemeralIntentFilter> filters) {
+        this(digest, packageName, filters, -1 /*versionCode*/);
     }
 
     public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName,
-            @Nullable List<EphemeralIntentFilter> filters) {
+            @Nullable List<EphemeralIntentFilter> filters, int versionConde) {
         // validate arguments
         if ((packageName == null && (filters != null && filters.size() != 0))
                 || (packageName != null && (filters == null || filters.size() == 0))) {
@@ -75,7 +84,9 @@
         } else {
             mFilters = null;
         }
+        mLegacyFilters = null;
         mPackageName = packageName;
+        mVersionCode = versionConde;
     }
 
     public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName,
@@ -88,6 +99,7 @@
         mPackageName = in.readString();
         mFilters = new ArrayList<EphemeralIntentFilter>();
         in.readList(mFilters, null /*loader*/);
+        mVersionCode = in.readInt();
         mLegacyFilters = new ArrayList<IntentFilter>();
         in.readList(mLegacyFilters, null /*loader*/);
     }
@@ -104,15 +116,19 @@
         return mPackageName;
     }
 
+    public List<EphemeralIntentFilter> getIntentFilters() {
+        return mFilters;
+    }
+
+    public int getVersionCode() {
+        return mVersionCode;
+    }
+
     @Deprecated
     public List<IntentFilter> getFilters() {
         return mLegacyFilters;
     }
 
-    public List<EphemeralIntentFilter> getIntentFilters() {
-        return mFilters;
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -123,6 +139,7 @@
         out.writeParcelable(mDigest, flags);
         out.writeString(mPackageName);
         out.writeList(mFilters);
+        out.writeInt(mVersionCode);
         out.writeList(mLegacyFilters);
     }
 
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 5152416..c08bd1d 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -38,15 +38,21 @@
 interface ILauncherApps {
     void addOnAppsChangedListener(String callingPackage, in IOnAppsChangedListener listener);
     void removeOnAppsChangedListener(in IOnAppsChangedListener listener);
-    ParceledListSlice getLauncherActivities(String packageName, in UserHandle user);
-    ActivityInfo resolveActivity(in ComponentName component, in UserHandle user);
-    void startActivityAsUser(in ComponentName component, in Rect sourceBounds,
+    ParceledListSlice getLauncherActivities(
+            String callingPackage, String packageName, in UserHandle user);
+    ActivityInfo resolveActivity(
+            String callingPackage, in ComponentName component, in UserHandle user);
+    void startActivityAsUser(String callingPackage,
+            in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
-    void showAppDetailsAsUser(in ComponentName component, in Rect sourceBounds,
+    void showAppDetailsAsUser(
+            String callingPackage, in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
-    boolean isPackageEnabled(String packageName, in UserHandle user);
-    boolean isActivityEnabled(in ComponentName component, in UserHandle user);
-    ApplicationInfo getApplicationInfo(String packageName, int flags, in UserHandle user);
+    boolean isPackageEnabled(String callingPackage, String packageName, in UserHandle user);
+    boolean isActivityEnabled(
+            String callingPackage, in ComponentName component, in UserHandle user);
+    ApplicationInfo getApplicationInfo(
+            String callingPackage, String packageName, int flags, in UserHandle user);
 
     ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
             in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
@@ -62,7 +68,8 @@
 
     boolean hasShortcutHostPermission(String callingPackage);
 
-    ParceledListSlice getShortcutConfigActivities(String packageName, in UserHandle user);
+    ParceledListSlice getShortcutConfigActivities(
+            String callingPackage, String packageName, in UserHandle user);
     IntentSender getShortcutConfigActivityIntent(String callingPackage, in ComponentName component,
             in UserHandle user);
 }
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 9d88cdd..a135d8f 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -18,6 +18,8 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 /**
  * Information you can retrieve about a particular piece of test
@@ -44,8 +46,12 @@
     public String publicSourceDir;
 
     /**
-     * Full paths to zero or more split APKs that, when combined with the base
-     * APK defined in {@link #sourceDir}, form a complete application.
+     * The names of all installed split APKs, ordered lexicographically.
+     */
+    public String[] splitNames;
+
+    /**
+     * Full paths to zero or more split APKs, indexed by the same order as {@link #splitNames}.
      */
     public String[] splitSourceDirs;
 
@@ -53,10 +59,31 @@
      * Full path to the publicly available parts of {@link #splitSourceDirs},
      * including resources and manifest. This may be different from
      * {@link #splitSourceDirs} if an application is forward locked.
+     *
+     * @see #splitSourceDirs
      */
     public String[] splitPublicSourceDirs;
 
     /**
+     * Maps the dependencies between split APKs. All splits implicitly depend on the base APK.
+     *
+     * Available since platform version O.
+     *
+     * Only populated if the application opts in to isolated split loading via the
+     * {@link android.R.attr.isolatedSplits} attribute in the &lt;manifest&gt; tag of the app's
+     * AndroidManifest.xml.
+     *
+     * The keys and values are all indices into the {@link #splitNames}, {@link #splitSourceDirs},
+     * and {@link #splitPublicSourceDirs} arrays.
+     * Each key represents a split and its value is its parent split.
+     * Cycles do not exist because they are illegal and screened for during installation.
+     *
+     * May be null if no splits are installed, or if no dependencies exist between them.
+     * @hide
+     */
+    public SparseIntArray splitDependencies;
+
+    /**
      * Full path to a directory assigned to the package for its persistent data.
      */
     public String dataDir;
@@ -88,8 +115,10 @@
         targetPackage = orig.targetPackage;
         sourceDir = orig.sourceDir;
         publicSourceDir = orig.publicSourceDir;
+        splitNames = orig.splitNames;
         splitSourceDirs = orig.splitSourceDirs;
         splitPublicSourceDirs = orig.splitPublicSourceDirs;
+        splitDependencies = orig.splitDependencies;
         dataDir = orig.dataDir;
         deviceProtectedDataDir = orig.deviceProtectedDataDir;
         credentialProtectedDataDir = orig.credentialProtectedDataDir;
@@ -114,8 +143,10 @@
         dest.writeString(targetPackage);
         dest.writeString(sourceDir);
         dest.writeString(publicSourceDir);
+        dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
+        dest.writeSparseIntArray(splitDependencies);
         dest.writeString(dataDir);
         dest.writeString(deviceProtectedDataDir);
         dest.writeString(credentialProtectedDataDir);
@@ -135,13 +166,16 @@
         }
     };
 
+    @SuppressWarnings("unchecked")
     private InstrumentationInfo(Parcel source) {
         super(source);
         targetPackage = source.readString();
         sourceDir = source.readString();
         publicSourceDir = source.readString();
+        splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
+        splitDependencies = source.readSparseIntArray();
         dataDir = source.readString();
         deviceProtectedDataDir = source.readString();
         credentialProtectedDataDir = source.readString();
@@ -156,8 +190,10 @@
         ai.packageName = packageName;
         ai.sourceDir = sourceDir;
         ai.publicSourceDir = publicSourceDir;
+        ai.splitNames = splitNames;
         ai.splitSourceDirs = splitSourceDirs;
         ai.splitPublicSourceDirs = splitPublicSourceDirs;
+        ai.splitDependencies = splitDependencies;
         ai.dataDir = dataDir;
         ai.deviceProtectedDataDir = deviceProtectedDataDir;
         ai.credentialProtectedDataDir = credentialProtectedDataDir;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c6a8674..57d2ba7 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -407,7 +407,8 @@
      */
     public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
         try {
-            return convertToActivityList(mService.getLauncherActivities(packageName, user), user);
+            return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
+                    packageName, user), user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -423,7 +424,8 @@
      */
     public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
         try {
-            ActivityInfo ai = mService.resolveActivity(intent.getComponent(), user);
+            ActivityInfo ai = mService.resolveActivity(mContext.getPackageName(),
+                    intent.getComponent(), user);
             if (ai != null) {
                 LauncherActivityInfo info = new LauncherActivityInfo(mContext, ai, user);
                 return info;
@@ -448,7 +450,8 @@
             Log.i(TAG, "StartMainActivity " + component + " " + user.getIdentifier());
         }
         try {
-            mService.startActivityAsUser(component, sourceBounds, opts, user);
+            mService.startActivityAsUser(mContext.getPackageName(),
+                    component, sourceBounds, opts, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -466,7 +469,8 @@
     public void startAppDetailsActivity(ComponentName component, UserHandle user,
             Rect sourceBounds, Bundle opts) {
         try {
-            mService.showAppDetailsAsUser(component, sourceBounds, opts, user);
+            mService.showAppDetailsAsUser(mContext.getPackageName(),
+                    component, sourceBounds, opts, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -486,7 +490,8 @@
     public List<LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String packageName,
             @NonNull UserHandle user) {
         try {
-            return convertToActivityList(mService.getShortcutConfigActivities(packageName, user),
+            return convertToActivityList(mService.getShortcutConfigActivities(
+                    mContext.getPackageName(), packageName, user),
                     user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
@@ -549,7 +554,7 @@
      */
     public boolean isPackageEnabled(String packageName, UserHandle user) {
         try {
-            return mService.isPackageEnabled(packageName, user);
+            return mService.isPackageEnabled(mContext.getPackageName(), packageName, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -569,7 +574,7 @@
     public ApplicationInfo getApplicationInfo(String packageName, @ApplicationInfoFlags int flags,
             UserHandle user) {
         try {
-            return mService.getApplicationInfo(packageName, flags, user);
+            return mService.getApplicationInfo(mContext.getPackageName(), packageName, flags, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -585,7 +590,7 @@
      */
     public boolean isActivityEnabled(ComponentName component, UserHandle user) {
         try {
-            return mService.isActivityEnabled(component, user);
+            return mService.isActivityEnabled(mContext.getPackageName(), component, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 98edbf8..ffcb1f3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2162,6 +2162,15 @@
     public static final String FEATURE_WATCH = "android.hardware.type.watch";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: This is a device for IoT and may not have an UI. An embedded
+     * device is defined as a full stack Android device with or without a display and no
+     * user-installable apps.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device supports printing.
      */
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d8d7abe..8223726 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -18,12 +18,12 @@
 
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
 import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
+import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -49,6 +49,9 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.split.SplitAssetDependencyLoader;
+import android.content.pm.split.SplitAssetLoader;
+import android.content.pm.split.DefaultSplitAssetLoader;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -74,6 +77,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseIntArray;
 import android.util.TypedValue;
 import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.jar.StrictJarFile;
@@ -106,6 +110,7 @@
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -155,6 +160,7 @@
 
     private static final String TAG_MANIFEST = "manifest";
     private static final String TAG_APPLICATION = "application";
+    private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
     private static final String TAG_OVERLAY = "overlay";
     private static final String TAG_KEY_SETS = "key-sets";
     private static final String TAG_PERMISSION_GROUP = "permission-group";
@@ -178,6 +184,7 @@
     private static final String TAG_EAT_COMMENT = "eat-comment";
     private static final String TAG_PACKAGE = "package";
     private static final String TAG_RESTRICT_UPDATE = "restrict-update";
+    private static final String TAG_USES_SPLIT = "uses-split";
 
     /**
      * Bit mask of all the valid bits that can be set in restartOnConfigChanges.
@@ -352,6 +359,9 @@
         /** Names of any split APKs, ordered by parsed splitName */
         public final String[] splitNames;
 
+        /** Dependencies of any split APKs, ordered by parsed splitName */
+        public final String[] usesSplitNames;
+
         /**
          * Path where this package was found on disk. For monolithic packages
          * this is path to single base APK file; for cluster packages this is
@@ -374,14 +384,17 @@
         public final boolean multiArch;
         public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
+        public final boolean isolatedSplits;
 
         public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
-                String[] splitCodePaths, int[] splitRevisionCodes) {
+                String[] usesSplitNames, String[] splitCodePaths,
+                int[] splitRevisionCodes) {
             this.packageName = baseApk.packageName;
             this.versionCode = baseApk.versionCode;
             this.installLocation = baseApk.installLocation;
             this.verifiers = baseApk.verifiers;
             this.splitNames = splitNames;
+            this.usesSplitNames = usesSplitNames;
             this.codePath = codePath;
             this.baseCodePath = baseApk.codePath;
             this.splitCodePaths = splitCodePaths;
@@ -392,6 +405,7 @@
             this.multiArch = baseApk.multiArch;
             this.use32bitAbi = baseApk.use32bitAbi;
             this.extractNativeLibs = baseApk.extractNativeLibs;
+            this.isolatedSplits = baseApk.isolatedSplits;
         }
 
         public List<String> getAllCodePaths() {
@@ -411,6 +425,7 @@
         public final String codePath;
         public final String packageName;
         public final String splitName;
+        public final String usesSplitName;
         public final int versionCode;
         public final int revisionCode;
         public final int installLocation;
@@ -422,15 +437,17 @@
         public final boolean multiArch;
         public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
+        public final boolean isolatedSplits;
 
-        public ApkLite(String codePath, String packageName, String splitName, int versionCode,
-                int revisionCode, int installLocation, List<VerifierInfo> verifiers,
+        public ApkLite(String codePath, String packageName, String splitName, String usesSplitName,
+                int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers,
                 Signature[] signatures, Certificate[][] certificates, boolean coreApp,
                 boolean debuggable, boolean multiArch, boolean use32bitAbi,
-                boolean extractNativeLibs) {
+                boolean extractNativeLibs, boolean isolatedSplits) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
+            this.usesSplitName = usesSplitName;
             this.versionCode = versionCode;
             this.revisionCode = revisionCode;
             this.installLocation = installLocation;
@@ -442,6 +459,7 @@
             this.multiArch = multiArch;
             this.use32bitAbi = use32bitAbi;
             this.extractNativeLibs = extractNativeLibs;
+            this.isolatedSplits = isolatedSplits;
         }
     }
 
@@ -492,7 +510,7 @@
         return isApkPath(file.getName());
     }
 
-    private static boolean isApkPath(String path) {
+    public static boolean isApkPath(String path) {
         return path.endsWith(".apk");
     }
 
@@ -738,23 +756,23 @@
     public static PackageLite parsePackageLite(File packageFile, int flags)
             throws PackageParserException {
         if (packageFile.isDirectory()) {
-            return parseClusterPackageLite(packageFile, flags, null);
+            return parseClusterPackageLite(packageFile, flags);
         } else {
-            return parseMonolithicPackageLite(packageFile, flags, null);
+            return parseMonolithicPackageLite(packageFile, flags);
         }
     }
 
-    private static PackageLite parseMonolithicPackageLite(File packageFile, int flags,
-            AssetManager cachedAssetManager) throws PackageParserException {
+    private static PackageLite parseMonolithicPackageLite(File packageFile, int flags)
+            throws PackageParserException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
-        final ApkLite baseApk = parseApkLite(packageFile, flags, cachedAssetManager);
+        final ApkLite baseApk = parseApkLite(packageFile, flags);
         final String packagePath = packageFile.getAbsolutePath();
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        return new PackageLite(packagePath, baseApk, null, null, null);
+        return new PackageLite(packagePath, baseApk, null, null, null, null);
     }
 
-    private static PackageLite parseClusterPackageLite(File packageDir, int flags,
-            AssetManager cachedAssetManager) throws PackageParserException {
+    private static PackageLite parseClusterPackageLite(File packageDir, int flags)
+            throws PackageParserException {
         final File[] files = packageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
@@ -768,7 +786,7 @@
         final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
         for (File file : files) {
             if (isApkFile(file)) {
-                final ApkLite lite = parseApkLite(file, flags, cachedAssetManager);
+                final ApkLite lite = parseApkLite(file, flags);
 
                 // Assert that all package names and version codes are
                 // consistent with the first one we encounter.
@@ -808,10 +826,12 @@
         final int size = apks.size();
 
         String[] splitNames = null;
+        String[] usesSplitNames = null;
         String[] splitCodePaths = null;
         int[] splitRevisionCodes = null;
         if (size > 0) {
             splitNames = new String[size];
+            usesSplitNames = new String[size];
             splitCodePaths = new String[size];
             splitRevisionCodes = new int[size];
 
@@ -819,13 +839,15 @@
             Arrays.sort(splitNames, sSplitNameComparator);
 
             for (int i = 0; i < size; i++) {
-                splitCodePaths[i] = apks.get(splitNames[i]).codePath;
-                splitRevisionCodes[i] = apks.get(splitNames[i]).revisionCode;
+                final ApkLite apk = apks.get(splitNames[i]);
+                usesSplitNames[i] = apk.usesSplitName;
+                splitCodePaths[i] = apk.codePath;
+                splitRevisionCodes[i] = apk.revisionCode;
             }
         }
 
         final String codePath = packageDir.getAbsolutePath();
-        return new PackageLite(codePath, baseApk, splitNames, splitCodePaths,
+        return new PackageLite(codePath, baseApk, splitNames, usesSplitNames, splitCodePaths,
                 splitRevisionCodes);
     }
 
@@ -1004,6 +1026,42 @@
         }
     }
 
+    private static SparseIntArray buildSplitDependencyTree(PackageLite pkg)
+            throws PackageParserException {
+        SparseIntArray splitDependencies = new SparseIntArray();
+        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+            final String splitDependency = pkg.usesSplitNames[splitIdx];
+            if (splitDependency != null) {
+                final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+                if (depIdx < 0) {
+                    throw new PackageParserException(
+                            PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                            "Split '" + pkg.splitNames[splitIdx] + "' requires split '"
+                                    + splitDependency + "', which is missing.");
+                }
+                splitDependencies.put(splitIdx, depIdx);
+            }
+        }
+
+        // Verify that there are no cycles.
+        final BitSet bitset = new BitSet();
+        for (int i = 0; i < splitDependencies.size(); i++) {
+            int splitIdx = splitDependencies.keyAt(i);
+
+            bitset.clear();
+            while (splitIdx != -1) {
+                if (bitset.get(splitIdx)) {
+                    throw new PackageParserException(
+                            PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                            "Cycle detected in split dependencies.");
+                }
+                bitset.set(splitIdx);
+                splitIdx = splitDependencies.get(splitIdx, -1);
+            }
+        }
+        return splitDependencies.size() != 0 ? splitDependencies : null;
+    }
+
     /**
      * Parse all APKs contained in the given directory, treating them as a
      * single package. This also performs sanity checking, such as requiring
@@ -1014,25 +1072,24 @@
      * must be done separately in {@link #collectCertificates(Package, int)}.
      */
     private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
-        final AssetManager assets = newConfiguredAssetManager();
-        final PackageLite lite = parseClusterPackageLite(packageDir, 0, assets);
-
+        final PackageLite lite = parseClusterPackageLite(packageDir, 0);
         if (mOnlyCoreApps && !lite.coreApp) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                     "Not a coreApp: " + packageDir);
         }
 
+        // Build the split dependency tree.
+        SparseIntArray splitDependencies = null;
+        final SplitAssetLoader assetLoader;
+        if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
+            splitDependencies = buildSplitDependencyTree(lite);
+            assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+        } else {
+            assetLoader = new DefaultSplitAssetLoader(lite, flags);
+        }
+
         try {
-            // Load all splits into the AssetManager (base has already been loaded earlier)
-            // so that resources can be overriden when parsing the manifests.
-            loadApkIntoAssetManager(assets, lite.baseCodePath, flags);
-
-            if (!ArrayUtils.isEmpty(lite.splitCodePaths)) {
-                for (String path : lite.splitCodePaths) {
-                    loadApkIntoAssetManager(assets, path, flags);
-                }
-            }
-
+            final AssetManager assets = assetLoader.getBaseAssetManager();
             final File baseApk = new File(lite.baseCodePath);
             final Package pkg = parseBaseApk(baseApk, assets, flags);
             if (pkg == null) {
@@ -1047,9 +1104,12 @@
                 pkg.splitRevisionCodes = lite.splitRevisionCodes;
                 pkg.splitFlags = new int[num];
                 pkg.splitPrivateFlags = new int[num];
+                pkg.applicationInfo.splitNames = pkg.splitNames;
+                pkg.applicationInfo.splitDependencies = splitDependencies;
 
                 for (int i = 0; i < num; i++) {
-                    parseSplitApk(pkg, i, assets, flags);
+                    final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
+                    parseSplitApk(pkg, i, splitAssets, flags);
                 }
             }
 
@@ -1057,7 +1117,7 @@
             pkg.setUse32bitAbi(lite.use32bitAbi);
             return pkg;
         } finally {
-            IoUtils.closeQuietly(assets);
+            IoUtils.closeQuietly(assetLoader);
         }
     }
 
@@ -1074,7 +1134,7 @@
     @Deprecated
     public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
         final AssetManager assets = newConfiguredAssetManager();
-        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags, assets);
+        final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
         if (mOnlyCoreApps) {
             if (!lite.coreApp) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
@@ -1168,7 +1228,7 @@
 
         final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
 
-        Resources res = null;
+        final Resources res;
         XmlResourceParser parser = null;
         try {
             res = new Resources(assets, mMetrics, null);
@@ -1225,7 +1285,7 @@
             }
 
             String tagName = parser.getName();
-            if (tagName.equals("application")) {
+            if (tagName.equals(TAG_APPLICATION)) {
                 if (foundApp) {
                     if (RIGID_PARSER) {
                         outError[0] = "<manifest> has more than one <application>";
@@ -1523,17 +1583,12 @@
      */
     public static ApkLite parseApkLite(File apkFile, int flags)
             throws PackageParserException {
-        return parseApkLite(apkFile, flags, null);
-    }
-
-    private static ApkLite parseApkLite(File apkFile, int flags,
-            @Nullable AssetManager cachedAssetManager) throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
         AssetManager assets = null;
         XmlResourceParser parser = null;
         try {
-            assets = cachedAssetManager == null ? newConfiguredAssetManager() : cachedAssetManager;
+            assets = newConfiguredAssetManager();
             int cookie = assets.addAssetPath(apkPath);
             if (cookie == 0) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
@@ -1543,7 +1598,6 @@
             final DisplayMetrics metrics = new DisplayMetrics();
             metrics.setToDefaults();
 
-            final Resources res = new Resources(assets, metrics, null);
             parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
 
             final Signature[] signatures;
@@ -1565,16 +1619,14 @@
             }
 
             final AttributeSet attrs = parser;
-            return parseApkLite(apkPath, res, parser, attrs, flags, signatures, certificates);
+            return parseApkLite(apkPath, parser, attrs, flags, signatures, certificates);
 
         } catch (XmlPullParserException | IOException | RuntimeException e) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                     "Failed to parse " + apkPath, e);
         } finally {
             IoUtils.closeQuietly(parser);
-            if (cachedAssetManager == null) {
-                IoUtils.closeQuietly(assets);
-            }
+            IoUtils.closeQuietly(assets);
         }
     }
 
@@ -1652,9 +1704,9 @@
                 (splitName != null) ? splitName.intern() : splitName);
     }
 
-    private static ApkLite parseApkLite(String codePath, Resources res, XmlPullParser parser,
-            AttributeSet attrs, int flags, Signature[] signatures, Certificate[][] certificates)
-                    throws IOException, XmlPullParserException, PackageParserException {
+    private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
+            int flags, Signature[] signatures, Certificate[][] certificates)
+            throws IOException, XmlPullParserException, PackageParserException {
         final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
 
         int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
@@ -1665,6 +1717,8 @@
         boolean multiArch = false;
         boolean use32bitAbi = false;
         boolean extractNativeLibs = true;
+        boolean isolatedSplits = false;
+        String usesSplitName = null;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
             final String attr = attrs.getAttributeName(i);
@@ -1677,6 +1731,8 @@
                 revisionCode = attrs.getAttributeIntValue(i, 0);
             } else if (attr.equals("coreApp")) {
                 coreApp = attrs.getAttributeBooleanValue(i, false);
+            } else if (attr.equals("isolatedSplits")) {
+                isolatedSplits = attrs.getAttributeBooleanValue(i, false);
             }
         }
 
@@ -1691,14 +1747,16 @@
                 continue;
             }
 
-            if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) {
-                final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags);
+            if (parser.getDepth() != searchDepth) {
+                continue;
+            }
+
+            if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+                final VerifierInfo verifier = parseVerifier(attrs);
                 if (verifier != null) {
                     verifiers.add(verifier);
                 }
-            }
-
-            if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) {
+            } else if (TAG_APPLICATION.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     if ("debuggable".equals(attr)) {
@@ -1714,12 +1772,25 @@
                         extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
                     }
                 }
+            } else if (TAG_USES_SPLIT.equals(parser.getName())) {
+                if (usesSplitName != null) {
+                    Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
+                    continue;
+                }
+
+                usesSplitName = attrs.getAttributeValue(ANDROID_RESOURCES, "name");
+                if (usesSplitName == null) {
+                    throw new PackageParserException(
+                            PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+                            "<uses-split> tag requires 'android:name' attribute");
+                }
             }
         }
 
-        return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
-                revisionCode, installLocation, verifiers, signatures, certificates, coreApp,
-                debuggable, multiArch, use32bitAbi, extractNativeLibs);
+        return new ApkLite(codePath, packageSplit.first, packageSplit.second, usesSplitName,
+                versionCode, revisionCode, installLocation, verifiers, signatures,
+                certificates, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs,
+                isolatedSplits);
     }
 
     /**
@@ -1940,6 +2011,10 @@
             pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_EPHEMERAL;
         }
 
+        if (sa.getBoolean(com.android.internal.R.styleable.AndroidManifest_isolatedSplits, false)) {
+            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
+        }
+
         // Resource boolean are -1, so 1 means we don't know the value.
         int supportsSmallScreens = 1;
         int supportsNormalScreens = 1;
@@ -2402,7 +2477,7 @@
         // cannot be windowed / resized. Note that an SDK version of 0 is common for
         // pre-Doughnut applications.
         if (pkg.applicationInfo.usesCompatibilityMode()) {
-            adjustPackageToBeUnresizeable(pkg);
+            adjustPackageToBeUnresizeableAndUnpipable(pkg);
         }
         return pkg;
     }
@@ -2413,9 +2488,10 @@
      *
      * @param pkg The package which needs to be marked as unresizable.
      */
-    private void adjustPackageToBeUnresizeable(Package pkg) {
+    private void adjustPackageToBeUnresizeableAndUnpipable(Package pkg) {
         for (Activity a : pkg.activities) {
             a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+            a.info.flags &= ~FLAG_SUPPORTS_PICTURE_IN_PICTURE;
         }
     }
 
@@ -3656,6 +3732,8 @@
                 continue;
             }
 
+            ComponentInfo parsedComponent = null;
+
             String tagName = parser.getName();
             if (tagName.equals("activity")) {
                 Activity a = parseActivity(owner, res, parser, flags, outError, false,
@@ -3666,6 +3744,7 @@
                 }
 
                 owner.activities.add(a);
+                parsedComponent = a.info;
 
             } else if (tagName.equals("receiver")) {
                 Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
@@ -3675,6 +3754,7 @@
                 }
 
                 owner.receivers.add(a);
+                parsedComponent = a.info;
 
             } else if (tagName.equals("service")) {
                 Service s = parseService(owner, res, parser, flags, outError);
@@ -3684,6 +3764,7 @@
                 }
 
                 owner.services.add(s);
+                parsedComponent = s.info;
 
             } else if (tagName.equals("provider")) {
                 Provider p = parseProvider(owner, res, parser, flags, outError);
@@ -3693,6 +3774,7 @@
                 }
 
                 owner.providers.add(p);
+                parsedComponent = p.info;
 
             } else if (tagName.equals("activity-alias")) {
                 Activity a = parseActivityAlias(owner, res, parser, flags, outError);
@@ -3702,6 +3784,7 @@
                 }
 
                 owner.activities.add(a);
+                parsedComponent = a.info;
 
             } else if (parser.getName().equals("meta-data")) {
                 // note: application meta-data is stored off to the side, so it can
@@ -3768,6 +3851,14 @@
                     return false;
                 }
             }
+
+            if (parsedComponent != null && parsedComponent.splitName == null) {
+                // If the loaded component did not specify a split, inherit the split name
+                // based on the split it is defined in.
+                // This is used to later load the correct split when starting this
+                // component.
+                parsedComponent.splitName = owner.splitNames[splitIndex];
+            }
         }
 
         return true;
@@ -3899,6 +3990,9 @@
         a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
                 owner.applicationInfo.taskAffinity, str, outError);
 
+        a.info.splitName =
+                sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_splitName, 0);
+
         a.info.flags = 0;
         if (sa.getBoolean(
                 R.styleable.AndroidManifestActivity_multiprocess, false)) {
@@ -3997,6 +4091,11 @@
 
             setActivityResizeMode(a.info, sa, owner);
 
+            if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture,
+                    false)) {
+                a.info.flags |= FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+            }
+
             if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysFocusable, false)) {
                 a.info.flags |= FLAG_ALWAYS_FOCUSABLE;
             }
@@ -4017,6 +4116,9 @@
 
             a.info.rotationAnimation =
                 sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, ROTATION_ANIMATION_ROTATE);
+
+            a.info.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode,
+                    ActivityInfo.COLOR_MODE_DEFAULT);
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
@@ -4158,16 +4260,13 @@
     private void setActivityResizeMode(ActivityInfo aInfo, TypedArray sa, Package owner) {
         final boolean appExplicitDefault = (owner.applicationInfo.privateFlags
                 & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES_EXPLICITLY_SET) != 0;
-        final boolean supportsPip =
-                sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, false);
 
         if (sa.hasValue(R.styleable.AndroidManifestActivity_resizeableActivity)
                 || appExplicitDefault) {
             // Activity or app explicitly set if it is resizeable or not;
             if (sa.getBoolean(R.styleable.AndroidManifestActivity_resizeableActivity,
                     appExplicitDefault)) {
-                aInfo.resizeMode =
-                        supportsPip ? RESIZE_MODE_RESIZEABLE_AND_PIPABLE : RESIZE_MODE_RESIZEABLE;
+                aInfo.resizeMode = RESIZE_MODE_RESIZEABLE;
             } else {
                 aInfo.resizeMode = RESIZE_MODE_UNRESIZEABLE;
             }
@@ -4520,6 +4619,9 @@
                 com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
                 0);
 
+        p.info.splitName =
+                sa.getNonConfigurationString(R.styleable.AndroidManifestProvider_splitName, 0);
+
         p.info.flags = 0;
 
         if (sa.getBoolean(
@@ -4816,6 +4918,9 @@
             s.info.permission = str.length() > 0 ? str.toString().intern() : null;
         }
 
+        s.info.splitName =
+                sa.getNonConfigurationString(R.styleable.AndroidManifestService_splitName, 0);
+
         s.info.flags = 0;
         if (sa.getBoolean(
                 com.android.internal.R.styleable.AndroidManifestService_stopWithTask,
@@ -5024,18 +5129,23 @@
         return data;
     }
 
-    private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser,
-            AttributeSet attrs, int flags) {
-        final TypedArray sa = res.obtainAttributes(attrs,
-                com.android.internal.R.styleable.AndroidManifestPackageVerifier);
+    private static VerifierInfo parseVerifier(AttributeSet attrs) {
+        String packageName = null;
+        String encodedPublicKey = null;
 
-        final String packageName = sa.getNonResourceString(
-                com.android.internal.R.styleable.AndroidManifestPackageVerifier_name);
+        final int attrCount = attrs.getAttributeCount();
+        for (int i = 0; i < attrCount; i++) {
+            final int attrResId = attrs.getAttributeNameResource(i);
+            switch (attrResId) {
+                case com.android.internal.R.attr.name:
+                    packageName = attrs.getAttributeValue(i);
+                    break;
 
-        final String encodedPublicKey = sa.getNonResourceString(
-                com.android.internal.R.styleable.AndroidManifestPackageVerifier_publicKey);
-
-        sa.recycle();
+                case com.android.internal.R.attr.publicKey:
+                    encodedPublicKey = attrs.getAttributeValue(i);
+                    break;
+            }
+        }
 
         if (packageName == null || packageName.length() == 0) {
             Slog.i(TAG, "verifier package name was null; skipping");
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
new file mode 100644
index 0000000..5a9966d
--- /dev/null
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -0,0 +1,100 @@
+/*
+ * 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 android.content.pm.split;
+
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+import android.os.Build;
+
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+/**
+ * Loads the base and split APKs into a single AssetManager.
+ * @hide
+ */
+public class DefaultSplitAssetLoader implements SplitAssetLoader {
+    private final String mBaseCodePath;
+    private final String[] mSplitCodePaths;
+    private final int mFlags;
+
+    private AssetManager mCachedAssetManager;
+
+    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
+        mBaseCodePath = pkg.baseCodePath;
+        mSplitCodePaths = pkg.splitCodePaths;
+        mFlags = flags;
+    }
+
+    private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
+            throws PackageParser.PackageParserException {
+        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) {
+            throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "Invalid package file: " + apkPath);
+        }
+
+        if (assets.addAssetPath(apkPath) == 0) {
+            throw new PackageParser.PackageParserException(
+                    INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                    "Failed adding asset path: " + apkPath);
+        }
+    }
+
+    @Override
+    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+        if (mCachedAssetManager != null) {
+            return mCachedAssetManager;
+        }
+
+        AssetManager assets = new AssetManager();
+        try {
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
+
+            if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+                for (String apkPath : mSplitCodePaths) {
+                    loadApkIntoAssetManager(assets, apkPath, mFlags);
+                }
+            }
+
+            mCachedAssetManager = assets;
+            assets = null;
+            return mCachedAssetManager;
+        } finally {
+            if (assets != null) {
+                IoUtils.closeQuietly(assets);
+            }
+        }
+    }
+
+    @Override
+    public AssetManager getSplitAssetManager(int splitIdx)
+            throws PackageParser.PackageParserException {
+        return getBaseAssetManager();
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (mCachedAssetManager != null) {
+            IoUtils.closeQuietly(mCachedAssetManager);
+        }
+    }
+}
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
new file mode 100644
index 0000000..3ad45b6
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -0,0 +1,153 @@
+/*
+ * 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 android.content.pm.split;
+
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
+
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+import android.os.Build;
+import android.util.SparseIntArray;
+
+import libcore.io.IoUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
+ * is to be used when an application opts-in to isolated split loading.
+ * @hide
+ */
+public class SplitAssetDependencyLoader
+        extends SplitDependencyLoaderHelper<PackageParser.PackageParserException>
+        implements SplitAssetLoader {
+    private static final int BASE_ASSET_PATH_IDX = -1;
+    private final String mBasePath;
+    private final String[] mSplitNames;
+    private final String[] mSplitPaths;
+    private final int mFlags;
+
+    private String[] mCachedBasePaths;
+    private AssetManager mCachedBaseAssetManager;
+
+    private String[][] mCachedSplitPaths;
+    private AssetManager[] mCachedAssetManagers;
+
+    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseIntArray dependencies,
+            int flags) {
+        super(dependencies);
+        mBasePath = pkg.baseCodePath;
+        mSplitNames = pkg.splitNames;
+        mSplitPaths = pkg.splitCodePaths;
+        mFlags = flags;
+        mCachedBasePaths = null;
+        mCachedBaseAssetManager = null;
+        mCachedSplitPaths = new String[mSplitNames.length][];
+        mCachedAssetManagers = new AssetManager[mSplitNames.length];
+    }
+
+    @Override
+    protected boolean isSplitCached(int splitIdx) {
+        if (splitIdx != -1) {
+            return mCachedAssetManagers[splitIdx] != null;
+        }
+        return mCachedBaseAssetManager != null;
+    }
+
+    // Adds all non-code configuration splits for this split name. The split name is expected
+    // to represent a feature split.
+    private void addAllConfigSplits(String splitName, ArrayList<String> outAssetPaths) {
+        for (int i = 0; i < mSplitNames.length; i++) {
+            if (isConfigurationSplitOf(mSplitNames[i], splitName)) {
+                outAssetPaths.add(mSplitPaths[i]);
+            }
+        }
+    }
+
+    private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
+            throws PackageParser.PackageParserException {
+        final AssetManager assets = new AssetManager();
+        try {
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+
+            for (String assetPath : assetPaths) {
+                if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 &&
+                        !PackageParser.isApkPath(assetPath)) {
+                    throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                            "Invalid package file: " + assetPath);
+                }
+
+                if (assets.addAssetPath(assetPath) == 0) {
+                    throw new PackageParser.PackageParserException(
+                            INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                            "Failed adding asset path: " + assetPath);
+                }
+            }
+            return assets;
+        } catch (Throwable e) {
+            IoUtils.closeQuietly(assets);
+            throw e;
+        }
+    }
+
+    @Override
+    protected void constructSplit(int splitIdx, int parentSplitIdx) throws
+            PackageParser.PackageParserException {
+        final ArrayList<String> assetPaths = new ArrayList<>();
+        if (splitIdx == BASE_ASSET_PATH_IDX) {
+            assetPaths.add(mBasePath);
+            addAllConfigSplits(null, assetPaths);
+            mCachedBasePaths = assetPaths.toArray(new String[assetPaths.size()]);
+            mCachedBaseAssetManager = createAssetManagerWithPaths(mCachedBasePaths, mFlags);
+            return;
+        }
+
+        if (parentSplitIdx == BASE_ASSET_PATH_IDX) {
+            Collections.addAll(assetPaths, mCachedBasePaths);
+        } else {
+            Collections.addAll(assetPaths, mCachedSplitPaths[parentSplitIdx]);
+        }
+
+        assetPaths.add(mSplitPaths[splitIdx]);
+        addAllConfigSplits(mSplitNames[splitIdx], assetPaths);
+        mCachedSplitPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedSplitPaths[splitIdx],
+                mFlags);
+    }
+
+    @Override
+    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+        loadDependenciesForSplit(BASE_ASSET_PATH_IDX);
+        return mCachedBaseAssetManager;
+    }
+
+    @Override
+    public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
+        loadDependenciesForSplit(idx);
+        return mCachedAssetManagers[idx];
+    }
+
+    @Override
+    public void close() throws Exception {
+        IoUtils.closeQuietly(mCachedBaseAssetManager);
+        for (AssetManager assets : mCachedAssetManagers) {
+            IoUtils.closeQuietly(assets);
+        }
+    }
+}
diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/core/java/android/content/pm/split/SplitAssetLoader.java
new file mode 100644
index 0000000..108fb95
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitAssetLoader.java
@@ -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.
+ */
+package android.content.pm.split;
+
+import android.content.pm.PackageParser;
+import android.content.res.AssetManager;
+
+/**
+ * Simple interface for loading base Assets and Splits. Used by PackageParser when parsing
+ * split APKs.
+ *
+ * @hide
+ */
+public interface SplitAssetLoader extends AutoCloseable {
+    AssetManager getBaseAssetManager() throws PackageParser.PackageParserException;
+    AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException;
+}
diff --git a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
new file mode 100644
index 0000000..b493480
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
@@ -0,0 +1,149 @@
+/*
+ * 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 android.content.pm.split;
+
+import android.annotation.Nullable;
+import android.util.IntArray;
+import android.util.SparseIntArray;
+
+/**
+ * A helper class that implements the dependency tree traversal for splits. Callbacks
+ * are implemented by subclasses to notify whether a split has already been constructed
+ * and is cached, and to actually create the split requested.
+ *
+ * This helper is meant to be subclassed so as to reduce the number of allocations
+ * needed to make use of it.
+ *
+ * All inputs and outputs are assumed to be indices into an array of splits.
+ *
+ * @hide
+ */
+public abstract class SplitDependencyLoaderHelper<E extends Exception> {
+    @Nullable private final SparseIntArray mDependencies;
+
+    /**
+     * Construct a new SplitDependencyLoaderHelper. Meant to be called from the
+     * subclass constructor.
+     * @param dependencies The dependency tree of splits. Can be null, which leads to
+     *                     just the implicit dependency of all splits on the base.
+     */
+    protected SplitDependencyLoaderHelper(@Nullable SparseIntArray dependencies) {
+        mDependencies = dependencies;
+    }
+
+    /**
+     * Traverses the dependency tree and constructs any splits that are not already
+     * cached. This routine short-circuits and skips the creation of splits closer to the
+     * root if they are cached, as reported by the subclass implementation of
+     * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+     * implementation of {@link #constructSplit(int, int)}.
+     * @param splitIdx The index of the split to load. Can be -1, which represents the
+     *                 base Application.
+     */
+    protected void loadDependenciesForSplit(int splitIdx) throws E {
+        // Quick check before any allocations are done.
+        if (isSplitCached(splitIdx)) {
+            return;
+        }
+
+        final IntArray linearDependencies = new IntArray();
+        linearDependencies.add(splitIdx);
+
+        // Collect all the dependencies that need to be constructed.
+        // They will be listed from leaf to root.
+        while (splitIdx >= 0) {
+            splitIdx = mDependencies != null ? mDependencies.get(splitIdx, -1) : -1;
+            if (isSplitCached(splitIdx)) {
+                break;
+            }
+            linearDependencies.add(splitIdx);
+        }
+
+        // Visit each index, from right to left (root to leaf).
+        int parentIdx = splitIdx;
+        for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+            final int idx = linearDependencies.get(i);
+            constructSplit(idx, parentIdx);
+            parentIdx = idx;
+        }
+    }
+
+    /**
+     * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+     * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+     * @param splitIdx The index of the split to check for in the cache.
+     * @return true if the split is cached and does not need to be constructed.
+     */
+    protected abstract boolean isSplitCached(int splitIdx);
+
+    /**
+     * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+     * The result is expected to be cached by the subclass in its own structures.
+     * @param splitIdx The index of the split to construct. Can be -1, which represents the
+     *                 base Application.
+     * @param parentSplitIdx The index of the parent split. Can be -1, which represents the
+     *                       base Application.
+     * @throws E
+     */
+    protected abstract void constructSplit(int splitIdx, int parentSplitIdx) throws E;
+
+    /**
+     * Returns true if `splitName` represents a Configuration split of `featureSplitName`.
+     *
+     * A Configuration split's name is prefixed with the associated Feature split's name
+     * or the empty string if the split is for the base Application APK. It is then followed by the
+     * dollar sign character "$" and some unique string that should represent the configurations
+     * the split contains.
+     *
+     * Example:
+     * <table>
+     *     <tr>
+     *         <th>Feature split name</th>
+     *         <th>Configuration split name: xhdpi</th>
+     *         <th>Configuration split name: fr-rFR</th>
+     *     </tr>
+     *     <tr>
+     *         <td>(base APK)</td>
+     *         <td><code>$xhdpi</code></td>
+     *         <td><code>$fr-rFR</code></td>
+     *     </tr>
+     *     <tr>
+     *         <td><code>Extras</code></td>
+     *         <td><code>Extras$xhdpi</code></td>
+     *         <td><code>Extras$fr-rFR</code></td>
+     *     </tr>
+     * </table>
+     *
+     * @param splitName The name of the split to check.
+     * @param featureSplitName The name of the Feature split. May be null or "" if checking
+     *                         the base Application APK.
+     * @return true if the splitName represents a Configuration split of featureSplitName.
+     */
+    protected static boolean isConfigurationSplitOf(String splitName, String featureSplitName) {
+        if (featureSplitName == null || featureSplitName.length() == 0) {
+            // We are looking for configuration splits of the base, which have some legacy support.
+            if (splitName.startsWith("config_")) {
+                return true;
+            } else if (splitName.startsWith("$")) {
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return splitName.startsWith(featureSplitName + "$");
+        }
+    }
+}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index a81329d..99fbee1 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -102,66 +102,66 @@
     public boolean userSetLocale;
 
 
-    /** Constant for {@link #colorimetry}: bits that encode whether the screen is wide gamut. */
-    public static final int COLORIMETRY_WIDE_COLOR_GAMUT_MASK = 0x3;
+    /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */
+    public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;
     /**
-     * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value
+     * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
      * indicating that it is unknown whether or not the screen is wide gamut.
      */
-    public static final int COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED = 0x0;
+    public static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0x0;
     /**
-     * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value
+     * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
      * indicating that the screen is not wide gamut.
      * <p>Corresponds to the <code>-nowidecg</code> resource qualifier.</p>
      */
-    public static final int COLORIMETRY_WIDE_COLOR_GAMUT_NO = 0x1;
+    public static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x1;
     /**
-     * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} value
+     * Constant for {@link #colorMode}: a {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} value
      * indicating that the screen is wide gamut.
      * <p>Corresponds to the <code>-widecg</code> resource qualifier.</p>
      */
-    public static final int COLORIMETRY_WIDE_COLOR_GAMUT_YES = 0x2;
+    public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x2;
 
-    /** Constant for {@link #colorimetry}: bits that encode whether the dynamic range of the screen. */
-    public static final int COLORIMETRY_HDR_MASK = 0xc;
-    /** Constant for {@link #colorimetry}: bits shift to get the screen dynamic range. */
-    public static final int COLORIMETRY_HDR_SHIFT = 2;
+    /** Constant for {@link #colorMode}: bits that encode whether the dynamic range of the screen. */
+    public static final int COLOR_MODE_HDR_MASK = 0xc;
+    /** Constant for {@link #colorMode}: bits shift to get the screen dynamic range. */
+    public static final int COLOR_MODE_HDR_SHIFT = 2;
     /**
-     * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value
+     * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
      * indicating that it is unknown whether or not the screen is HDR.
      */
-    public static final int COLORIMETRY_HDR_UNDEFINED = 0x0;
+    public static final int COLOR_MODE_HDR_UNDEFINED = 0x0;
     /**
-     * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value
+     * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
      * indicating that the screen is not HDR (low/standard dynamic range).
      * <p>Corresponds to the <code>-lowdr</code> resource qualifier.</p>
      */
-    public static final int COLORIMETRY_HDR_NO = 0x1 << COLORIMETRY_HDR_SHIFT;
+    public static final int COLOR_MODE_HDR_NO = 0x1 << COLOR_MODE_HDR_SHIFT;
     /**
-     * Constant for {@link #colorimetry}: a {@link #COLORIMETRY_HDR_MASK} value
+     * Constant for {@link #colorMode}: a {@link #COLOR_MODE_HDR_MASK} value
      * indicating that the screen is HDR (dynamic range).
      * <p>Corresponds to the <code>-highdr</code> resource qualifier.</p>
      */
-    public static final int COLORIMETRY_HDR_YES = 0x2 << COLORIMETRY_HDR_SHIFT;
+    public static final int COLOR_MODE_HDR_YES = 0x2 << COLOR_MODE_HDR_SHIFT;
 
-    /** Constant for {@link #colorimetry}: a value indicating that colorimetry is undefined */
+    /** Constant for {@link #colorMode}: a value indicating that the color mode is undefined */
     @SuppressWarnings("PointlessBitwiseExpression")
-    public static final int COLORIMETRY_UNDEFINED = COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED |
-            COLORIMETRY_HDR_UNDEFINED;
+    public static final int COLOR_MODE_UNDEFINED = COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED |
+            COLOR_MODE_HDR_UNDEFINED;
 
     /**
      * Bit mask of for color capabilities of the screen. Currently there are two fields:
-     * <p>The {@link #COLORIMETRY_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of
+     * <p>The {@link #COLOR_MODE_WIDE_COLOR_GAMUT_MASK} bits define the color gamut of
      * the screen. They may be one of
-     * {@link #COLORIMETRY_WIDE_COLOR_GAMUT_NO} or {@link #COLORIMETRY_WIDE_COLOR_GAMUT_YES}.</p>
+     * {@link #COLOR_MODE_WIDE_COLOR_GAMUT_NO} or {@link #COLOR_MODE_WIDE_COLOR_GAMUT_YES}.</p>
      *
-     * <p>The {@link #COLORIMETRY_HDR_MASK} defines the dynamic range of the screen. They may be
-     * one of {@link #COLORIMETRY_HDR_NO} or {@link #COLORIMETRY_HDR_YES}.</p>
+     * <p>The {@link #COLOR_MODE_HDR_MASK} defines the dynamic range of the screen. They may be
+     * one of {@link #COLOR_MODE_HDR_NO} or {@link #COLOR_MODE_HDR_YES}.</p>
      *
      * <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
      * Multiple Screens</a> for more information.</p>
      */
-    public int colorimetry;
+    public int colorMode;
 
     /** Constant for {@link #screenLayout}: bits that encode the size. */
     public static final int SCREENLAYOUT_SIZE_MASK = 0x0f;
@@ -393,8 +393,8 @@
         if ((diff & ActivityInfo.CONFIG_SCREEN_LAYOUT) != 0) {
             list.add("CONFIG_SCREEN_LAYOUT");
         }
-        if ((diff & ActivityInfo.CONFIG_COLORIMETRY) != 0) {
-            list.add("CONFIG_COLORIMETRY");
+        if ((diff & ActivityInfo.CONFIG_COLOR_MODE) != 0) {
+            list.add("CONFIG_COLOR_MODE");
         }
         if ((diff & ActivityInfo.CONFIG_UI_MODE) != 0) {
             list.add("CONFIG_UI_MODE");
@@ -776,7 +776,7 @@
                     NATIVE_CONFIG_UI_MODE,
                     NATIVE_CONFIG_SMALLEST_SCREEN_SIZE,
                     NATIVE_CONFIG_LAYOUTDIR,
-                    NATIVE_CONFIG_COLORIMETRY,
+                    NATIVE_CONFIG_COLOR_MODE,
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface NativeConfig {}
@@ -813,8 +813,8 @@
     public static final int NATIVE_CONFIG_SMALLEST_SCREEN_SIZE = 0x2000;
     /** @hide Native-specific bit mask for LAYOUTDIR config ; DO NOT USE UNLESS YOU ARE SURE.*/
     public static final int NATIVE_CONFIG_LAYOUTDIR = 0x4000;
-    /** @hide Native-specific bit mask for COLORIMETRY config ; DO NOT USE UNLESS YOU ARE SURE.*/
-    public static final int NATIVE_CONFIG_COLORIMETRY = 0x10000;
+    /** @hide Native-specific bit mask for COLOR_MODE config ; DO NOT USE UNLESS YOU ARE SURE.*/
+    public static final int NATIVE_CONFIG_COLOR_MODE = 0x10000;
 
     /**
      * <p>Construct an invalid Configuration. This state is only suitable for constructing a
@@ -873,7 +873,7 @@
         navigationHidden = o.navigationHidden;
         orientation = o.orientation;
         screenLayout = o.screenLayout;
-        colorimetry = o.colorimetry;
+        colorMode = o.colorMode;
         uiMode = o.uiMode;
         screenWidthDp = o.screenWidthDp;
         screenHeightDp = o.screenHeightDp;
@@ -954,19 +954,19 @@
             default: sb.append(" layoutLong=");
                     sb.append(screenLayout&SCREENLAYOUT_LONG_MASK); break;
         }
-        switch ((colorimetry&COLORIMETRY_HDR_MASK)) {
-            case COLORIMETRY_HDR_UNDEFINED: sb.append(" ?ldr"); break; // most likely not HDR
-            case COLORIMETRY_HDR_NO: /* ldr is not interesting to print */ break;
-            case COLORIMETRY_HDR_YES: sb.append(" hdr"); break;
+        switch ((colorMode &COLOR_MODE_HDR_MASK)) {
+            case COLOR_MODE_HDR_UNDEFINED: sb.append(" ?ldr"); break; // most likely not HDR
+            case COLOR_MODE_HDR_NO: /* ldr is not interesting to print */ break;
+            case COLOR_MODE_HDR_YES: sb.append(" hdr"); break;
             default: sb.append(" dynamicRange=");
-                sb.append(colorimetry&COLORIMETRY_HDR_MASK); break;
+                sb.append(colorMode &COLOR_MODE_HDR_MASK); break;
         }
-        switch ((colorimetry&COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
-            case COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED: sb.append(" ?wideColorGamut"); break;
-            case COLORIMETRY_WIDE_COLOR_GAMUT_NO: /* not wide is not interesting to print */ break;
-            case COLORIMETRY_WIDE_COLOR_GAMUT_YES: sb.append(" widecg"); break;
+        switch ((colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+            case COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED: sb.append(" ?wideColorGamut"); break;
+            case COLOR_MODE_WIDE_COLOR_GAMUT_NO: /* not wide is not interesting to print */ break;
+            case COLOR_MODE_WIDE_COLOR_GAMUT_YES: sb.append(" widecg"); break;
             default: sb.append(" wideColorGamut=");
-                sb.append(colorimetry&COLORIMETRY_WIDE_COLOR_GAMUT_MASK); break;
+                sb.append(colorMode &COLOR_MODE_WIDE_COLOR_GAMUT_MASK); break;
         }
         switch (orientation) {
             case ORIENTATION_UNDEFINED: sb.append(" ?orien"); break;
@@ -1059,7 +1059,7 @@
         navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
         orientation = ORIENTATION_UNDEFINED;
         screenLayout = SCREENLAYOUT_UNDEFINED;
-        colorimetry = COLORIMETRY_UNDEFINED;
+        colorMode = COLOR_MODE_UNDEFINED;
         uiMode = UI_MODE_TYPE_UNDEFINED;
         screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
         screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
@@ -1195,21 +1195,21 @@
                 | (delta.screenLayout & SCREENLAYOUT_COMPAT_NEEDED);
         }
 
-        if (((delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
-                     COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED)
-                && (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)
-                != (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
-            changed |= ActivityInfo.CONFIG_COLORIMETRY;
-            colorimetry = (colorimetry & ~COLORIMETRY_WIDE_COLOR_GAMUT_MASK)
-                    | (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK);
+        if (((delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+                     COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED)
+                && (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)
+                != (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+            changed |= ActivityInfo.CONFIG_COLOR_MODE;
+            colorMode = (colorMode & ~COLOR_MODE_WIDE_COLOR_GAMUT_MASK)
+                    | (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK);
         }
 
-        if (((delta.colorimetry & COLORIMETRY_HDR_MASK) != COLORIMETRY_HDR_UNDEFINED)
-                && (delta.colorimetry & COLORIMETRY_HDR_MASK)
-                != (colorimetry & COLORIMETRY_HDR_MASK)) {
-            changed |= ActivityInfo.CONFIG_COLORIMETRY;
-            colorimetry = (colorimetry & ~COLORIMETRY_HDR_MASK)
-                    | (delta.colorimetry & COLORIMETRY_HDR_MASK);
+        if (((delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED)
+                && (delta.colorMode & COLOR_MODE_HDR_MASK)
+                != (colorMode & COLOR_MODE_HDR_MASK)) {
+            changed |= ActivityInfo.CONFIG_COLOR_MODE;
+            colorMode = (colorMode & ~COLOR_MODE_HDR_MASK)
+                    | (delta.colorMode & COLOR_MODE_HDR_MASK);
         }
 
         if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
@@ -1362,17 +1362,17 @@
             changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
         }
         if ((compareUndefined ||
-                     (delta.colorimetry & COLORIMETRY_HDR_MASK) != COLORIMETRY_HDR_UNDEFINED)
-                && (colorimetry & COLORIMETRY_HDR_MASK) !=
-                        (delta.colorimetry & COLORIMETRY_HDR_MASK)) {
-            changed |= ActivityInfo.CONFIG_COLORIMETRY;
+                     (delta.colorMode & COLOR_MODE_HDR_MASK) != COLOR_MODE_HDR_UNDEFINED)
+                && (colorMode & COLOR_MODE_HDR_MASK) !=
+                        (delta.colorMode & COLOR_MODE_HDR_MASK)) {
+            changed |= ActivityInfo.CONFIG_COLOR_MODE;
         }
         if ((compareUndefined ||
-                     (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
-                             COLORIMETRY_WIDE_COLOR_GAMUT_UNDEFINED)
-                && (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
-                        (delta.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
-            changed |= ActivityInfo.CONFIG_COLORIMETRY;
+                     (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+                             COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED)
+                && (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+                        (delta.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+            changed |= ActivityInfo.CONFIG_COLOR_MODE;
         }
         if ((compareUndefined || delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED))
                 && uiMode != delta.uiMode) {
@@ -1485,7 +1485,7 @@
         dest.writeInt(navigationHidden);
         dest.writeInt(orientation);
         dest.writeInt(screenLayout);
-        dest.writeInt(colorimetry);
+        dest.writeInt(colorMode);
         dest.writeInt(uiMode);
         dest.writeInt(screenWidthDp);
         dest.writeInt(screenHeightDp);
@@ -1520,7 +1520,7 @@
         navigationHidden = source.readInt();
         orientation = source.readInt();
         screenLayout = source.readInt();
-        colorimetry = source.readInt();
+        colorMode = source.readInt();
         uiMode = source.readInt();
         screenWidthDp = source.readInt();
         screenHeightDp = source.readInt();
@@ -1602,7 +1602,7 @@
         if (n != 0) return n;
         n = this.orientation - that.orientation;
         if (n != 0) return n;
-        n = this.colorimetry - that.colorimetry;
+        n = this.colorMode - that.colorMode;
         if (n != 0) return n;
         n = this.screenLayout - that.screenLayout;
         if (n != 0) return n;
@@ -1649,7 +1649,7 @@
         result = 31 * result + navigationHidden;
         result = 31 * result + orientation;
         result = 31 * result + screenLayout;
-        result = 31 * result + colorimetry;
+        result = 31 * result + colorMode;
         result = 31 * result + uiMode;
         result = 31 * result + screenWidthDp;
         result = 31 * result + screenHeightDp;
@@ -1763,7 +1763,7 @@
      * @return true if the screen has a wide color gamut, false otherwise
      */
     public boolean isScreenWideColorGamut() {
-        return (colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) == COLORIMETRY_WIDE_COLOR_GAMUT_YES;
+        return (colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) == COLOR_MODE_WIDE_COLOR_GAMUT_YES;
     }
 
     /**
@@ -1772,7 +1772,7 @@
      * @return true if the screen has a high dynamic range, false otherwise
      */
     public boolean isScreenHdr() {
-        return (colorimetry & COLORIMETRY_HDR_MASK) == COLORIMETRY_HDR_YES;
+        return (colorMode & COLOR_MODE_HDR_MASK) == COLOR_MODE_HDR_YES;
     }
 
     /**
@@ -1907,22 +1907,22 @@
                 break;
         }
 
-        switch (config.colorimetry & Configuration.COLORIMETRY_HDR_MASK) {
-            case Configuration.COLORIMETRY_HDR_YES:
+        switch (config.colorMode & Configuration.COLOR_MODE_HDR_MASK) {
+            case Configuration.COLOR_MODE_HDR_YES:
                 parts.add("highdr");
                 break;
-            case Configuration.COLORIMETRY_HDR_NO:
+            case Configuration.COLOR_MODE_HDR_NO:
                 parts.add("lowdr");
                 break;
             default:
                 break;
         }
 
-        switch (config.colorimetry & Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_MASK) {
-            case Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_YES:
+        switch (config.colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK) {
+            case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES:
                 parts.add("widecg");
                 break;
-            case Configuration.COLORIMETRY_WIDE_COLOR_GAMUT_NO:
+            case Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO:
                 parts.add("nowidecg");
                 break;
             default:
@@ -2154,14 +2154,14 @@
             delta.screenLayout |= change.screenLayout & SCREENLAYOUT_ROUND_MASK;
         }
 
-        if ((base.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK) !=
-                (change.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK)) {
-            delta.colorimetry |= change.colorimetry & COLORIMETRY_WIDE_COLOR_GAMUT_MASK;
+        if ((base.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK) !=
+                (change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK)) {
+            delta.colorMode |= change.colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK;
         }
 
-        if ((base.colorimetry & COLORIMETRY_HDR_MASK) !=
-                (change.colorimetry & COLORIMETRY_HDR_MASK)) {
-            delta.colorimetry |= change.colorimetry & COLORIMETRY_HDR_MASK;
+        if ((base.colorMode & COLOR_MODE_HDR_MASK) !=
+                (change.colorMode & COLOR_MODE_HDR_MASK)) {
+            delta.colorMode |= change.colorMode & COLOR_MODE_HDR_MASK;
         }
 
         if ((base.uiMode & UI_MODE_TYPE_MASK) != (change.uiMode & UI_MODE_TYPE_MASK)) {
@@ -2206,7 +2206,7 @@
     private static final String XML_ATTR_NAVIGATION_HIDDEN = "navHid";
     private static final String XML_ATTR_ORIENTATION = "ori";
     private static final String XML_ATTR_SCREEN_LAYOUT = "scrLay";
-    private static final String XML_ATTR_COLORIMETRY = "clrMtry";
+    private static final String XML_ATTR_COLOR_MODE = "clrMod";
     private static final String XML_ATTR_UI_MODE = "ui";
     private static final String XML_ATTR_SCREEN_WIDTH = "width";
     private static final String XML_ATTR_SCREEN_HEIGHT = "height";
@@ -2249,8 +2249,8 @@
                 ORIENTATION_UNDEFINED);
         configOut.screenLayout = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_LAYOUT,
                 SCREENLAYOUT_UNDEFINED);
-        configOut.colorimetry = XmlUtils.readIntAttribute(parser, XML_ATTR_COLORIMETRY,
-                COLORIMETRY_UNDEFINED);
+        configOut.colorMode = XmlUtils.readIntAttribute(parser, XML_ATTR_COLOR_MODE,
+                COLOR_MODE_UNDEFINED);
         configOut.uiMode = XmlUtils.readIntAttribute(parser, XML_ATTR_UI_MODE, 0);
         configOut.screenWidthDp = XmlUtils.readIntAttribute(parser, XML_ATTR_SCREEN_WIDTH,
                 SCREEN_WIDTH_DP_UNDEFINED);
@@ -2313,8 +2313,8 @@
         if (config.screenLayout != SCREENLAYOUT_UNDEFINED) {
             XmlUtils.writeIntAttribute(xml, XML_ATTR_SCREEN_LAYOUT, config.screenLayout);
         }
-        if (config.colorimetry != COLORIMETRY_UNDEFINED) {
-            XmlUtils.writeIntAttribute(xml, XML_ATTR_COLORIMETRY, config.colorimetry);
+        if (config.colorMode != COLOR_MODE_UNDEFINED) {
+            XmlUtils.writeIntAttribute(xml, XML_ATTR_COLOR_MODE, config.colorMode);
         }
         if (config.uiMode != 0) {
             XmlUtils.writeIntAttribute(xml, XML_ATTR_UI_MODE, config.uiMode);
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 227066d..8cd3d7b 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -728,13 +728,10 @@
      * @param values the {@link ContentValues} to put the row into.
      */
     public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
-        AbstractWindowedCursor awc =
-                (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null;
-
         String[] columns = cursor.getColumnNames();
         int length = columns.length;
         for (int i = 0; i < length; i++) {
-            if (awc != null && awc.isBlob(i)) {
+            if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
                 values.put(columns[i], cursor.getBlob(i));
             } else {
                 values.put(columns[i], cursor.getString(i));
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index fffb1d7..e97bb2f 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -40,7 +40,7 @@
 public final class HardwareBuffer implements Parcelable {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({RGBA_8888, RGBA_FP16, RGBX_8888, RGB_888, RGB_565})
+    @IntDef({RGBA_8888, RGBA_FP16, RGBX_8888, RGB_888, RGB_565, BLOB})
     public @interface Format {};
 
     /** Format: 8 bits each red, green, blue, alpha */
@@ -52,7 +52,9 @@
     /** Format: 5 bits each red and blue, 6 bits green, no alpha */
     public static final int RGB_565     = 4;
     /** Format: 16 bits each red, green, blue, alpha */
-    public static final int RGBA_FP16   = 5;
+    public static final int RGBA_FP16   = 0x16;
+    /** Format: opaque format used for raw data transfer; must have a height of 1 */
+    public static final int BLOB        = 0x21;
 
     // Note: do not rename, this field is used by native code
     private long mNativeObject;
@@ -135,6 +137,9 @@
         if (layers <= 0) {
             throw new IllegalArgumentException("Invalid layer count " + layers);
         }
+        if (format == BLOB && height != 1) {
+            throw new IllegalArgumentException("Height must be 1 when using the BLOB format");
+        }
         long nativeObject = nCreateHardwareBuffer(width, height, format, layers, usage);
         if (nativeObject == 0) {
             throw new IllegalArgumentException("Unable to create a HardwareBuffer, either the " +
@@ -295,6 +300,7 @@
             case RGBX_8888:
             case RGB_565:
             case RGB_888:
+            case BLOB:
                 return true;
         }
         return false;
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 06ab13e..d87c55e 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -729,14 +729,22 @@
     private static final int DATA_INJECTION_MASK = 0x10;
     private static final int DATA_INJECTION_SHIFT = 4;
 
-    // MASK for dynamic sensor (sensor that added during runtime), bit 6.
+    // MASK for dynamic sensor (sensor that added during runtime), bit 5.
     private static final int DYNAMIC_SENSOR_MASK = 0x20;
     private static final int DYNAMIC_SENSOR_SHIFT = 5;
 
-    // MASK for indication bit of sensor additional information support (bit 7).
+    // MASK for indication bit of sensor additional information support, bit 6.
     private static final int ADDITIONAL_INFO_MASK = 0x40;
     private static final int ADDITIONAL_INFO_SHIFT = 6;
 
+    // Mask for direct mode highest rate level, bit 7, 8, 9.
+    private static final int DIRECT_REPORT_MASK = 0x380;
+    private static final int DIRECT_REPORT_SHIFT = 7;
+
+    // Mask for supported direct channel, bit 10, 11
+    private static final int DIRECT_CHANNEL_MASK = 0xC00;
+    private static final int DIRECT_CHANNEL_SHIFT = 10;
+
     // TODO(): The following arrays are fragile and error-prone. This needs to be refactored.
 
     // Note: This needs to be updated, whenever a new sensor is added.
@@ -796,6 +804,42 @@
         return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT);
     }
 
+    /**
+     * Get the highest supported direct report mode rate level of the sensor.
+     *
+     * @return Highest direct report rate level of this sensor. If the sensor does not support
+     * direct report mode, this returns {@link SensorDirectChannel#RATE_STOP}.
+     * @see SensorDirectChannel#RATE_STOP
+     * @see SensorDirectChannel#RATE_NORMAL
+     * @see SensorDirectChannel#RATE_FAST
+     * @see SensorDirectChannel#RATE_VERY_FAST
+     */
+    @SensorDirectChannel.RateLevel
+    public int getHighestDirectReportRateLevel() {
+        int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT);
+        return rateLevel <= SensorDirectChannel.RATE_VERY_FAST
+                ? rateLevel : SensorDirectChannel.RATE_VERY_FAST;
+    }
+
+    /**
+     * Test if sensor support direct channel backed by a specific type of shared memory.
+     *
+     * @param sharedMemType type of shared memory used by direct channel.
+     * @return <code>true</code> if the shared memory type is supported.
+     * @see SensorDirectChannel#TYPE_ASHMEM
+     * @see SensorDirectChannel#TYPE_HARDWARE_BUFFER
+     */
+    public boolean isDirectChannelTypeSupported(@SensorDirectChannel.MemoryType int sharedMemType) {
+        switch (sharedMemType) {
+            case SensorDirectChannel.TYPE_ASHMEM:
+                return (mFlags & (1 << DIRECT_CHANNEL_SHIFT)) > 0;
+            case SensorDirectChannel.TYPE_HARDWARE_BUFFER:
+                return (mFlags & (1 << DIRECT_CHANNEL_SHIFT + 1)) > 0;
+            default:
+                return false;
+        }
+    }
+
     static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
         // RotationVector length has changed to 3 to 5 for API level 18
         // Set it to 3 for backward compatibility.
diff --git a/core/java/android/hardware/SensorDirectChannel.java b/core/java/android/hardware/SensorDirectChannel.java
new file mode 100644
index 0000000..0efd62b
--- /dev/null
+++ b/core/java/android/hardware/SensorDirectChannel.java
@@ -0,0 +1,168 @@
+/*
+ * 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 android.hardware;
+
+import android.annotation.IntDef;
+import android.os.MemoryFile;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Class representing a sensor direct channel. Use {@link
+ * SensorManager#createDirectChannel(android.os.MemoryFile)} to obtain object.
+ */
+public final class SensorDirectChannel implements AutoCloseable {
+
+    // shared memory types
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {TYPE_ASHMEM, TYPE_HARDWARE_BUFFER})
+    public @interface MemoryType {};
+    /**
+     * Shared memory type ashmem, wrapped in MemoryFile object.
+     *
+     * @see SensorManager#createDirectChannel(MemoryFile)
+     */
+    public static final int TYPE_ASHMEM = 1;
+
+    /**
+     * Shared memory type wrapped by HardwareBuffer object.
+     *
+     * @see SensorManager#createDirectChannel(HardwareBuffer)
+     */
+    public static final int TYPE_HARDWARE_BUFFER = 2;
+
+    // sensor rate levels
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {RATE_STOP, RATE_NORMAL, RATE_FAST, RATE_VERY_FAST})
+    public @interface RateLevel {};
+
+    /**
+     * Sensor stopped (no event output).
+     *
+     * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+     */
+    public static final int RATE_STOP = 0;
+    /**
+     * Sensor operates at nominal rate of 50Hz.
+     *
+     * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 27.5Hz to
+     * 110Hz.
+     *
+     * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+     */
+    public static final int RATE_NORMAL = 1; //50Hz
+    /**
+     * Sensor operates at nominal rate of 200Hz.
+     *
+     * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 110Hz to
+     * 440Hz.
+     *
+     * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+     */
+    public static final int RATE_FAST = 2; // ~200Hz
+    /**
+     * Sensor operates at nominal rate of 800Hz.
+     *
+     * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 440Hz to
+     * 1760Hz.
+     *
+     * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+     */
+    public static final int RATE_VERY_FAST = 3; // ~800Hz
+
+    /**
+     * Determine if a channel is still valid. A channel is invalidated after {@link #close()} is
+     * called.
+     *
+     * @return <code>true</code> if channel is valid.
+     */
+    public boolean isValid() {
+        return !mClosed.get();
+    }
+
+    /**
+     * Close sensor direct channel.
+     *
+     * Stop all active sensor in the channel and free sensor system resource related to channel.
+     * Shared memory used for creating the direct channel need to be closed or freed separately.
+     *
+     * @see SensorManager#createDirectChannel(MemoryFile)
+     * @see SensorManager#createDirectChannel(HardwareBuffer)
+     */
+    @Override
+    public void close() {
+        mCloseGuard.close();
+        if (mClosed.compareAndSet(false, true)) {
+            // actual close action
+            mManager.destroyDirectChannel(this);
+        }
+    }
+
+    /** @hide */
+    SensorDirectChannel(SensorManager manager, int id, int type, long size) {
+        mManager = manager;
+        mNativeHandle = id;
+        mType = type;
+        mSize = size;
+        mCloseGuard.open("SensorDirectChannel");
+    }
+
+    /** @hide */
+    int getNativeHandle() {
+        return mNativeHandle;
+    }
+
+    /**
+     * This function encode handle information in {@link android.os.Memory} into a long array to be
+     * passed down to native methods.
+     *
+     * @hide */
+    static long[] encodeData(MemoryFile ashmem) {
+        int fd;
+        try {
+            fd = ashmem.getFileDescriptor().getInt$();
+        } catch (IOException e) {
+            fd = -1;
+        }
+        return new long[] { 1 /*numFds*/, 0 /*numInts*/, fd };
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            mCloseGuard.warnIfOpen();
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private final AtomicBoolean mClosed = new AtomicBoolean();
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final SensorManager mManager;
+    private final int mNativeHandle;
+    private final long mSize;
+    private final int mType;
+}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 3ccac69..cfda2f4 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -19,6 +19,7 @@
 import android.annotation.SystemApi;
 import android.os.Build;
 import android.os.Handler;
+import android.os.MemoryFile;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -881,6 +882,104 @@
 
 
     /**
+     * Create a sensor direct channel backed by shared memory wrapped by MemoryFile object.
+     *
+     * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct
+     * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()}
+     * to free up resource in sensor system associated with the direct channel.
+     *
+     * @param mem A {@link android.os.MemoryFile} shared memory object.
+     * @return A {@link android.hardware.SensorDirectChannel} object if successful, null otherwise.
+     * @throws IllegalArgumentException when mem is null.
+     * @see SensorDirectChannel#close()
+     * @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
+     */
+    public SensorDirectChannel createDirectChannel(MemoryFile mem) {
+        return createDirectChannelImpl(mem.length(), mem, null);
+    }
+
+    /**
+     * Create a sensor direct channel backed by shared memory wrapped by HardwareBuffer object.
+     *
+     * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct
+     * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()}
+     * to free up resource in sensor system associated with the direct channel.
+     *
+     * @param mem A {@link android.hardware.HardwareBuffer} shared memory object.
+     * @return A {@link android.hardware.SensorDirectChannel} object if successful,
+     *         null otherwise.
+     * @throws IllegalArgumentException when mem is null.
+     * @see SensorDirectChannel#close()
+     * @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
+     */
+    public SensorDirectChannel createDirectChannel(HardwareBuffer mem) {
+        return null;
+    }
+
+    /** @hide */
+    protected abstract SensorDirectChannel createDirectChannelImpl(long size,
+            MemoryFile ashmemFile, HardwareBuffer hardwareBuffer);
+
+    /** @hide */
+    void destroyDirectChannel(SensorDirectChannel channel) {
+        destroyDirectChannelImpl(channel);
+    }
+
+    /** @hide */
+    protected abstract void destroyDirectChannelImpl(SensorDirectChannel channel);
+
+    /**
+     * Configure sensor rate or stop sensor report on a direct report channel specified.
+     *
+     * To start event report of a sensor, or change rate of existing report, call this function with
+     * rateLevel other than {@link android.hardware.SensorDirectChannel#RATE_STOP}. Sensor events
+     * will be added into a queue formed by the shared memory used in creation of direction channel.
+     * Each element of the queue has size of 104 bytes and represents a sensor event. Data
+     * structure of an element (all fields in little-endian):
+     *
+     *   offset   type                    name
+     *-  ---------------------------------------------
+     *   0x0000   int32_t                 size (always 104)
+     *   0x0004   int32_t                 sensor report token
+     *   0x0008   int32_t                 type (see SensorType)
+     *   0x000C   uint32_t                atomic counter
+     *   0x0010   int64_t                 timestamp (see Event)
+     *   0x0018   float[16]/int64_t[8]    data (data type depends on sensor type)
+     *   0x0058   int32_t[4]              reserved (set to zero)
+     *
+     * There is no head or tail pointers. The sequence and frontier of new sensor events is
+     * determined by the atomic counter, which counts from 1 after creation of direct channel and
+     * increments 1 for each new event. The writer in sensor system will wrap around from to
+     * start of shared memory region when it reaches the end. If size of memory region is not
+     * a multiple of size of element (104 bytes), the residual is not used at the end.
+     * Function returns a positive sensor report token on success. This token can be used for
+     * differentiate sensor events from multiple sensor of the same type. For example, if there are
+     * two accelerometer in the system A and B, it is guaranteed different report tokens will be
+     * returned when starting sensor A and B.
+     *
+     * To stop a sensor, call this function with rateLevel equal {@link
+     * android.hardware.SensorDirectChannel#RATE_STOP}. If the sensor parameter is left to be null,
+     * this will stop all active sensor report associated with the direct channel specified.
+     * Function return 1 on success or 0 on failure.
+     *
+     * @param channel A {@link android.hardware.SensorDirectChannel} object representing direct
+     *                channel to be configured.
+     * @param sensor A {@link android.hardware.Sensor} object to denote sensor to be operated.
+     * @param rateLevel rate level defined in {@link android.hardware.SensorDirectChannel}.
+     * @return starting report or changing rate: positive sensor report token on success, 0 on failure;
+     *         stopping report: 1 on success, 0 on failure.
+     * @throws IllegalArgumentException when SensorDirectChannel is null.
+     */
+    public int configureDirectChannel(SensorDirectChannel channel, Sensor sensor,
+            @SensorDirectChannel.RateLevel int rateLevel) {
+        return configureDirectChannelImpl(channel, sensor, rateLevel);
+    }
+
+    /** @hide */
+    protected abstract int configureDirectChannelImpl(
+            SensorDirectChannel channel, Sensor s, int rate);
+
+    /**
      * Used for receiving notifications from the SensorManager when dynamic sensors are connected or
      * disconnected.
      */
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 259ca03..4992def 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.MemoryFile;
 import android.os.MessageQueue;
 import android.util.Log;
 import android.util.SparseArray;
@@ -57,6 +58,13 @@
     private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
 
+    private static native int nativeCreateDirectChannel(
+            long nativeInstance, long size, int channelType, long [] channelData);
+    private static native void nativeDestroyDirectChannel(
+            long nativeInstance, int channelHandle);
+    private static native int nativeConfigDirectChannel(
+            long nativeInstance, int channelHandle, int sensorHandle, int rate);
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static boolean sNativeClassInited = false;
@@ -484,6 +492,71 @@
         return changed;
     }
 
+    /** @hide */
+    protected int configureDirectChannelImpl(
+            SensorDirectChannel channel, Sensor sensor, int rate) {
+        if (channel == null) throw new IllegalArgumentException("channel cannot be null");
+
+        if (!channel.isValid()) {
+            throw new IllegalStateException("channel is invalid");
+        }
+
+        if (rate < SensorDirectChannel.RATE_STOP
+                || rate > SensorDirectChannel.RATE_VERY_FAST) {
+            throw new IllegalArgumentException("rate parameter invalid");
+        }
+
+        if (sensor == null && rate != SensorDirectChannel.RATE_STOP) {
+            // the stop all sensors case
+            throw new IllegalArgumentException(
+                    "when sensor is null, rate can only be DIRECT_RATE_STOP");
+        }
+
+        int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
+
+        int ret = nativeConfigDirectChannel(
+                mNativeInstance, channel.getNativeHandle(), sensorHandle, rate);
+
+        if (rate == SensorDirectChannel.RATE_STOP) {
+            return (ret == 0) ? 1 : 0;
+        } else {
+            return (ret > 0) ? ret : 0;
+        }
+    }
+
+    /** @hide */
+    protected SensorDirectChannel createDirectChannelImpl(long size,
+            MemoryFile ashmemFile, HardwareBuffer grallocMemObject) {
+        SensorDirectChannel ch = null;
+
+        if (size <= 0) throw new IllegalArgumentException("size has to be greater than 0");
+
+        if (ashmemFile != null) {
+            if (size != ashmemFile.length()) {
+                throw new IllegalArgumentException("size has to match MemoryFile.length()");
+            }
+            int id = nativeCreateDirectChannel(
+                    mNativeInstance, size, SensorDirectChannel.TYPE_ASHMEM,
+                    SensorDirectChannel.encodeData(ashmemFile));
+            if (id > 0) {
+                ch = new SensorDirectChannel(this, id, SensorDirectChannel.TYPE_ASHMEM, size);
+            }
+        } else if (grallocMemObject != null) {
+            Log.wtf(TAG, "Implement GRALLOC or remove GRALLOC support entirely");
+        } else {
+            throw new IllegalArgumentException("Invalid parameter");
+        }
+
+        return ch;
+    }
+
+    /** @hide */
+    protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+        if (channel != null) {
+            nativeDestroyDirectChannel(mNativeInstance, channel.getNativeHandle());
+        }
+    }
+
     /*
      * BaseEventQueue is the communication channel with the sensor service,
      * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 62b7f32..bcebb7d 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -221,8 +221,8 @@
     public abstract void tearDown(@NonNull Surface surface) throws CameraAccessException;
 
     /**
-     * <p>Finish the deferred output configurations where the output Surface was not configured
-     * before.</p>
+     * <p>Finalize the output configurations that now have their deferred and/or extra Surfaces
+     * included.</p>
      *
      * <p>For camera use cases where a preview and other output configurations need to be
      * configured, it can take some time for the preview Surface to be ready. For example, if the
@@ -235,22 +235,31 @@
      * and defer the preview output configuration until the Surface is ready. After the
      * {@link CameraCaptureSession} is created successfully with this deferred output and other
      * normal outputs, the application can start submitting requests as long as they do not include
-     * deferred output Surfaces. Once a deferred Surface is ready, the application can set the
-     * Surface on the deferred output configuration with the
-     * {@link OutputConfiguration#setDeferredSurface} method, and then finish the deferred output
+     * deferred output Surfaces. Once a deferred Surface is ready, the application can add the
+     * Surface to the deferred output configuration with the
+     * {@link OutputConfiguration#addSurface} method, and then update the deferred output
      * configuration via this method, before it can submit capture requests with this output
      * target.</p>
      *
-     * <p>The output Surfaces included by this list of deferred
+     * <p>This function can also be called in case where multiple surfaces share the same
+     * OutputConfiguration, and one of the surfaces becomes available after the {@link
+     * CameraCaptureSession} is created. In that case, the application must first create the
+     * OutputConfiguration with the available Surface, then enable furture surface sharing via
+     * {@link OutputConfiguration#enableSurfaceSharing}, before creating the CameraCaptureSession.
+     * After the CameraCaptureSession is created, and once the extra Surface becomes available, the
+     * application must then call {@link OutputConfiguration#addSurface} before finalizing the
+     * configuration with this method.</p>
+     *
+     * <p>The output Surfaces included by this list of
      * {@link OutputConfiguration OutputConfigurations} can be used as {@link CaptureRequest}
      * targets as soon as this call returns.</p>
      *
      * <p>This method is not supported by
      * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}-level devices.</p>
      *
-     * @param deferredOutputConfigs a list of {@link OutputConfiguration OutputConfigurations} that
-     *            have had {@link OutputConfiguration#setDeferredSurface setDeferredSurface} invoked
-     *            with a valid output Surface.
+     * @param outputConfigs a list of {@link OutputConfiguration OutputConfigurations} that
+     *            have had {@link OutputConfiguration#addSurface addSurface} invoked with a valid
+     *            output Surface after {@link CameraDevice#createCaptureSessionByOutputConfigurations}.
      * @throws CameraAccessException if the camera device is no longer connected or has encountered
      *             a fatal error.
      * @throws IllegalStateException if this session is no longer active, either because the session
@@ -261,8 +270,8 @@
      *             source. Or if one of the output configuration was already finished with an
      *             included surface in a prior call.
      */
-    public abstract void finishDeferredConfiguration(
-            List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException;
+    public abstract void finalizeOutputConfigurations(
+            List<OutputConfiguration> outputConfigs) throws CameraAccessException;
 
     /**
      * <p>Submit a request for an image to be captured by the camera device.</p>
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 98a8904..12b46c1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1625,6 +1625,34 @@
             new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
 
     /**
+     * <p>Allow camera device to enable zero-shutter-lag mode for requests with
+     * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE.</p>
+     * <p>If enableZsl is <code>true</code>, the camera device may enable zero-shutter-lag mode for requests with
+     * STILL_CAPTURE capture intent. The camera device may use images captured in the past to
+     * produce output images for a zero-shutter-lag request. The result metadata including the
+     * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} reflects the source frames used to produce output images.
+     * Therefore, the contents of the output images and the result metadata may be out of order
+     * compared to previous regular requests. enableZsl does not affect requests with other
+     * capture intents.</p>
+     * <p>For example, when requests are submitted in the following order:
+     *   Request A: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is PREVIEW
+     *   Request B: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is STILL_CAPTURE</p>
+     * <p>The output images for request B may have contents captured before the output images for
+     * request A, and the result metadata for request B may be older than the result metadata for
+     * request A.</p>
+     * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in the
+     * past for requests with STILL_CAPTURE capture intent.</p>
+     * <p>The value of enableZsl in capture templates is always <code>false</code> if present.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+     * @see CaptureResult#SENSOR_TIMESTAMP
+     */
+    @PublicKey
+    public static final Key<Boolean> CONTROL_ENABLE_ZSL =
+            new Key<Boolean>("android.control.enableZsl", boolean.class);
+
+    /**
      * <p>Operation mode for edge
      * enhancement.</p>
      * <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 23f4b9e..3f8b57a 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2132,6 +2132,34 @@
             new Key<Integer>("android.control.postRawSensitivityBoost", int.class);
 
     /**
+     * <p>Allow camera device to enable zero-shutter-lag mode for requests with
+     * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE.</p>
+     * <p>If enableZsl is <code>true</code>, the camera device may enable zero-shutter-lag mode for requests with
+     * STILL_CAPTURE capture intent. The camera device may use images captured in the past to
+     * produce output images for a zero-shutter-lag request. The result metadata including the
+     * {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} reflects the source frames used to produce output images.
+     * Therefore, the contents of the output images and the result metadata may be out of order
+     * compared to previous regular requests. enableZsl does not affect requests with other
+     * capture intents.</p>
+     * <p>For example, when requests are submitted in the following order:
+     *   Request A: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is PREVIEW
+     *   Request B: enableZsl is ON, {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} is STILL_CAPTURE</p>
+     * <p>The output images for request B may have contents captured before the output images for
+     * request A, and the result metadata for request B may be older than the result metadata for
+     * request A.</p>
+     * <p>Note that when enableZsl is <code>true</code>, it is not guaranteed to get output images captured in the
+     * past for requests with STILL_CAPTURE capture intent.</p>
+     * <p>The value of enableZsl in capture templates is always <code>false</code> if present.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+     * @see CaptureResult#SENSOR_TIMESTAMP
+     */
+    @PublicKey
+    public static final Key<Boolean> CONTROL_ENABLE_ZSL =
+            new Key<Boolean>("android.control.enableZsl", boolean.class);
+
+    /**
      * <p>Operation mode for edge
      * enhancement.</p>
      * <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 4befb29..891df63 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -151,9 +151,9 @@
     }
 
     @Override
-    public void finishDeferredConfiguration(
-            List<OutputConfiguration> deferredOutputConfigs) throws CameraAccessException {
-        mDeviceImpl.finishDeferredConfig(deferredOutputConfigs);
+    public void finalizeOutputConfigurations(
+            List<OutputConfiguration> outputConfigs) throws CameraAccessException {
+        mDeviceImpl.finalizeOutputConfigs(outputConfigs);
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index 01e58f4..15dbf26 100644
--- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -258,9 +258,9 @@
     }
 
     @Override
-    public void finishDeferredConfiguration(List<OutputConfiguration> deferredOutputConfigs)
+    public void finalizeOutputConfigurations(List<OutputConfiguration> deferredOutputConfigs)
             throws CameraAccessException {
-        mSessionImpl.finishDeferredConfiguration(deferredOutputConfigs);
+        mSessionImpl.finalizeOutputConfigurations(deferredOutputConfigs);
     }
 
     private class WrapperCallback extends StateCallback {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index d2aeaea..2364ebe 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -743,14 +743,14 @@
         }
     }
 
-    public void finishDeferredConfig(List<OutputConfiguration> deferredConfigs)
+    public void finalizeOutputConfigs(List<OutputConfiguration> outputConfigs)
             throws CameraAccessException {
-        if (deferredConfigs == null || deferredConfigs.size() == 0) {
+        if (outputConfigs == null || outputConfigs.size() == 0) {
             throw new IllegalArgumentException("deferred config is null or empty");
         }
 
         synchronized(mInterfaceLock) {
-            for (OutputConfiguration config : deferredConfigs) {
+            for (OutputConfiguration config : outputConfigs) {
                 int streamId = -1;
                 for (int i = 0; i < mConfiguredOutputs.size(); i++) {
                     // Have to use equal here, as createCaptureSessionByOutputConfigurations() and
@@ -765,11 +765,11 @@
                             + "session");
                 }
 
-                if (config.getSurface() == null) {
-                    throw new IllegalArgumentException("The deferred config for stream " + streamId
-                            + " must have a non-null surface");
+                if (config.getSurfaces().size() == 0) {
+                    throw new IllegalArgumentException("The final config for stream " + streamId
+                            + " must have at least 1 surface");
                 }
-                mRemoteDevice.setDeferredConfiguration(streamId, config);
+                mRemoteDevice.finalizeOutputConfigurations(streamId, config);
             }
         }
     }
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index d77f60b..d9f666e 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -215,10 +215,10 @@
         }
     }
 
-    public void setDeferredConfiguration(int streamId, OutputConfiguration deferredConfig)
+    public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
             throws CameraAccessException {
         try {
-            mRemoteDevice.setDeferredConfiguration(streamId, deferredConfig);
+            mRemoteDevice.finalizeOutputConfigurations(streamId, deferredConfig);
         } catch (Throwable t) {
             CameraManager.throwAsPublicException(t);
             throw new UnsupportedOperationException("Unexpected exception", t);
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 2a9bf6b..d8ec4df 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -578,8 +578,8 @@
     }
 
     @Override
-    public void setDeferredConfiguration(int steamId, OutputConfiguration config) {
-        String err = "Set deferred configuration is not supported on legacy devices";
+    public void finalizeOutputConfigurations(int steamId, OutputConfiguration config) {
+        String err = "Finalizing output configuration is not supported on legacy devices";
         Log.e(TAG, err);
         throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
     }
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 4654fc2..6b7546f 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -34,6 +34,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Collections;
+import java.util.ArrayList;
 
 import static com.android.internal.util.Preconditions.*;
 
@@ -116,6 +117,15 @@
     private final int SURFACE_TYPE_SURFACE_TEXTURE = 1;
 
     /**
+     * Maximum number of surfaces supported by one {@link OutputConfiguration}.
+     *
+     * <p>The combined number of surfaces added by the constructor and
+     * {@link OutputConfiguration#addSurface} should not exceed this value.</p>
+     *
+     */
+    private static final int MAX_SURFACES_COUNT = 2;
+
+    /**
      * Create a new {@link OutputConfiguration} instance with a {@link Surface},
      * with a surface group ID.
      *
@@ -151,50 +161,6 @@
     }
 
     /**
-     * Create a new {@link OutputConfiguration} instance with two surfaces sharing the same stream,
-     * with a surface group ID.
-     *
-     * <p>For advanced use cases, a camera application may require more streams than the combination
-     * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, two compatible
-     * surfaces can be attached to one OutputConfiguration so that they map to one camera stream,
-     * and buffers are reference counted when being consumed by both surfaces. </p>
-     *
-     * <p>Two surfaces are compatible in below 2 cases:</p>
-     *
-     * <ol>
-     * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case,
-     * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed.
-     *
-     * <li> Surfaces with the same size, format, and dataSpace, but different Surface
-     * source classes. However, on some devices, the underlying camera device is able to use the
-     * same buffer layout for both surfaces. The only way to discover if this is the case is to
-     * create a capture session with that output configuration. For example, if the camera device
-     * uses the same private buffer format between a SurfaceView/SurfaceTexture and a
-     * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations}
-     * will succeed. Otherwise, it throws {@code IllegalArgumentException}.
-     * </ol>
-     *
-     * @param surfaceGroupId
-     *          A group ID for this output, used for sharing memory between multiple outputs.
-     * @param surface
-     *          A Surface for camera to output to.
-     * @param surface2
-     *          Second surface for camera to output to.
-     * @throws IllegalArgumentException if the two surfaces have different size, format, or
-     * dataSpace.
-     *
-     * @hide
-     */
-    public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface,
-            @NonNull Surface surface2) {
-        this(surfaceGroupId, surface, ROTATION_0, surface2);
-
-        checkNotNull(surface2, "Surface must not be null");
-        checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace,
-                mConfiguredGenerationId, surface2);
-    }
-
-    /**
      * Create a new {@link OutputConfiguration} instance.
      *
      * <p>This constructor takes an argument for desired camera rotation</p>
@@ -240,68 +206,19 @@
      */
     @SystemApi
     public OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation) {
-        this(surfaceGroupId, surface, rotation, null /*surface2*/);
-    }
-
-    /**
-     * Create a new {@link OutputConfiguration} instance, with rotation, a group ID, and a secondary
-     * surface.
-     *
-     * <p>This constructor takes an argument for desired camera rotation, the surface group
-     * ID, and a secondary surface.  See {@link #OutputConfiguration(int, Surface)} for details
-     * of the group ID.</p>
-     *
-     * <p>surface2 should be compatible with surface. See {@link #OutputConfiguration(int, Surface,
-     * Surface} for details of compatibility between surfaces.</p>
-     *
-     * <p>Since the rotation is done by the CameraDevice, both surfaces will receive buffers with
-     * the same rotation applied. This means that if the application needs two compatible surfaces
-     * to have different rotations, these surfaces cannot be shared within one OutputConfiguration.
-     * </p>
-     *
-     * @param surfaceGroupId
-     *          A group ID for this output, used for sharing memory between multiple outputs.
-     * @param surface
-     *          A Surface for camera to output to.
-     * @param rotation
-     *          The desired rotation to be applied on camera output. Value must be one of
-     *          ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degrees,
-     *          application should make sure corresponding surface size has width and height
-     *          transposed relative to the width and height without rotation. For example,
-     *          if application needs camera to capture 1280x720 picture and rotate it by 90 degree,
-     *          application should set rotation to {@code ROTATION_90} and make sure the
-     *          corresponding Surface size is 720x1280. Note that {@link CameraDevice} might
-     *          throw {@code IllegalArgumentException} if device cannot perform such rotation.
-     * @param surface2
-     *          Second surface for camera to output to.
-
-     * @throws IllegalArgumentException if the two surfaces are not compatible to be shared in
-     *                                  one OutputConfiguration.
-     *
-     * @hide
-     */
-    private OutputConfiguration(int surfaceGroupId, @NonNull Surface surface, int rotation,
-            @Nullable Surface surface2) {
         checkNotNull(surface, "Surface must not be null");
         checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
-
         mSurfaceGroupId = surfaceGroupId;
         mSurfaceType = SURFACE_TYPE_UNKNOWN;
+        mSurfaces = new ArrayList<Surface>();
+        mSurfaces.add(surface);
         mRotation = rotation;
         mConfiguredSize = SurfaceUtils.getSurfaceSize(surface);
         mConfiguredFormat = SurfaceUtils.getSurfaceFormat(surface);
         mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(surface);
         mConfiguredGenerationId = surface.getGenerationId();
         mIsDeferredConfig = false;
-
-        if (surface2 == null) {
-            mSurfaces = new Surface[1];
-            mSurfaces[0] = surface;
-        } else {
-            mSurfaces = new Surface[MAX_SURFACES_COUNT];
-            mSurfaces[0] = surface;
-            mSurfaces[1] = surface2;
-        }
+        mIsShared = false;
     }
 
     /**
@@ -309,16 +226,16 @@
      * source class.
      * <p>
      * This constructor takes an argument for desired Surface size and the Surface source class
-     * without providing the actual output Surface. This is used to setup a output configuration
+     * without providing the actual output Surface. This is used to setup an output configuration
      * with a deferred Surface. The application can use this output configuration to create a
      * session.
      * </p>
      * <p>
-     * However, the actual output Surface must be set via {@link #setDeferredSurface} and finish the
-     * deferred Surface configuration via {@link CameraCaptureSession#finishDeferredConfiguration}
-     * before submitting a request with this Surface target. The deferred Surface can only be
-     * obtained from either from {@link android.view.SurfaceView} by calling
-     * {@link android.view.SurfaceHolder#getSurface}, or from
+     * However, the actual output Surface must be set via {@link #addSurface} and the deferred
+     * Surface configuration must be finalized via {@link
+     * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this
+     * Surface target. The deferred Surface can only be obtained either from {@link
+     * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from
      * {@link android.graphics.SurfaceTexture} via
      * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).
      * </p>
@@ -329,41 +246,68 @@
      *            {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported.
      */
     public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) {
-        this(surfaceSize, klass, true /* dummy */);
+        checkNotNull(klass, "surfaceSize must not be null");
+        checkNotNull(klass, "klass must not be null");
+        if (klass == android.view.SurfaceHolder.class) {
+            mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
+        } else if (klass == android.graphics.SurfaceTexture.class) {
+            mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
+        } else {
+            mSurfaceType = SURFACE_TYPE_UNKNOWN;
+            throw new IllegalArgumentException("Unknow surface source class type");
+        }
 
-        mSurfaces = new Surface[1];
+        mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
+        mSurfaces = new ArrayList<Surface>();
+        mRotation = ROTATION_0;
+        mConfiguredSize = surfaceSize;
+        mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
+        mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
+        mConfiguredGenerationId = 0;
+        mIsDeferredConfig = true;
+        mIsShared = false;
     }
 
     /**
-     * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface
-     * source class for the deferred surface, and a secondary surface.
+     * Enable multiple surfaces sharing the same OutputConfiguration
      *
-     * <p>This constructor takes an argument for desired surface size and surface source class of
-     * the deferred surface, and a secondary surface. See {@link #OutputConfiguration(Size, Class)}
-     * for details of the surface size and surface source class.</p>
+     * <p>For advanced use cases, a camera application may require more streams than the combination
+     * guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one
+     * compatible surface can be attached to an OutputConfiguration so that they map to one
+     * camera stream, and the outputs share memory buffers when possible. </p>
      *
-     * <p> The deferred surface and secondary surface should be compatible. See
-     * {@link #OutputConfiguration(int, Surface, Surface)} for details of compatible surfaces.
+     * <p>Two surfaces are compatible in the below cases:</p>
      *
-     * @hide
+     * <li> Surfaces with the same size, format, dataSpace, and Surface source class. In this case,
+     * {@link CameraDevice#createCaptureSessionByOutputConfigurations} is guaranteed to succeed.
+     *
+     * <li> Surfaces with the same size, format, and dataSpace, but different Surface source classes
+     * that are generally not compatible. However, on some devices, the underlying camera device is
+     * able to use the same buffer layout for both surfaces. The only way to discover if this is the
+     * case is to create a capture session with that output configuration. For example, if the
+     * camera device uses the same private buffer format between a SurfaceView/SurfaceTexture and a
+     * MediaRecorder/MediaCodec, {@link CameraDevice#createCaptureSessionByOutputConfigurations}
+     * will succeed. Otherwise, it throws {@code IllegalArgumentException}.
+     * </ol>
+     *
+     * <p>To enable surface sharing, this function must be called before {@link
+     * CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link
+     * CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p>
+     *
+     * <p>Up to 2 surfaces can be shared for an OutputConfiguration. The supported surfaces for
+     * sharing must be of type SurfaceTexture, SurfaceView, MediaRecorder, MediaCodec, or
+     * implementation defined ImageReader.</p>
      */
-    public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass,
-            @NonNull Surface surface2) {
-        this(surfaceSize, klass, true /* dummy */);
-
-        checkMatchingSurfaces(mConfiguredSize, mConfiguredFormat, mConfiguredDataspace,
-                mConfiguredGenerationId, surface2);
-
-        mSurfaces = new Surface[MAX_SURFACES_COUNT];
-        mSurfaces[0] = null;
-        mSurfaces[1] = surface2;
+    public void enableSurfaceSharing() {
+        mIsShared = true;
     }
 
     /**
      * Check if this configuration has deferred configuration.
      *
-     * <p>This will return true if the output configuration was constructed with surface deferred.
-     * It will return true even after the deferred surface is set later.</p>
+     * <p>This will return true if the output configuration was constructed with surface deferred by
+     * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after
+     * the deferred surface is added later by {@link OutputConfiguration#addSurface}.</p>
      *
      * @return true if this configuration has deferred surface.
      * @hide
@@ -373,38 +317,63 @@
     }
 
     /**
-     * Set the deferred surface to this OutputConfiguration.
+     * Add a surface to this OutputConfiguration.
      *
-     * <p>
-     * The deferred surface must be obtained from either from {@link android.view.SurfaceView} by
-     * calling {@link android.view.SurfaceHolder#getSurface}, or from
-     * {@link android.graphics.SurfaceTexture} via
-     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). After the deferred
-     * surface is set, the application must finish the deferred surface configuration via
-     * {@link CameraCaptureSession#finishDeferredConfiguration} before submitting a request with
-     * this surface target.
+     * <p> This function can be called before or after {@link
+     * CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after,
+     * the application must finalize the capture session with
+     * {@link CameraCaptureSession#finalizeOutputConfigurations}.
      * </p>
      *
-     * @param surface The deferred surface to be set.
-     * @throws IllegalArgumentException if the Surface is invalid.
-     * @throws IllegalStateException if a Surface was already set to this deferred
-     *         OutputConfiguration.
+     * <p> If the OutputConfiguration was constructed with a deferred surface by {@link
+     * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
+     * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface},
+     * or from {@link android.graphics.SurfaceTexture} via
+     * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p>
+     *
+     * <p> If the OutputConfiguration was constructed by other constructors, the added
+     * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for
+     * details of compatible surfaces.</p>
+     *
+     * <p> If the OutputConfiguration already contains a Surface, {@link #enableSurfaceSharing} must
+     * be called before calling this function to add a new Surface.</p>
+     *
+     * @param surface The surface to be added.
+     * @throws IllegalArgumentException if the Surface is invalid, the Surface's
+     *         size/dataspace/format doesn't match, or adding the Surface would exceed number of
+     *         shared surfaces supported.
+     * @throws IllegalStateException if the Surface was already added to this OutputConfiguration,
+     *         or if the OutputConfiguration is not shared and it already has a surface associated
+     *         with it.
      */
-    public void setDeferredSurface(@NonNull Surface surface) {
+    public void addSurface(@NonNull Surface surface) {
         checkNotNull(surface, "Surface must not be null");
-        if (mSurfaces[0] != null) {
-            throw new IllegalStateException("Deferred surface is already set!");
+        if (mSurfaces.contains(surface)) {
+            throw new IllegalStateException("Surface is already added!");
+        }
+        if (mSurfaces.size() == 1 && !mIsShared) {
+            throw new IllegalStateException("Cannot have 2 surfaces for a non-sharing configuration");
+        }
+        if (mSurfaces.size() + 1 > MAX_SURFACES_COUNT) {
+            throw new IllegalArgumentException("Exceeds maximum number of surfaces");
         }
 
-        // This will throw IAE is the surface was abandoned.
-        Size surfaceSize = SurfaceUtils.getSurfaceSize(surface);
-        if (!surfaceSize.equals(mConfiguredSize)) {
-            Log.w(TAG, "Deferred surface size " + surfaceSize +
-                    " is different with pre-configured size " + mConfiguredSize +
-                    ", the pre-configured size will be used.");
+        // TODO: b/34697112. This needs to be reverted once app fix is merged.
+        // Do not throw exception for below case:
+        // - OutputConfiguration(Size(0, 0), klass)
+        // - addSurface(surface)
+        if ((mConfiguredSize.getWidth() != 0 || mConfiguredSize.getHeight() != 0) &&
+                !mConfiguredSize.equals(SurfaceUtils.getSurfaceSize(surface))) {
+            throw new IllegalArgumentException("The size of added surface doesn't match");
+        }
+        if (mConfiguredDataspace != SurfaceUtils.getSurfaceDataspace(surface)) {
+            throw new IllegalArgumentException("The dataspace of added surface doesn't match");
+        }
+        if (mConfiguredFormat != SurfaceUtils.getSurfaceFormat(surface)) {
+            throw new IllegalArgumentException("The format of added surface format doesn't match");
         }
 
-        mSurfaces[0] = surface;
+        mSurfaces.add(surface);
     }
 
     /**
@@ -432,49 +401,6 @@
     }
 
     /**
-     * Private constructor to initialize Configuration based on surface size and class
-     */
-    private <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass,
-            boolean dummy) {
-        checkNotNull(surfaceSize, "surfaceSize must not be null");
-        checkNotNull(klass, "klass must not be null");
-        if (klass == android.view.SurfaceHolder.class) {
-            mSurfaceType = SURFACE_TYPE_SURFACE_VIEW;
-        } else if (klass == android.graphics.SurfaceTexture.class) {
-            mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE;
-        } else {
-            mSurfaceType = SURFACE_TYPE_UNKNOWN;
-            throw new IllegalArgumentException("Unknow surface source class type");
-        }
-
-        mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
-        mRotation = ROTATION_0;
-        mConfiguredSize = surfaceSize;
-        mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
-        mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
-        mConfiguredGenerationId = 0;
-        mIsDeferredConfig = true;
-    }
-
-    /**
-     * Check if the surface properties match that of the given surface.
-     *
-     * @return true if the properties and the surface match.
-     */
-    private void checkMatchingSurfaces(Size size, int format, int dataSpace, int generationId,
-            @NonNull Surface surface) {
-        if (!size.equals(SurfaceUtils.getSurfaceSize(surface))) {
-            throw new IllegalArgumentException("Secondary surface size doesn't match");
-        }
-        if (dataSpace != SurfaceUtils.getSurfaceDataspace(surface)) {
-            throw new IllegalArgumentException("Secondary surface dataspace doesn't match");
-        }
-        if (format != SurfaceUtils.getSurfaceFormat(surface)) {
-            throw new IllegalArgumentException("Secondary surface format doesn't match");
-        }
-    }
-
-    /**
      * Create an OutputConfiguration from Parcel.
      */
     private OutputConfiguration(@NonNull Parcel source) {
@@ -483,27 +409,9 @@
         int surfaceType = source.readInt();
         int width = source.readInt();
         int height = source.readInt();
-        int surfaceCnt = source.readInt();
-
-        if (surfaceCnt <= 0) {
-            throw new IllegalArgumentException(
-                    "Surface count in OutputConfiguration must be greater than 0");
-        }
-        if (surfaceCnt > MAX_SURFACES_COUNT) {
-            throw new IllegalArgumentException(
-                    "Surface count in OutputConfiguration must not be more than "
-                    + MAX_SURFACES_COUNT);
-        }
-
-        Surface[] surfaces = new Surface[surfaceCnt];
-        for (int i = 0; i < surfaceCnt; i++) {
-            Surface surface = Surface.CREATOR.createFromParcel(source);
-            surfaces[i] = surface;
-
-            if (surface == null && i > 0) {
-                throw new IllegalArgumentException("Only the first surface can be deferred");
-            }
-        }
+        boolean isDeferred = source.readInt() == 1;
+        ArrayList<Surface> surfaces = new ArrayList<Surface>();
+        source.readTypedList(surfaces, Surface.CREATOR);
 
         checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
 
@@ -511,13 +419,13 @@
         mRotation = rotation;
         mSurfaces = surfaces;
         mConfiguredSize = new Size(width, height);
-        // First surface could be null (being deferred). Use last surface to look up surface
-        // characteristics.
-        if (mSurfaces[surfaceCnt-1] != null) {
+        mIsDeferredConfig = isDeferred;
+        mSurfaces = surfaces;
+        if (mSurfaces.size() > 0) {
             mSurfaceType = SURFACE_TYPE_UNKNOWN;
-            mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces[surfaceCnt-1]);
-            mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces[surfaceCnt-1]);
-            mConfiguredGenerationId = mSurfaces[surfaceCnt-1].getGenerationId();
+            mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces.get(0));
+            mConfiguredDataspace = SurfaceUtils.getSurfaceDataspace(mSurfaces.get(0));
+            mConfiguredGenerationId = mSurfaces.get(0).getGenerationId();
         } else {
             mSurfaceType = surfaceType;
             mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE);
@@ -525,38 +433,31 @@
                     StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE);
             mConfiguredGenerationId = 0;
         }
-
-        if (mSurfaces[0] == null) {
-            mIsDeferredConfig = true;
-        } else {
-            mIsDeferredConfig = false;
-        }
     }
 
     /**
      * Get the {@link Surface} associated with this {@link OutputConfiguration}.
      *
-     * @return the {@link Surface} associated with this {@link OutputConfiguration}. If more than
-     * one surface is associated with this {@link OutputConfiguration}, return the first one as
-     * specified in the constructor. If there is a deferred surface, null will be returned.
+     * If more than one surface is associated with this {@link OutputConfiguration}, return the
+     * first one as specified in the constructor or {@link OutputConfiguration#addSurface}.
      */
     public @Nullable Surface getSurface() {
-        return mSurfaces[0];
+        if (mSurfaces.size() == 0) {
+            return null;
+        }
+
+        return mSurfaces.get(0);
     }
 
     /**
      * Get the immutable list of surfaces associated with this {@link OutputConfiguration}.
      *
-     * @return the list of surfaces associated with this {@link OutputConfiguration} in the order
-     * specified in the constructor. If there is a deferred surface in the {@link
-     * OutputConfiguration}, it is returned as null as first element of the list. The list should
-     * not be modified.
-     *
-     * @hide
+     * @return the list of surfaces associated with this {@link OutputConfiguration} as specified in
+     * the constructor and {@link OutputConfiguration#addSurface}. The list should not be modified.
      */
     @NonNull
     public List<Surface> getSurfaces() {
-        return Collections.unmodifiableList(Arrays.asList(mSurfaces));
+        return Collections.unmodifiableList(mSurfaces);
     }
 
     /**
@@ -616,12 +517,9 @@
         dest.writeInt(mSurfaceType);
         dest.writeInt(mConfiguredSize.getWidth());
         dest.writeInt(mConfiguredSize.getHeight());
-        dest.writeInt(mSurfaces.length);
-        for (int i = 0; i < mSurfaces.length; i++) {
-            if (mSurfaces[i] != null) {
-                mSurfaces[i].writeToParcel(dest, flags);
-            }
-        }
+        dest.writeInt(mIsDeferredConfig ? 1 : 0);
+        dest.writeInt(mIsShared ? 1 : 0);
+        dest.writeTypedList(mSurfaces);
     }
 
     /**
@@ -647,16 +545,15 @@
                     mSurfaceGroupId != other.mSurfaceGroupId ||
                     mSurfaceType != other.mSurfaceType ||
                     mIsDeferredConfig != other.mIsDeferredConfig ||
+                    mIsShared != other.mIsShared ||
                     mConfiguredFormat != other.mConfiguredFormat ||
                     mConfiguredDataspace != other.mConfiguredDataspace ||
-                    mSurfaces.length != other.mSurfaces.length ||
                     mConfiguredGenerationId != other.mConfiguredGenerationId)
                 return false;
 
-            // If deferred, skip the first surface of mSurfaces when comparing.
-            int minIndex = (mIsDeferredConfig ? 1 : 0);
-            for (int i = minIndex;  i < mSurfaces.length; i++) {
-                if (mSurfaces[i] != other.mSurfaces[i])
+            int minLen = Math.min(mSurfaces.size(), other.mSurfaces.size());
+            for (int i = 0;  i < minLen; i++) {
+                if (mSurfaces.get(i) != other.mSurfaces.get(i))
                     return false;
             }
 
@@ -670,23 +567,23 @@
      */
     @Override
     public int hashCode() {
-        // Need ensure that the hashcode remains unchanged after set a deferred surface. Otherwise
+        // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise
         // the deferred output configuration will be lost in the camera streammap after the deferred
         // surface is set.
-        int minIndex = (mIsDeferredConfig ? 1 : 0);
-        Surface nonDeferredSurfaces[] = Arrays.copyOfRange(mSurfaces,
-                minIndex, mSurfaces.length);
-        int surfaceHash = HashCodeHelpers.hashCodeGeneric(nonDeferredSurfaces);
+        if (mIsDeferredConfig) {
+            return HashCodeHelpers.hashCode(
+                    mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace,
+                    mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0);
+        }
 
         return HashCodeHelpers.hashCode(
-                mRotation, surfaceHash, mConfiguredGenerationId,
+                mRotation, mSurfaces.hashCode(), mConfiguredGenerationId,
                 mConfiguredSize.hashCode(), mConfiguredFormat,
-                mConfiguredDataspace, mSurfaceGroupId);
+                mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0);
     }
 
     private static final String TAG = "OutputConfiguration";
-    private static final int MAX_SURFACES_COUNT = 2;
-    private Surface mSurfaces[];
+    private ArrayList<Surface> mSurfaces;
     private final int mRotation;
     private final int mSurfaceGroupId;
     // Surface source type, this is only used by the deferred surface configuration objects.
@@ -700,4 +597,6 @@
     private final int mConfiguredGenerationId;
     // Flag indicating if this config has deferred surface.
     private final boolean mIsDeferredConfig;
+    // Flag indicating if this config has shared surfaces
+    private boolean mIsShared;
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 4b57078..2c9e6c7 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1074,7 +1074,7 @@
 
         @Override // binder call
         public void onAuthenticationFailed(long deviceId) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();;
+            mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
         }
 
         @Override // binder call
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index c9a4e9b..fea730e 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -16,11 +16,12 @@
 
 package android.hardware.usb;
 
-import com.android.internal.util.Preconditions;
-
+import android.hardware.usb.V1_0.Constants;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
 /**
  * Represents a physical USB port and describes its characteristics.
  * <p>
@@ -33,6 +34,7 @@
     private final String mId;
     private final int mSupportedModes;
 
+    public static final int MODE_NONE = Constants.PortMode.NONE;
     /**
      * Mode bit: This USB port can act as a downstream facing port (host).
      * <p>
@@ -40,7 +42,7 @@
      * combination of roles (and possibly others as well).
      * </p>
      */
-    public static final int MODE_DFP = 1 << 0;
+    public static final int MODE_DFP = Constants.PortMode.DFP;
 
     /**
      * Mode bit: This USB port can act as an upstream facing port (device).
@@ -49,7 +51,7 @@
      * combination of roles (and possibly others as well).
      * </p>
      */
-    public static final int MODE_UFP = 1 << 1;
+    public static final int MODE_UFP = Constants.PortMode.UFP;
 
     /**
      * Mode bit: This USB port can act either as an downstream facing port (host) or as
@@ -60,29 +62,43 @@
      * combination of roles (and possibly others as well).
      * </p>
      */
-    public static final int MODE_DUAL = MODE_DFP | MODE_UFP;
+    public static final int MODE_DUAL = Constants.PortMode.DRP;
+
+    /**
+     * Power role: This USB port does not have a power role.
+     */
+    public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE;
 
     /**
      * Power role: This USB port can act as a source (provide power).
      */
-    public static final int POWER_ROLE_SOURCE = 1;
+    public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE;
 
     /**
      * Power role: This USB port can act as a sink (receive power).
      */
-    public static final int POWER_ROLE_SINK = 2;
+    public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK;
+
+    /**
+     * Power role: This USB port does not have a data role.
+     */
+    public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE;
 
     /**
      * Data role: This USB port can act as a host (access data services).
      */
-    public static final int DATA_ROLE_HOST = 1;
+    public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST;
 
     /**
      * Data role: This USB port can act as a device (offer data services).
      */
-    public static final int DATA_ROLE_DEVICE = 2;
+    public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE;
 
-    private static final int NUM_DATA_ROLES = 3;
+    private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES;
+    /**
+     * Points to the first power role in the IUsb HAL.
+     */
+    private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE;
 
     /** @hide */
     public UsbPort(String id, int supportedModes) {
@@ -126,14 +142,14 @@
      */
     public static int combineRolesAsBit(int powerRole, int dataRole) {
         checkRoles(powerRole, dataRole);
-        final int index = powerRole * NUM_DATA_ROLES + dataRole;
+        final int index = ((powerRole - POWER_ROLE_OFFSET) * NUM_DATA_ROLES) + dataRole;
         return 1 << index;
     }
 
     /** @hide */
     public static String modeToString(int mode) {
         switch (mode) {
-            case 0:
+            case MODE_NONE:
                 return "none";
             case MODE_DFP:
                 return "dfp";
@@ -149,7 +165,7 @@
     /** @hide */
     public static String powerRoleToString(int role) {
         switch (role) {
-            case 0:
+            case POWER_ROLE_NONE:
                 return "no-power";
             case POWER_ROLE_SOURCE:
                 return "source";
@@ -163,7 +179,7 @@
     /** @hide */
     public static String dataRoleToString(int role) {
         switch (role) {
-            case 0:
+            case DATA_ROLE_NONE:
                 return "no-data";
             case DATA_ROLE_HOST:
                 return "host";
@@ -183,7 +199,7 @@
         while (combo != 0) {
             final int index = Integer.numberOfTrailingZeros(combo);
             combo &= ~(1 << index);
-            final int powerRole = index / NUM_DATA_ROLES;
+            final int powerRole = (index / NUM_DATA_ROLES + POWER_ROLE_OFFSET);
             final int dataRole = index % NUM_DATA_ROLES;
             if (first) {
                 first = false;
@@ -200,9 +216,28 @@
     }
 
     /** @hide */
+    public static void checkMode(int powerRole) {
+        Preconditions.checkArgumentInRange(powerRole, Constants.PortMode.NONE,
+                Constants.PortMode.NUM_MODES - 1, "portMode");
+    }
+
+    /** @hide */
+    public static void checkPowerRole(int dataRole) {
+        Preconditions.checkArgumentInRange(dataRole, Constants.PortPowerRole.NONE,
+                Constants.PortPowerRole.NUM_POWER_ROLES - 1, "powerRole");
+    }
+
+    /** @hide */
+    public static void checkDataRole(int mode) {
+        Preconditions.checkArgumentInRange(mode, Constants.PortDataRole.NONE,
+                Constants.PortDataRole.NUM_DATA_ROLES - 1, "powerRole");
+    }
+
+    /** @hide */
     public static void checkRoles(int powerRole, int dataRole) {
-        Preconditions.checkArgumentInRange(powerRole, 0, POWER_ROLE_SINK, "powerRole");
-        Preconditions.checkArgumentInRange(dataRole, 0, DATA_ROLE_DEVICE, "dataRole");
+        Preconditions.checkArgumentInRange(powerRole, POWER_ROLE_NONE, POWER_ROLE_SINK,
+                "powerRole");
+        Preconditions.checkArgumentInRange(dataRole, DATA_ROLE_NONE, DATA_ROLE_DEVICE, "dataRole");
     }
 
     @Override
diff --git a/core/java/com/android/internal/logging/LogBuilder.java b/core/java/android/metrics/LogMaker.java
similarity index 73%
rename from core/java/com/android/internal/logging/LogBuilder.java
rename to core/java/android/metrics/LogMaker.java
index 7eda3da..2bf841c 100644
--- a/core/java/com/android/internal/logging/LogBuilder.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -13,76 +13,89 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.internal.logging;
+package android.metrics;
 
-import android.util.EventLog;
+import android.annotation.SystemApi;
 import android.util.Log;
 import android.util.SparseArray;
-import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 
+
 /**
  * Helper class to assemble more complex logs.
  *
  * @hide
  */
-
-public class LogBuilder {
+@SystemApi
+public class LogMaker {
     private static final String TAG = "LogBuilder";
+
+    /**
+     * Min required eventlog line length.
+     * See: android/util/cts/EventLogTest.java
+     * Size checks enforced here are intended only as sanity checks;
+     * your logs may be truncated earlier. Please log responsibly.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static final int MAX_SERIALIZED_SIZE = 4000;
+
     private SparseArray<Object> entries = new SparseArray();
 
-    public LogBuilder(int mainCategory) {
+    public LogMaker(int mainCategory) {
         setCategory(mainCategory);
     }
 
     /* Deserialize from the eventlog */
-    public LogBuilder(Object[] items) {
+    public LogMaker(Object[] items) {
       deserialize(items);
     }
 
-    public LogBuilder setCategory(int category) {
+    public LogMaker setCategory(int category) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, category);
         return this;
     }
 
-    public LogBuilder setType(int type) {
+    public LogMaker setType(int type) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE, type);
         return this;
     }
 
-    public LogBuilder setSubtype(int subtype) {
+    public LogMaker setSubtype(int subtype) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype);
         return this;
     }
 
-    public LogBuilder setTimestamp(long timestamp) {
+    public LogMaker setTimestamp(long timestamp) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp);
         return this;
     }
 
-    public LogBuilder setPackageName(String packageName) {
+    public LogMaker setPackageName(String packageName) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName);
         return this;
     }
 
-    public LogBuilder setCounterName(String name) {
+    public LogMaker setCounterName(String name) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name);
         return this;
     }
 
-    public LogBuilder setCounterBucket(int bucket) {
+    public LogMaker setCounterBucket(int bucket) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
         return this;
     }
 
-    public LogBuilder setCounterBucket(long bucket) {
+    public LogMaker setCounterBucket(long bucket) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_BUCKET, bucket);
         return this;
     }
 
-    public LogBuilder setCounterValue(int value) {
+    public LogMaker setCounterValue(int value) {
         entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_VALUE, value);
         return this;
     }
@@ -92,20 +105,28 @@
      * @param value One of Integer, Long, Float, String
      * @return
      */
-    public LogBuilder addTaggedData(int tag, Object value) {
-        if (isValidValue(value)) {
+    public LogMaker addTaggedData(int tag, Object value) {
+        if (!isValidValue(value)) {
             throw new IllegalArgumentException(
                     "Value must be loggable type - int, long, float, String");
         }
-        entries.put(tag, value);
+        if (value.toString().getBytes().length > MAX_SERIALIZED_SIZE) {
+            Log.i(TAG, "Log value too long, omitted: " + value.toString());
+        } else {
+            entries.put(tag, value);
+        }
         return this;
     }
 
     public boolean isValidValue(Object value) {
-        return !(value instanceof Integer ||
+        if (value == null) {
+            Log.i("LogBuilder", "Logging a null value.");
+            return true;
+        }
+        return value instanceof Integer ||
             value instanceof String ||
             value instanceof Long ||
-            value instanceof Float);
+            value instanceof Float;
     }
 
     public Object getTaggedData(int tag) {
@@ -198,18 +219,23 @@
             out[i * 2] = entries.keyAt(i);
             out[i * 2 + 1] = entries.valueAt(i);
         }
+        int size = out.toString().getBytes().length;
+        if (size > MAX_SERIALIZED_SIZE) {
+            Log.i(TAG, "Log line too long, did not emit: " + size + " bytes.");
+            throw new RuntimeException();
+        }
         return out;
     }
 
     public void deserialize(Object[] items) {
         int i = 0;
-        while(i < items.length) {
+        while (i < items.length) {
             Object key = items[i++];
             Object value = i < items.length ? items[i++] : null;
             if (key instanceof Integer) {
                 entries.put((Integer) key, value);
             } else {
-              Log.i(TAG, "Invalid key " + key.toString());
+                Log.i(TAG, "Invalid key " + key.toString());
             }
         }
     }
diff --git a/core/java/com/android/internal/logging/MetricsReader.java b/core/java/android/metrics/MetricsReader.java
similarity index 92%
rename from core/java/com/android/internal/logging/MetricsReader.java
rename to core/java/android/metrics/MetricsReader.java
index c4fc963..079c2c9 100644
--- a/core/java/com/android/internal/logging/MetricsReader.java
+++ b/core/java/android/metrics/MetricsReader.java
@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.internal.logging;
+package android.metrics;
+
+import android.annotation.SystemApi;
 
 import com.android.internal.logging.legacy.LegacyConversionLogger;
 import com.android.internal.logging.legacy.EventLogCollector;
@@ -22,10 +24,12 @@
 
 /**
  * Read platform logs.
+ * @hide
  */
+@SystemApi
 public class MetricsReader {
     private EventLogCollector mReader;
-    private Queue<LogBuilder> mEventQueue;
+    private Queue<LogMaker> mEventQueue;
     private long mLastEventMs;
     private long mCheckpointMs;
 
@@ -57,7 +61,7 @@
     }
 
     /* Next entry in the current log session. */
-    public LogBuilder next() {
+    public LogMaker next() {
         return mEventQueue == null ? null : mEventQueue.remove();
     }
 
diff --git a/core/java/android/net/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl
index 35601ce..1da7d67 100644
--- a/core/java/android/net/INetworkScoreCache.aidl
+++ b/core/java/android/net/INetworkScoreCache.aidl
@@ -34,7 +34,7 @@
  * the current scores for each network for debugging purposes.
  * @hide
  */
-interface INetworkScoreCache
+oneway interface INetworkScoreCache
 {
     void updateScores(in List<ScoredNetwork> networks);
 
diff --git a/core/java/android/net/NetworkBadging.java b/core/java/android/net/NetworkBadging.java
new file mode 100644
index 0000000..5cf2f96
--- /dev/null
+++ b/core/java/android/net/NetworkBadging.java
@@ -0,0 +1,152 @@
+/*
+ * 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 android.net;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.ScoredNetwork.Badging;
+import android.net.wifi.WifiManager;
+import android.view.View;
+
+/**
+ * Utility methods for working with network badging.
+ *
+ * TODO: move ScoredNetwork.Badging and related constants to this class.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkBadging {
+    private NetworkBadging() {}
+
+    /**
+     * Returns a Wi-Fi icon for a network with a given signal level and badging value.
+     *
+     * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+     *                    for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+     * @param badging  {@see ScoredNetwork#Badging}, retrieved from
+     *                 {@link ScoredNetwork#calculateBadge(int)}.
+     * @param theme The theme for the current application, may be null.
+     * @return Drawable for the given icon
+     * @throws IllegalArgumentException if {@code signalLevel} is out of range or {@code badging}
+     *                                  is an invalid value
+     */
+    @NonNull public static Drawable getWifiIcon(
+            @IntRange(from=0, to=4) int signalLevel, @Badging int badging, @Nullable Theme theme) {
+        Resources resources = Resources.getSystem();
+        if (badging == ScoredNetwork.BADGING_NONE) {
+            return resources.getDrawable(getWifiSignalResource(signalLevel), theme);
+        }
+        Drawable[] layers = new Drawable[] {
+                resources.getDrawable(getBadgedWifiSignalResource(signalLevel), theme),
+                resources.getDrawable(getWifiBadgeResource(badging), theme)
+        };
+        return new LayerDrawable(layers);
+    }
+
+    /**
+     * Returns the wifi signal resource id for the given signal level.
+     *
+     * <p>This wifi signal resource is a wifi icon to be displayed by itself when there is no badge.
+     *
+     * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+     *                    for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+     * @return the @DrawableRes for the icon
+     * @throws IllegalArgumentException for an invalid signal level
+     * @hide
+     */
+    @DrawableRes private static int getWifiSignalResource(int signalLevel) {
+        switch (signalLevel) {
+            case 0:
+                return com.android.internal.R.drawable.ic_wifi_signal_0;
+            case 1:
+                return com.android.internal.R.drawable.ic_wifi_signal_1;
+            case 2:
+                return com.android.internal.R.drawable.ic_wifi_signal_2;
+            case 3:
+                return com.android.internal.R.drawable.ic_wifi_signal_3;
+            case 4:
+                return com.android.internal.R.drawable.ic_wifi_signal_4;
+            default:
+                throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
+        }
+    }
+
+    /**
+     * Returns the badged wifi signal resource id for the given signal level.
+     *
+     * <p>This badged wifi signal resource should be displayed with the quality badge retrieved
+     * from {@link #getWifiBadgeResource(int)}. If there is no badge,
+     * {@link #getWifiBadgeResource(int)} should be used instead of this method.
+     *
+     * @param signalLevel The level returned by {@link WifiManager#calculateSignalLevel(int, int)}
+     *                    for a network. Must be between 0 and {@link WifiManager#RSSI_LEVELS}-1.
+     * @return the @DrawableRes for the icon
+     * @throws IllegalArgumentException for an invalid signal level
+     * @hide
+     */
+    @DrawableRes private static int getBadgedWifiSignalResource(int signalLevel) {
+        switch (signalLevel) {
+            case 0:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars;
+            case 1:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar;
+            case 2:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars;
+            case 3:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_3_bars;
+            case 4:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_4_bars;
+            default:
+                throw new IllegalArgumentException("Invalid signal level: " + signalLevel);
+        }
+    }
+
+    /**
+     * Returns the wifi quality badge resource id for the the given badging balue.
+     *
+     * <p>This badge should be displayed with the badge signal resource retrieved from
+     * {@link #getBadgedWifiSignalResource(int)}.
+     *
+     * @param badging {@see ScoredNetwork#Badging} from {@link ScoredNetwork#calculateBadge(int)}.
+     * @return the @DrawableRes for the icon or {@link View#NO_ID} for
+     *         {@link ScoredNetwork#BADGING_NONE}
+     * @throws IllegalArgumentException for an invalid badging value.
+     * @hide
+     */
+    @DrawableRes private static int getWifiBadgeResource(@Badging int badging) {
+        switch (badging) {
+            case ScoredNetwork.BADGING_NONE:
+                return View.NO_ID;
+            case ScoredNetwork.BADGING_SD:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_sd;
+            case ScoredNetwork.BADGING_HD:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_hd;
+            case ScoredNetwork.BADGING_4K:
+                return com.android.internal.R.drawable.ic_signal_wifi_badged_4k;
+            default:
+                throw new IllegalArgumentException("No resource found for badge: " + badging);
+        }
+    }
+}
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 1a128e0..e5f0bf0 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,10 +16,14 @@
 
 package android.net;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -65,6 +69,27 @@
     }
 
     /**
+     * Constructs a new NetworkKey for the given {@link WifiInfo}.
+     *
+     * @param wifiInfo the {@link WifiInfo} to create a {@link NetworkKey} for.
+     * @return A new {@link NetworkKey} instance or <code>null</code> if the given {@link WifiInfo}
+     *         instance doesn't represent a connected WiFi network.
+     * @hide
+     */
+    @Nullable
+    public static NetworkKey createFromWifiInfo(@Nullable WifiInfo wifiInfo) {
+        if (wifiInfo != null) {
+            final String ssid = wifiInfo.getSSID();
+            final String bssid = wifiInfo.getBSSID();
+            if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
+                    && !TextUtils.isEmpty(bssid)) {
+                return new NetworkKey(new WifiKey(ssid, bssid));
+            }
+        }
+        return null;
+    }
+
+    /**
      * Construct a new {@link NetworkKey} for a Wi-Fi network.
      * @param wifiKey the {@link WifiKey} identifying this Wi-Fi network.
      */
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 83d17ba..bd32314 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -805,7 +805,7 @@
 
                 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
                     throw new FormatException("expected MB flag");
-                } else if (mb && records.size() != 0 && !ignoreMbMe) {
+                } else if (mb && (records.size() != 0 || inChunk) && !ignoreMbMe) {
                     throw new FormatException("unexpected MB flag");
                 } else if (inChunk && il) {
                     throw new FormatException("unexpected IL flag in non-leading chunk");
@@ -839,6 +839,9 @@
 
                 if (cf && !inChunk) {
                     // first chunk
+                    if (typeLength == 0) {
+                        throw new FormatException("expected non-zero type length in first chunk");
+                    }
                     chunks.clear();
                     chunkTnf = tnf;
                 }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 20de370..e4cdbce 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -44,7 +44,6 @@
         // without significantly disrupting other activity launch work.
         Thread eglInitThread = new Thread(
                 () -> {
-                    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
                     EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
                 },
                 "EGL Init");
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 1c2588a..12f5396 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -43,6 +43,7 @@
     void setUserEnabled(int userHandle);
     void evictCredentialEncryptionKey(int userHandle);
     boolean removeUser(int userHandle);
+    boolean removeUserEvenWhenDisallowed(int userHandle);
     void setUserName(int userHandle, String name);
     void setUserIcon(int userHandle, in Bitmap icon);
     ParcelFileDescriptor getUserIcon(int userHandle);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d6d5cb6..1db685a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -26,6 +26,7 @@
 import android.util.SizeF;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 
 import libcore.util.SneakyThrow;
 
@@ -891,6 +892,21 @@
         }
     }
 
+    public final void writeSparseIntArray(SparseIntArray val) {
+        if (val == null) {
+            writeInt(-1);
+            return;
+        }
+        int N = val.size();
+        writeInt(N);
+        int i=0;
+        while (i < N) {
+            writeInt(val.keyAt(i));
+            writeInt(val.valueAt(i));
+            i++;
+        }
+    }
+
     public final void writeBooleanArray(boolean[] val) {
         if (val != null) {
             int N = val.length;
@@ -2154,6 +2170,20 @@
     }
 
     /**
+     * Read and return a new SparseIntArray object from the parcel at the current
+     * dataPosition(). Returns null if the previously written array object was null.
+     */
+    public final SparseIntArray readSparseIntArray() {
+        int N = readInt();
+        if (N < 0) {
+            return null;
+        }
+        SparseIntArray sa = new SparseIntArray(N);
+        readSparseIntArrayInternal(sa, N);
+        return sa;
+    }
+
+    /**
      * Read and return a new ArrayList containing a particular object type from
      * the parcel that was written with {@link #writeTypedList} at the
      * current dataPosition().  Returns null if the
@@ -2922,6 +2952,15 @@
         }
     }
 
+    private void readSparseIntArrayInternal(SparseIntArray outVal, int N) {
+        while (N > 0) {
+            int key = readInt();
+            int value = readInt();
+            outVal.append(key, value);
+            N--;
+        }
+    }
+
     /**
      * @hide For testing
      */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index efacb20..c5c380c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2082,6 +2082,22 @@
     }
 
     /**
+     * Similar to {@link #removeUser(int)} except bypassing the checking of
+     * {@link UserManager#DISALLOW_REMOVE_USER}
+     * or {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}.
+     *
+     * @see {@link #removeUser(int)}
+     * @hide
+     */
+    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+        try {
+            return mService.removeUserEvenWhenDisallowed(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Updates the user's name.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 8303bca..089eaec 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -145,6 +145,8 @@
 
     private List<Preference> mDependents;
 
+    private PreferenceGroup mParentGroup;
+
     private boolean mBaseMethodCalled;
 
     /**
@@ -1238,6 +1240,16 @@
         registerDependency();
     }
 
+    /**
+     * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set null to remove
+     * the current parent.
+     *
+     * @param parentGroup Parent preference group of this Preference or null if none.
+     */
+    void assignParent(@Nullable PreferenceGroup parentGroup) {
+        mParentGroup = parentGroup;
+    }
+
     private void registerDependency() {
 
         if (TextUtils.isEmpty(mDependencyKey)) return;
@@ -1401,6 +1413,17 @@
     }
 
     /**
+     * Returns the {@link PreferenceGroup} which is this Preference assigned to or null if this
+     * preference is not assigned to any group or is a root Preference.
+     *
+     * @return The parent PreferenceGroup or null if not attached to any.
+     */
+    @Nullable
+    public PreferenceGroup getParent() {
+        return mParentGroup;
+    }
+
+    /**
      * Called when this Preference is being removed from the hierarchy. You
      * should remove any references to this Preference that you know about. Make
      * sure to call through to the superclass implementation.
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index f17506b..f135b26 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -29,14 +29,14 @@
  * A container for multiple
  * {@link Preference} objects. It is a base class for  Preference objects that are
  * parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}.
- * 
+ *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>For information about building a settings UI with Preferences,
  * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
  * guide.</p>
  * </div>
- * 
+ *
  * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
  */
 public abstract class PreferenceGroup extends Preference implements GenericInflater.Parent<Preference> {
@@ -80,7 +80,7 @@
      * <p>
      * If this is called after preferences are added, they will not be
      * re-ordered in the order they were added, hence call this method early on.
-     * 
+     *
      * @param orderingAsAdded Whether to order according to the order added.
      * @see Preference#setOrder(int)
      */
@@ -90,7 +90,7 @@
 
     /**
      * Whether this group is ordering preferences in the order they are added.
-     * 
+     *
      * @return Whether this group orders based on the order the children are added.
      * @see #setOrderingAsAdded(boolean)
      */
@@ -115,7 +115,7 @@
 
     /**
      * Returns the {@link Preference} at a particular index.
-     * 
+     *
      * @param index The index of the {@link Preference} to retrieve.
      * @return The {@link Preference}.
      */
@@ -126,7 +126,7 @@
     /**
      * Adds a {@link Preference} at the correct position based on the
      * preference's order.
-     * 
+     *
      * @param preference The preference to add.
      * @return Whether the preference is now in this group.
      */
@@ -135,7 +135,7 @@
             // Exists
             return true;
         }
-        
+
         if (preference.getOrder() == Preference.DEFAULT_ORDER) {
             if (mOrderingAsAdded) {
                 preference.setOrder(mCurrentPreferenceOrder++);
@@ -161,11 +161,12 @@
         }
 
         preference.onAttachedToHierarchy(getPreferenceManager());
-        
+        preference.assignParent(this);
+
         if (mAttachedToActivity) {
             preference.onAttachedToActivity();
         }
-        
+
         notifyHierarchyChanged();
 
         return true;
@@ -173,7 +174,7 @@
 
     /**
      * Removes a {@link Preference} from this group.
-     * 
+     *
      * @param preference The preference to remove.
      * @return Whether the preference was found and removed.
      */
@@ -186,10 +187,13 @@
     private boolean removePreferenceInt(Preference preference) {
         synchronized(this) {
             preference.onPrepareForRemoval();
+            if (preference.getParent() == this) {
+                preference.assignParent(null);
+            }
             return mPreferenceList.remove(preference);
         }
     }
-    
+
     /**
      * Removes all {@link Preference Preferences} from this group.
      */
@@ -202,10 +206,10 @@
         }
         notifyHierarchyChanged();
     }
-    
+
     /**
      * Prepares a {@link Preference} to be added to the group.
-     * 
+     *
      * @param preference The preference to add.
      * @return Whether to allow adding the preference (true), or not (false).
      */
@@ -223,7 +227,7 @@
      * <p>
      * This will recursively search for the preference into children that are
      * also {@link PreferenceGroup PreferenceGroups}.
-     * 
+     *
      * @param key The key of the preference to retrieve.
      * @return The {@link Preference} with the key, or null.
      */
@@ -239,7 +243,7 @@
             if (curKey != null && curKey.equals(key)) {
                 return preference;
             }
-            
+
             if (preference instanceof PreferenceGroup) {
                 final Preference returnedPreference = ((PreferenceGroup)preference)
                         .findPreference(key);
@@ -255,14 +259,14 @@
     /**
      * Whether this preference group should be shown on the same screen as its
      * contained preferences.
-     * 
+     *
      * @return True if the contained preferences should be shown on the same
      *         screen as this preference.
      */
     protected boolean isOnSameScreenAsChildren() {
         return true;
     }
-    
+
     @Override
     protected void onAttachedToActivity() {
         super.onAttachedToActivity();
@@ -270,7 +274,7 @@
         // Mark as attached so if a preference is later added to this group, we
         // can tell it we are already attached
         mAttachedToActivity = true;
-        
+
         // Dispatch to all contained preferences
         final int preferenceCount = getPreferenceCount();
         for (int i = 0; i < preferenceCount; i++) {
@@ -281,7 +285,7 @@
     @Override
     protected void onPrepareForRemoval() {
         super.onPrepareForRemoval();
-        
+
         // We won't be attached to the activity anymore
         mAttachedToActivity = false;
     }
@@ -297,7 +301,7 @@
             getPreference(i).onParentChanged(this, disableDependents);
         }
     }
-    
+
     void sortPreferences() {
         synchronized (this) {
             Collections.sort(mPreferenceList);
@@ -314,7 +318,7 @@
             getPreference(i).dispatchSaveInstanceState(container);
         }
     }
-    
+
     @Override
     protected void dispatchRestoreInstanceState(Bundle container) {
         super.dispatchRestoreInstanceState(container);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 6170eb4..f5e558a 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -678,8 +678,18 @@
         throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
     }
 
+    /**
+     * WARNING: Sub-classes should not override this method. This method is non-final
+     * solely for the purposes of backwards compatibility.
+     *
+     * @see #queryChildDocuments(String, String[], Bundle),
+     *      {@link #queryDocument(String, String[])},
+     *      {@link #queryRecentDocuments(String, String[])},
+     *      {@link #queryRoots(String[])}, and
+     *      {@link #querySearchDocuments(String, String, String[])}.
+     */
     @Override
-    public final Cursor query(Uri uri, String[] projection, String selection,
+    public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
         // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
         // transport method. We override that, and don't ever delegate to this metohd.
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
new file mode 100644
index 0000000..90e710f
--- /dev/null
+++ b/core/java/android/provider/FontsContract.java
@@ -0,0 +1,197 @@
+/*
+ * 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 android.provider;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.graphics.fonts.FontRequest;
+import android.graphics.fonts.FontResult;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Utility class to deal with Font ContentProviders.
+ */
+public class FontsContract {
+    private static final String TAG = "FontsContract";
+
+    /**
+     * Defines the constants used in a response from a Font Provider. The cursor returned from the
+     * query should have the ID column populated with the content uri ID for the resulting font.
+     * This should point to a real file or shared memory, as the client will mmap the given file
+     * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the
+     * client application.
+     */
+    public static final class Columns implements BaseColumns {
+        /**
+         * Constant used to request data from a font provider. The cursor returned from the query
+         * should have this column populated with an int for the ttc index for the resulting font.
+         */
+        public static final String TTC_INDEX = "font_ttc_index";
+        /**
+         * Constant used to request data from a font provider. The cursor returned from the query
+         * may populate this column with the font variation settings String information for the
+         * font.
+         */
+        public static final String VARIATION_SETTINGS = "font_variation_settings";
+        /**
+         * Constant used to request data from a font provider. The cursor returned from the query
+         * should have this column populated with the int style for the resulting font. This should
+         * be one of {@link android.graphics.Typeface#NORMAL},
+         * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC} or
+         * {@link android.graphics.Typeface#BOLD_ITALIC}
+         */
+        public static final String STYLE = "font_style";
+    }
+
+    /**
+     * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle
+     * returned to the ResultReceiver in getFont.
+     * @hide
+     */
+    public static final String PARCEL_FONT_RESULTS = "font_results";
+
+    /** @hide */
+    public static final int RESULT_CODE_OK = 0;
+    /** @hide */
+    public static final int RESULT_CODE_FONT_NOT_FOUND = 1;
+    /** @hide */
+    public static final int RESULT_CODE_PROVIDER_NOT_FOUND = 2;
+
+    private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;
+
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private Handler mHandler;
+    @GuardedBy("mLock")
+    private HandlerThread mThread;
+
+    /** @hide */
+    public FontsContract() {
+        // TODO: investigate if the system context is the best option here. ApplicationContext or
+        // the one passed by developer?
+        // TODO: Looks like ActivityThread.currentActivityThread() can return null. Check when it
+        // returns null and check if we need to handle null case.
+        mContext = ActivityThread.currentActivityThread().getSystemContext();
+        mPackageManager = mContext.getPackageManager();
+    }
+
+    // We use a background thread to post the content resolving work for all requests on. This
+    // thread should be quit/stopped after all requests are done.
+    private final Runnable mReplaceDispatcherThreadRunnable = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                if (mThread != null) {
+                    mThread.quitSafely();
+                    mThread = null;
+                    mHandler = null;
+                }
+            }
+        }
+    };
+
+    /**
+     * @hide
+     */
+    public void getFont(FontRequest request, ResultReceiver receiver) {
+        synchronized (mLock) {
+            if (mHandler == null) {
+                mThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND);
+                mThread.start();
+                mHandler = new Handler(mThread.getLooper());
+            }
+            mHandler.post(() -> {
+                String providerAuthority = request.getProviderAuthority();
+                // TODO: Implement cert checking for non-system apps
+                ProviderInfo providerInfo = mPackageManager.resolveContentProvider(
+                        providerAuthority, PackageManager.MATCH_SYSTEM_ONLY);
+                if (providerInfo == null) {
+                    receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
+                    return;
+                }
+                Bundle result = getFontFromProvider(request, receiver, providerInfo);
+                if (result == null) {
+                    receiver.send(RESULT_CODE_FONT_NOT_FOUND, null);
+                    return;
+                }
+                receiver.send(RESULT_CODE_OK, result);
+            });
+            mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
+            mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
+        }
+    }
+
+    private Bundle getFontFromProvider(FontRequest request, ResultReceiver receiver,
+            ProviderInfo providerInfo) {
+        ArrayList<FontResult> result = null;
+        Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                .authority(providerInfo.authority)
+                .build();
+        try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
+                        Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE },
+                "query = ?", new String[] { request.getQuery() }, null);) {
+            // TODO: Should we restrict the amount of fonts that can be returned?
+            // TODO: Write documentation explaining that all results should be from the same family.
+            if (cursor != null && cursor.getCount() > 0) {
+                result = new ArrayList<>();
+                final int idColumnIndex = cursor.getColumnIndex(Columns._ID);
+                final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX);
+                final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS);
+                final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
+                while (cursor.moveToNext()) {
+                    long id = cursor.getLong(idColumnIndex);
+                    Uri fileUri = ContentUris.withAppendedId(uri, id);
+                    try {
+                        ParcelFileDescriptor pfd =
+                                mContext.getContentResolver().openFileDescriptor(fileUri, "r");
+                        final int ttcIndex = cursor.getInt(ttcIndexColumnIndex);
+                        final String variationSettings = cursor.getString(vsColumnIndex);
+                        final int style = cursor.getInt(styleColumnIndex);
+                        result.add(new FontResult(pfd, ttcIndex, variationSettings, style));
+                    } catch (FileNotFoundException e) {
+                        Log.e(TAG, "FileNotFoundException raised when interacting with content "
+                                + "provider " + providerInfo.authority, e);
+                    }
+                }
+            }
+        }
+        if (result != null && !result.isEmpty()) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
+            return bundle;
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 71b9482..fd8b2ca 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -30,6 +30,7 @@
 import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.app.Application;
+import android.app.NotificationChannel;
 import android.app.SearchManager;
 import android.app.WallpaperManager;
 import android.content.ComponentName;
@@ -58,6 +59,7 @@
 import android.os.LocaleList;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.speech.tts.TextToSpeech;
@@ -162,13 +164,13 @@
      * In some cases, a matching Activity may not exist, so ensure you
      * safeguard against this.
      * <p>
-     * Input: {@link ConnectivityManager.EXTRA_TETHER_TYPE} should be included to specify which type
-     * of tethering should be checked. {@link ConnectivityManager.EXTRA_PROVISION_CALLBACK} should
+     * Input: {@link ConnectivityManager#EXTRA_TETHER_TYPE} should be included to specify which type
+     * of tethering should be checked. {@link ConnectivityManager#EXTRA_PROVISION_CALLBACK} should
      * contain a {@link ResultReceiver} which will be called back with a tether result code.
      * <p>
      * Output: The result of the provisioning check.
-     * {@link ConnectivityManager.TETHER_ERROR_NO_ERROR} if successful,
-     * {@link ConnectivityManager.TETHER_ERROR_PROVISION_FAILED} for failure.
+     * {@link ConnectivityManager#TETHER_ERROR_NO_ERROR} if successful,
+     * {@link ConnectivityManager#TETHER_ERROR_PROVISION_FAILED} for failure.
      *
      * @hide
      */
@@ -1274,6 +1276,7 @@
     /**
      * Activity Action: Show notification settings for a single app.
      *
+     * Input: Optionally, {@link #EXTRA_CHANNEL_ID}, to highlight that channel.
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1281,6 +1284,35 @@
             = "android.settings.APP_NOTIFICATION_SETTINGS";
 
     /**
+     * Activity Action: Show notification settings for a single {@link NotificationChannel}.
+     * <p>
+     * Must be called from an activity.
+     * <p>
+     *     Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel to display.
+     *     Input: {@link #EXTRA_CHANNEL_ID}, the id of the channel to display.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHANNEL_NOTIFICATION_SETTINGS
+            = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
+
+    /**
+     * Activity Extra: The package owner of the notification channel settings to display.
+     * <p>
+     * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
+     */
+    public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
+
+    /**
+     * Activity Extra: The {@link NotificationChannel#getId()} of the notification channel settings
+     * to display.
+     * <p>
+     * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
+     */
+    public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
+
+    /**
      * Activity Action: Show notification redaction settings.
      *
      * @hide
@@ -1290,7 +1322,6 @@
             = "android.settings.ACTION_APP_NOTIFICATION_REDACTION";
 
     /** @hide */ public static final String EXTRA_APP_UID = "app_uid";
-    /** @hide */ public static final String EXTRA_APP_PACKAGE = "app_package";
 
     /**
      * Activity Action: Show a dialog with disabled by policy message.
@@ -1334,6 +1365,19 @@
             = "android.settings.VR_LISTENER_SETTINGS";
 
     /**
+     * Activity Action: Show Picture-in-picture settings.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PICTURE_IN_PICTURE_SETTINGS
+            = "android.settings.PICTURE_IN_PICTURE_SETTINGS";
+
+    /**
      * Activity Action: Show Storage Manager settings.
      * <p>
      * Input: Nothing.
@@ -8742,6 +8786,26 @@
                 BLUETOOTH_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_";
 
         /**
+         * Activity manager specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "enforce_bg_check=true,max_cached_processes=24"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * enforce_bg_check                     (boolean)
+         * max_cached_processes                 (int)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * @see com.android.server.am.ActivityManagerConstants
+         */
+        public static final String ACTIVITY_MANAGER_CONSTANTS = "activity_manager_constants";
+
+        /**
          * Device Idle (Doze) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -8807,6 +8871,25 @@
         public static final String APP_IDLE_CONSTANTS = "app_idle_constants";
 
         /**
+         * Power manager specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "no_cached_wake_locks=1"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * no_cached_wake_locks                 (boolean)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * @see com.android.server.power.PowerManagerConstants
+         */
+        public static final String POWER_MANAGER_CONSTANTS = "power_manager_constants";
+
+        /**
          * Alarm manager specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -9355,7 +9438,7 @@
         /**
          * WFC mode on roaming network.
          * <p>
-         * Type: int - see {@link WFC_IMS_MODE} for values
+         * Type: int - see {@link #WFC_IMS_MODE} for values
          *
          * @hide
          */
@@ -9501,10 +9584,11 @@
             DOCK_SOUNDS_ENABLED,
             CHARGING_SOUNDS_ENABLED,
             USB_MASS_STORAGE_ENABLED,
+            NETWORK_RECOMMENDATIONS_ENABLED,
+            CURATE_SAVED_OPEN_NETWORKS,
+            WIFI_WAKEUP_ENABLED,
             WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-            WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
             WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
-            WIFI_NUM_OPEN_NETWORKS_KEPT,
             EMERGENCY_TONE,
             CALL_AUTO_RETRY,
             DOCK_AUDIO_MEDIA_ENABLED,
@@ -10015,6 +10099,17 @@
             EPHEMERAL_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
         }
 
+        /**
+         * Whether to show the high temperature warning notification.
+         * @hide
+         */
+        public static final String SHOW_TEMPERATURE_WARNING = "show_temperature_warning";
+
+        /**
+         * Temperature at which the high temperature warning notification should be shown.
+         * @hide
+         */
+        public static final String WARNING_TEMPERATURE = "warning_temperature";
     }
 
     /**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index a4b6807..e48a0d0 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -319,7 +319,7 @@
          *
          * @hide
          */
-        public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicmail";
+        public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
 
         /**
          * A convenience method to build voicemail URI specific to a source package by appending
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 22f1bed..5461e6b 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -83,6 +83,12 @@
     public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
     public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
     public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
+    public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
+    public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
+    public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
+    public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
+    public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
+    public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
 
     public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
     public static final int KM_TAG_NONCE = KM_BYTES | 1001;
@@ -204,6 +210,7 @@
     public static final int KM_ERROR_INVALID_MAC_LENGTH = -57;
     public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58;
     public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59;
+    public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
     public static final int KM_ERROR_UNIMPLEMENTED = -100;
     public static final int KM_ERROR_VERSION_MISMATCH = -101;
     public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
@@ -249,6 +256,7 @@
                 "Caller-provided IV not permitted");
         sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH,
                 "Invalid MAC or authentication tag length");
+        sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids");
         sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
         sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
     }
diff --git a/core/java/android/service/autofill/AutoFillService.java b/core/java/android/service/autofill/AutoFillService.java
index 805d8e5..b5cb8f8 100644
--- a/core/java/android/service/autofill/AutoFillService.java
+++ b/core/java/android/service/autofill/AutoFillService.java
@@ -15,8 +15,6 @@
  */
 package android.service.autofill;
 
-import static android.service.voice.VoiceInteractionSession.KEY_FLAGS;
-import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
 import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
 import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
 
@@ -30,17 +28,21 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
 import android.util.Log;
 import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
 
 import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.SomeArgs;
 
-// TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
-// life-cycle (and how state could be maintained on server-side) is well documented.
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+//TODO(b/33197203): improve javadoc (of both class and methods); in particular, make sure the
+//life-cycle (and how state could be maintained on server-side) is well documented.
 
 /**
  * Top-level service of the current auto-fill service for a given user.
@@ -49,8 +51,11 @@
  */
 public abstract class AutoFillService extends Service {
 
-    static final String TAG = "AutoFillService";
-    static final boolean DEBUG = true; // TODO: set to false once stable
+    private static final String TAG = "AutoFillService";
+    static final boolean DEBUG = true; // TODO(b/33197203): set to false once stable
+
+    // TODO(b/33197203): check for device's memory size instead of DEBUG?
+    static final boolean DEBUG_PENDING_CALLBACKS = DEBUG;
 
     /**
      * The {@link Intent} that must be declared as handled by the service.
@@ -77,6 +82,7 @@
     // Internal bundle keys.
     /** @hide */ public static final String KEY_CALLBACK = "callback";
     /** @hide */ public static final String KEY_SAVABLE_IDS = "savable_ids";
+    /** @hide */ public static final String EXTRA_CRYPTO_OBJECT_ID = "crypto_object_id";
 
     // Prefix for public bundle keys.
     private static final String KEY_PREFIX = "android.service.autofill.extra.";
@@ -97,10 +103,32 @@
      */
     public static final String EXTRA_DATASET_EXTRAS = KEY_PREFIX + "DATASET_EXTRAS";
 
+    /**
+     * Used to indicate the user selected an action that requires authentication.
+     */
+    public static final int FLAG_AUTHENTICATION_REQUESTED = 1 << 0;
+
+    /**
+     * Used to indicate the user authentication succeeded.
+     */
+    public static final int FLAG_AUTHENTICATION_SUCCESS = 1 << 1;
+
+    /**
+     * Used to indicate the user authentication failed.
+     */
+    public static final int FLAG_AUTHENTICATION_ERROR = 1 << 2;
+
+    /**
+     * Used when the service requested Fingerprint authentication but such option is not available.
+     */
+    public static final int FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE  = 1 << 3;
+
     // Handler messages.
     private static final int MSG_CONNECT = 1;
-    private static final int MSG_AUTO_FILL_ACTIVITY = 2;
-    private static final int MSG_DISCONNECT = 3;
+    private static final int MSG_DISCONNECT = 2;
+    private static final int MSG_AUTO_FILL_ACTIVITY = 3;
+    private static final int MSG_AUTHENTICATE_FILL_RESPONSE = 4;
+    private static final int MSG_AUTHENTICATE_DATASET = 5;
 
     private final IAutoFillService mInterface = new IAutoFillService.Stub() {
 
@@ -113,6 +141,22 @@
         }
 
         @Override
+        public void authenticateFillResponse(Bundle extras, int flags) {
+            final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_FILL_RESPONSE);
+            msg.arg1 = flags;
+            msg.obj = extras;
+            mHandlerCaller.sendMessage(msg);
+        }
+
+        @Override
+        public void authenticateDataset(Bundle extras, int flags) {
+            final Message msg = mHandlerCaller.obtainMessage(MSG_AUTHENTICATE_DATASET);
+            msg.arg1 = flags;
+            msg.obj = extras;
+            mHandlerCaller.sendMessage(msg);
+        }
+
+        @Override
         public void onConnected() {
             mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_CONNECT));
         }
@@ -139,6 +183,16 @@
                     final IAutoFillServerCallback callback = (IAutoFillServerCallback) args.arg3;
                     requestAutoFill(callback, structure, extras, flags);
                     break;
+                } case MSG_AUTHENTICATE_FILL_RESPONSE: {
+                    final int flags = msg.arg1;
+                    final Bundle extras = (Bundle) msg.obj;
+                    onFillResponseAuthenticationRequest(extras, flags);
+                    break;
+                } case MSG_AUTHENTICATE_DATASET: {
+                    final int flags = msg.arg1;
+                    final Bundle extras = (Bundle) msg.obj;
+                    onDatasetAuthenticationRequest(extras, flags);
+                    break;
                 } case MSG_DISCONNECT: {
                     onDisconnected();
                     break;
@@ -151,6 +205,10 @@
 
     private HandlerCaller mHandlerCaller;
 
+    // User for debugging purposes
+    private final List<CallbackHelper.Dumpable> mPendingCallbacks =
+            DEBUG_PENDING_CALLBACKS ? new ArrayList<>() : null;
+
     /**
      * {@inheritDoc}
      *
@@ -215,16 +273,84 @@
     public abstract void onSaveRequest(AssistStructure structure,
             Bundle data, CancellationSignal cancellationSignal, SaveCallback callback);
 
+    /**
+     * Called as result of the user action for a {@link FillResponse} that required authentication.
+     *
+     * <p>When the {@link FillResponse} required authentication through
+     * {@link android.view.autofill.FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, this
+     * call indicates the user is requesting the service to authenticate him/her (and {@code flags}
+     * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the
+     * {@link Bundle} passed to that method.
+     *
+     * <p>When the {@link FillResponse} required authentication through
+     * {@link android.view.autofill.FillResponse.Builder#requiresFingerprintAuthentication(
+     * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)},
+     * {@code flags} this call contains the result of the fingerprint authentication (such as
+     * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and
+     * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the
+     * {@link Bundle} passed to that method.
+     */
+    public void onFillResponseAuthenticationRequest(@SuppressWarnings("unused") Bundle extras,
+            int flags) {
+        if (DEBUG) Log.d(TAG, "onFillResponseAuthenticationRequest(): flags=" + flags);
+    }
+
+    /**
+     * Called as result of the user action for a {@link Dataset} that required authentication.
+     *
+     * <p>When the {@link Dataset} required authentication through
+     * {@link android.view.autofill.Dataset.Builder#requiresCustomAuthentication(Bundle, int)}, this
+     * call indicates the user is requesting the service to authenticate him/her (and {@code flags}
+     * contains {@link #FLAG_AUTHENTICATION_REQUESTED}), and {@code extras} contains the
+     * {@link Bundle} passed to that method.
+     *
+     * <p>When the {@link Dataset} required authentication through
+     * {@link android.view.autofill.Dataset.Builder#requiresFingerprintAuthentication(
+     * android.hardware.fingerprint.FingerprintManager.CryptoObject, Bundle, int)},
+     * {@code flags} this call contains the result of the fingerprint authentication (such as
+     * {@link #FLAG_AUTHENTICATION_SUCCESS}, {@link #FLAG_AUTHENTICATION_ERROR}, and
+     * {@link #FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}) and {@code extras} contains the
+     * {@link Bundle} passed to that method.
+     */
+    public void onDatasetAuthenticationRequest(@SuppressWarnings("unused") Bundle extras,
+            int flags) {
+        if (DEBUG) Log.d(TAG, "onDatasetAuthenticationRequest(): flags=" + flags);
+    }
+
+    // TODO(b/33197203): make it final and create another method classes could extend so it's
+    // guaranteed to dump the pending callbacks?
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mPendingCallbacks != null) {
+            pw.print("Number of pending callbacks: "); pw.println(mPendingCallbacks.size());
+            final String prefix = "  ";
+            for (int i = 0; i < mPendingCallbacks.size(); i++) {
+                final CallbackHelper.Dumpable cb = mPendingCallbacks.get(i);
+                pw.print('#'); pw.print(i + 1); pw.println(':');
+                cb.dump(prefix, pw);
+            }
+            pw.println();
+        } else {
+            pw.println("Dumping disabled");
+        }
+    }
+
     private void requestAutoFill(IAutoFillServerCallback callback, AssistStructure structure,
             Bundle data, int flags) {
         switch (flags) {
             case AUTO_FILL_FLAG_TYPE_FILL:
                 final FillCallback fillCallback = new FillCallback(callback);
+                if (DEBUG_PENDING_CALLBACKS) {
+                    addPendingCallback(fillCallback);
+                }
                 // TODO(b/33197203): hook up the cancelationSignal
                 onFillRequest(structure, data, new CancellationSignal(), fillCallback);
                 break;
             case AUTO_FILL_FLAG_TYPE_SAVE:
                 final SaveCallback saveCallback = new SaveCallback(callback);
+                if (DEBUG_PENDING_CALLBACKS) {
+                    addPendingCallback(saveCallback);
+                }
                 // TODO(b/33197203): hook up the cancelationSignal
                 onSaveRequest(structure, data, new CancellationSignal(), saveCallback);
                 break;
@@ -233,6 +359,22 @@
         }
     }
 
+    private void addPendingCallback(CallbackHelper.Dumpable callback) {
+        if (mPendingCallbacks == null) {
+            // Shouldn't happend since call is controlled by DEBUG_PENDING_CALLBACKS guard.
+            Log.wtf(TAG, "addPendingCallback(): mPendingCallbacks not set");
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "Adding pending callback: " + callback);
+
+        callback.setFinalizer(() -> {
+            if (DEBUG) Log.d(TAG, "Removing pending callback: " + callback);
+            mPendingCallbacks.remove(callback);
+        });
+        mPendingCallbacks.add(callback);
+    }
+
     /**
      * Called when the Android system disconnects from the service.
      *
diff --git a/core/java/android/service/autofill/AutoFillServiceInfo.java b/core/java/android/service/autofill/AutoFillServiceInfo.java
index ab86580..fd957f1 100644
--- a/core/java/android/service/autofill/AutoFillServiceInfo.java
+++ b/core/java/android/service/autofill/AutoFillServiceInfo.java
@@ -25,15 +25,26 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.RemoteException;
+import android.util.AndroidException;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import com.android.internal.R;
+
 import java.io.IOException;
 
-/** @hide */
+// TODO(b/33197203 , b/33802548): add CTS tests
+/**
+ * {@link ServiceInfo} and meta-data about an {@link AutoFillService}.
+ *
+ * <p>Upon construction, if {@link #getParseError()} is {@code null}, then the service is configured
+ * correctly. Otherwise, {@link #getParseError()} indicates the parsing error.
+ *
+ * @hide
+ */
 public final class AutoFillServiceInfo {
     static final String TAG = "AutoFillServiceInfo";
 
@@ -53,67 +64,87 @@
     }
 
     @Nullable
-    private String mParseError;
+    private final String mParseError;
 
+    private final ServiceInfo mServiceInfo;
     @Nullable
-    private ServiceInfo mServiceInfo;
-    @Nullable
-    private String mSettingsActivity;
+    private final String mSettingsActivity;
 
     public AutoFillServiceInfo(PackageManager pm, ComponentName comp, int userHandle)
             throws PackageManager.NameNotFoundException {
         this(pm, getServiceInfoOrThrow(comp, userHandle));
     }
 
-    public AutoFillServiceInfo(PackageManager pm, ServiceInfo si)
-            throws PackageManager.NameNotFoundException{
-        if (si == null) {
-            mParseError = "Service not available";
-            return;
-        }
-        if (!Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) {
-            mParseError = "Service does not require permission "
-                    + Manifest.permission.BIND_AUTO_FILL;
-            return;
-        }
-
-        XmlResourceParser parser = null;
-        try {
-            parser = si.loadXmlMetaData(pm, AutoFillService.SERVICE_META_DATA);
-            if (parser == null) {
-                mParseError = "No " + AutoFillService.SERVICE_META_DATA
-                        + " meta-data for " + si.packageName;
-                return;
-            }
-
-            Resources res = pm.getResourcesForApplication(si.applicationInfo);
-
-            AttributeSet attrs = Xml.asAttributeSet(parser);
-
-            int type;
-            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
-                    && type != XmlPullParser.START_TAG) {
-            }
-
-            String nodeName = parser.getName();
-            if (!"autofill-service".equals(nodeName)) {
-                mParseError = "Meta-data does not start with autofill-service tag";
-                return;
-            }
-
-            TypedArray array = res.obtainAttributes(attrs,
-                    com.android.internal.R.styleable.AutoFillService);
-            mSettingsActivity = array.getString(
-                    com.android.internal.R.styleable.AutoFillService_settingsActivity);
-            array.recycle();
-        } catch (XmlPullParserException | IOException | PackageManager.NameNotFoundException e) {
-            mParseError = "Error parsing auto fill service meta-data: " + e;
-            Log.w(TAG, "error parsing auto fill service meta-data", e);
-            return;
-        } finally {
-            if (parser != null) parser.close();
-        }
+    public AutoFillServiceInfo(PackageManager pm, ServiceInfo si) {
         mServiceInfo = si;
+        TypedArray metaDataArray;
+        try {
+            metaDataArray = getMetaDataArray(pm, si);
+        } catch (AndroidException e) {
+            mParseError = e.getMessage();
+            mSettingsActivity = null;
+            Log.w(TAG, mParseError, e);
+            return;
+        }
+
+        mParseError = null;
+        if (metaDataArray != null) {
+            mSettingsActivity =
+                    metaDataArray.getString(R.styleable.AutoFillService_settingsActivity);
+            metaDataArray.recycle();
+        } else {
+            mSettingsActivity = null;
+        }
+    }
+
+    /**
+     * Gets the meta-data as a TypedArray, or null if not provided, or throws if invalid.
+     */
+    @Nullable
+    private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si)
+            throws AndroidException {
+        // Check for permissions.
+        if (!Manifest.permission.BIND_AUTO_FILL.equals(si.permission)) {
+            throw new AndroidException(
+                "Service does not require permission " + Manifest.permission.BIND_AUTO_FILL);
+        }
+
+        // Get the AutoFill metadata, if declared.
+        XmlResourceParser parser = si.loadXmlMetaData(pm, AutoFillService.SERVICE_META_DATA);
+        if (parser == null) {
+            return null;
+        }
+
+        // Parse service info and get the <autofill-service> tag as an AttributeSet.
+        AttributeSet attrs;
+        try {
+            // Move the XML parser to the first tag.
+            try {
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && type != XmlPullParser.START_TAG) {
+                }
+            } catch (XmlPullParserException | IOException e) {
+                throw new AndroidException("Error parsing auto fill service meta-data: " + e, e);
+            }
+
+            if (!"autofill-service".equals(parser.getName())) {
+                throw new AndroidException("Meta-data does not start with autofill-service tag");
+            }
+            attrs = Xml.asAttributeSet(parser);
+
+            // Get resources required to read the AttributeSet.
+            Resources res;
+            try {
+                res = pm.getResourcesForApplication(si.applicationInfo);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new AndroidException("Error getting application resources: " + e, e);
+            }
+
+            return res.obtainAttributes(attrs, R.styleable.AutoFillService);
+        } finally {
+            parser.close();
+        }
     }
 
     @Nullable
@@ -121,7 +152,6 @@
         return mParseError;
     }
 
-    @Nullable
     public ServiceInfo getServiceInfo() {
         return mServiceInfo;
     }
diff --git a/core/java/android/service/autofill/CallbackHelper.java b/core/java/android/service/autofill/CallbackHelper.java
new file mode 100644
index 0000000..ded8f97
--- /dev/null
+++ b/core/java/android/service/autofill/CallbackHelper.java
@@ -0,0 +1,30 @@
+/*
+ * 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 android.service.autofill;
+
+import java.io.PrintWriter;
+
+final class CallbackHelper {
+
+    static interface Dumpable {
+        void dump(String prefix, PrintWriter pw);
+        void setFinalizer(Finalizer f);
+    }
+
+    static interface Finalizer {
+        void gone();
+    }
+}
diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java
index 5a9a9f6..7cab7ae 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -17,28 +17,49 @@
 package android.service.autofill;
 
 import static android.service.autofill.AutoFillService.DEBUG;
+import static android.util.DebugUtils.flagsToString;
 
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.RemoteException;
+import android.service.autofill.CallbackHelper.Dumpable;
+import android.service.autofill.CallbackHelper.Finalizer;
 import android.util.Log;
+import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
+import java.io.PrintWriter;
+
 /**
  * Handles auto-fill requests from the {@link AutoFillService} into the {@link Activity} being
  * auto-filled.
+ *
+ * <p>This class is thread safe.
  */
-public final class FillCallback {
+public final class FillCallback implements Dumpable {
 
     private static final String TAG = "FillCallback";
 
+    // NOTE: constants below are public so they can be used by flagsToString()
+    /** @hide */ public static final int STATE_INITIAL = 1 << 0;
+    /** @hide */ public static final int STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE = 1 << 1;
+    /** @hide */ public static final int STATE_WAITING_DATASET_AUTH_RESPONSE = 1 << 2;
+    /** @hide */ public static final int STATE_FINISHED_OK = 1 << 3;
+    /** @hide */ public static final int STATE_FINISHED_FAILURE = 1 << 4;
+    /** @hide */ public static final int STATE_FINISHED_ERROR = 1 << 5;
+    /** @hide */ public static final int STATE_FINISHED_AUTHENTICATED = 1 << 6;
+
     private final IAutoFillServerCallback mCallback;
 
-    private boolean mReplied = false;
+    @GuardedBy("mCallback")
+    private int mState = STATE_INITIAL;
+
+    @GuardedBy("mCallback")
+    private Finalizer mFinalizer;
 
     /** @hide */
     FillCallback(IAutoFillServerCallback callback) {
@@ -47,51 +68,174 @@
 
     /**
      * Notifies the Android System that an
-     * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
-     * was successfully fulfilled by the service.
+     * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
+     * android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service.
      *
      * @param response auto-fill information for that activity, or {@code null} when the activity
-     * cannot be auto-filled (for example, if it only contains read-only fields).
-     *
-     * @throws RuntimeException if an error occurred while calling the Android System.
+     * cannot be auto-filled (for example, if it only contains read-only fields). See
+     * {@link FillResponse} for examples.
      */
     public void onSuccess(@Nullable FillResponse response) {
-        if (DEBUG) Log.d(TAG, "onSuccess(): respose=" + response);
+        final boolean authRequired = response != null && response.isAuthRequired();
 
-        checkNotRepliedYet();
+        if (DEBUG) Log.d(TAG, "onSuccess(): authReq= " + authRequired + ", resp=" + response);
 
-        try {
-            mCallback.showResponse(response);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+        synchronized (mCallback) {
+            if (authRequired) {
+                assertOnStateLocked(STATE_INITIAL);
+            } else {
+                assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE
+                        | STATE_WAITING_DATASET_AUTH_RESPONSE);
+            }
+
+            try {
+                mCallback.showResponse(response);
+                if (authRequired) {
+                    mState = STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE;
+                } else {
+                    // Check if at least one dataset requires authentication.
+                    boolean waitingAuth = false;
+                    if (response != null) {
+                        for (Dataset dataset : response.getDatasets()) {
+                            if (dataset.isAuthRequired()) {
+                                waitingAuth = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (waitingAuth) {
+                        mState = STATE_WAITING_DATASET_AUTH_RESPONSE;
+                    } else {
+                        setFinalStateLocked(STATE_FINISHED_OK);
+                    }
+                }
+            } catch (RemoteException e) {
+                setFinalStateLocked(STATE_FINISHED_ERROR);
+                e.rethrowAsRuntimeException();
+            }
         }
     }
 
     /**
      * Notifies the Android System that an
-     * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, FillCallback)}
+     * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+     * Bundle, android.os.CancellationSignal, FillCallback)}
      * could not be fulfilled by the service.
      *
      * @param message error message to be displayed to the user.
-     *
-     * @throws RuntimeException if an error occurred while calling the Android System.
      */
     public void onFailure(CharSequence message) {
         if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
 
-        checkNotRepliedYet();
         Preconditions.checkArgument(message != null, "message cannot be null");
 
-        try {
-            mCallback.showError(message.toString());
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+        synchronized (mCallback) {
+            assertOnStateLocked(STATE_INITIAL | STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE
+                    | STATE_WAITING_DATASET_AUTH_RESPONSE);
+
+            try {
+                mCallback.showError(message);
+                setFinalStateLocked(STATE_FINISHED_FAILURE);
+            } catch (RemoteException e) {
+                setFinalStateLocked(STATE_FINISHED_ERROR);
+                e.rethrowAsRuntimeException();
+            }
         }
     }
 
-    // There can be only one!!
-    private void checkNotRepliedYet() {
-        Preconditions.checkState(!mReplied, "already replied");
-        mReplied = true;
+    /**
+     * Notifies the Android System when the user authenticated a {@link FillResponse} previously
+     * passed to {@link #onSuccess(FillResponse)}.
+     *
+     * @param flags must contain either
+     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or
+     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}.
+     */
+    public void onFillResponseAuthentication(int flags) {
+        if (DEBUG) Log.d(TAG, "onFillResponseAuthentication(): flags=" + flags);
+
+        synchronized (mCallback) {
+            assertOnStateLocked(STATE_WAITING_FILL_RESPONSE_AUTH_RESPONSE);
+
+            try {
+                mCallback.unlockFillResponse(flags);
+                setFinalStateLocked(STATE_FINISHED_AUTHENTICATED);
+            } catch (RemoteException e) {
+                setFinalStateLocked(STATE_FINISHED_ERROR);
+                e.rethrowAsRuntimeException();
+            }
+        }
+    }
+
+    /**
+     * Notifies the Android System when the user authenticated a {@link Dataset} previously passed
+     * to {@link #onSuccess(FillResponse)}.
+     *
+     * @param dataset values to fill the activity with in case of successful authentication of a
+     * previously locked (and empty) dataset).
+     * @param flags must contain either
+     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} or
+     * {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}.
+     */
+    public void onDatasetAuthentication(@Nullable Dataset dataset, int flags) {
+        if (DEBUG) Log.d(TAG, "onDatasetAuthentication(): dataset=" + dataset + ", flags=" + flags);
+
+        synchronized (mCallback) {
+            assertOnStateLocked(STATE_WAITING_DATASET_AUTH_RESPONSE);
+
+            try {
+                mCallback.unlockDataset(dataset, flags);
+                setFinalStateLocked(STATE_FINISHED_AUTHENTICATED);
+            } catch (RemoteException e) {
+                setFinalStateLocked(STATE_FINISHED_ERROR);
+                e.rethrowAsRuntimeException();
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (!DEBUG) return super.toString();
+
+        return "FillCallback: [mState = " + mState + "]";
+    }
+
+    /** @hide */
+    @Override
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("FillCallback: mState="); pw.println(mState);
+    }
+
+    /** @hide */
+    @Override
+    public void setFinalizer(Finalizer f) {
+        synchronized (mCallback) {
+            mFinalizer = f;
+        }
+    }
+
+    /**
+     * Sets a final state (where the callback cannot be used anymore) and notifies the
+     * {@link Finalizer} (if any).
+     */
+    private void setFinalStateLocked(int state) {
+        if (DEBUG) Log.d(TAG, "setFinalState(): " + state);
+        mState = state;
+
+        if (mFinalizer != null) {
+            mFinalizer.gone();
+        }
+    }
+
+    // TODO(b/33197203): move and/or re-add state check logic on server side to avoid malicious app
+    // calling the callback on wrong state.
+
+    // Make sure callback method is called during the proper lifecycle state.
+    private void assertOnStateLocked(int flags) {
+        if (DEBUG) Log.d(TAG, "assertOnState(): current=" + mState + ", required=" + flags);
+
+        Preconditions.checkState((flags & mState) != 0,
+                "invalid state: required " + flagsToString(FillCallback.class, "STATE_", flags)
+                + ", current is " + flagsToString(FillCallback.class, "STATE_", mState));
     }
 }
diff --git a/core/java/android/service/autofill/IAutoFillAppCallback.aidl b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
index 629b1f0..cc83776 100644
--- a/core/java/android/service/autofill/IAutoFillAppCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillAppCallback.aidl
@@ -21,8 +21,15 @@
 import android.view.autofill.Dataset;
 
 /**
+ * Object running in the application process and responsible for auto-filling it.
+ *
  * @hide
  */
+// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
+// itself
 oneway interface IAutoFillAppCallback {
+    /**
+      * Auto-fills the activity with the contents of a dataset.
+      */
     void autoFill(in Dataset dataset);
 }
diff --git a/core/java/android/service/autofill/IAutoFillManagerService.aidl b/core/java/android/service/autofill/IAutoFillManagerService.aidl
index f8ae57b..ce42107 100644
--- a/core/java/android/service/autofill/IAutoFillManagerService.aidl
+++ b/core/java/android/service/autofill/IAutoFillManagerService.aidl
@@ -16,7 +16,9 @@
 
 package android.service.autofill;
 
+import android.graphics.Rect;
 import android.os.Bundle;
+import android.view.autofill.AutoFillId;
 
 /**
  * Mediator between apps being auto-filled and auto-fill service implementations.
@@ -24,5 +26,9 @@
  * {@hide}
  */
 oneway interface IAutoFillManagerService {
+
+    void showAutoFillInput(in AutoFillId id, in Rect boundaries);
+
+    // TODO(b/33197203): remove it and refactor onShellCommand
     void requestAutoFill(IBinder activityToken, int userId, in Bundle extras, int flags);
 }
diff --git a/core/java/android/service/autofill/IAutoFillServerCallback.aidl b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
index 9d58c99..185c8f3 100644
--- a/core/java/android/service/autofill/IAutoFillServerCallback.aidl
+++ b/core/java/android/service/autofill/IAutoFillServerCallback.aidl
@@ -18,14 +18,23 @@
 
 import java.util.List;
 
+import android.os.Bundle;
 import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
 
 /**
+ * Object running in the AutoFillService process and used to communicate back with system_server.
+ *
  * @hide
  */
+// TODO(b/33197203): rename methods to make them more consistent with a callback, or rename class
+// itself
 oneway interface IAutoFillServerCallback {
+    // TODO(b/33197203): document methods
     void showResponse(in FillResponse response);
-    void showError(String message);
+    void showError(CharSequence message);
     void highlightSavedFields(in AutoFillId[] ids);
+    void unlockFillResponse(int flags);
+    void unlockDataset(in Dataset dataset, int flags);
 }
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index a1f22bf..fa9786a 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -24,9 +24,13 @@
 /**
  * @hide
  */
+// TODO(b/33197203): document class and methods
 oneway interface IAutoFillService {
+    // TODO(b/33197203): rename method to make them more consistent
     void autoFill(in AssistStructure structure, in IAutoFillServerCallback callback,
                   in Bundle extras, int flags);
+    void authenticateFillResponse(in Bundle extras, int flags);
+    void authenticateDataset(in Bundle extras, int flags);
     void onConnected();
     void onDisconnected();
 }
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 627d74c..d5022d8 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -21,25 +21,35 @@
 import android.app.Activity;
 import android.app.assist.AssistStructure.ViewNode;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.RemoteException;
+import android.service.autofill.CallbackHelper.Dumpable;
+import android.service.autofill.CallbackHelper.Finalizer;
 import android.util.Log;
 import android.view.autofill.AutoFillId;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
+import java.io.PrintWriter;
+
 /**
  * Handles save requests from the {@link AutoFillService} into the {@link Activity} being
  * auto-filled.
+ *
+ * <p>This class is thread safe.
  */
-public final class SaveCallback {
+public final class SaveCallback implements Dumpable {
 
     private static final String TAG = "SaveCallback";
 
     private final IAutoFillServerCallback mCallback;
 
+    @GuardedBy("mCallback")
     private boolean mReplied = false;
 
+    @GuardedBy("mCallback")
+    private Finalizer mFinalizer;
+
     /** @hide */
     SaveCallback(IAutoFillServerCallback callback) {
         mCallback = callback;
@@ -47,8 +57,8 @@
 
     /**
      * Notifies the Android System that an
-     * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
-     * was successfully fulfilled by the service.
+     * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+     * android.os.CancellationSignal, SaveCallback)} was successfully fulfilled by the service.
      *
      * @param ids ids ({@link ViewNode#getAutoFillId()}) of the fields that were saved.
      *
@@ -58,21 +68,24 @@
         if (DEBUG) Log.d(TAG, "onSuccess(): ids=" + ((ids == null) ? "null" : ids.length));
 
         Preconditions.checkArgument(ids != null, "ids cannot be null");
-        checkNotRepliedYet();
-
         Preconditions.checkArgument(ids.length > 0, "ids cannot be empty");
 
-        try {
-            mCallback.highlightSavedFields(ids);
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+        synchronized (mCallback) {
+            checkNotRepliedYetLocked();
+            try {
+                mCallback.highlightSavedFields(ids);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            } finally {
+                setRepliedLocked();
+            }
         }
     }
 
     /**
      * Notifies the Android System that an
-     * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, SaveCallback)}
-     * could not be fulfilled by the service.
+     * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
+     * android.os.CancellationSignal, SaveCallback)} could not be fulfilled by the service.
      *
      * @param message error message to be displayed to the user.
      *
@@ -82,18 +95,53 @@
         if (DEBUG) Log.d(TAG, "onFailure(): message=" + message);
 
         Preconditions.checkArgument(message != null, "message cannot be null");
-        checkNotRepliedYet();
 
-        try {
-            mCallback.showError(message.toString());
-        } catch (RemoteException e) {
-            e.rethrowAsRuntimeException();
+        synchronized (mCallback) {
+            checkNotRepliedYetLocked();
+
+            try {
+                mCallback.showError(message);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            } finally {
+                setRepliedLocked();
+            }
         }
     }
 
+    /** @hide */
+    @Override
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("SaveCallback: mReplied="); pw.println(mReplied);
+    }
+
+    /** @hide */
+    @Override
+    public void setFinalizer(Finalizer f) {
+        synchronized (mCallback) {
+            mFinalizer = f;
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (!DEBUG) return super.toString();
+
+        return "SaveCallback: [mReplied= " + mReplied + "]";
+    }
+
     // There can be only one!!
-    private void checkNotRepliedYet() {
+    private void checkNotRepliedYetLocked() {
         Preconditions.checkState(!mReplied, "already replied");
+    }
+
+    private void setRepliedLocked() {
+        if (DEBUG) Log.d(TAG, "setReplied()");
+
         mReplied = true;
+
+        if (mFinalizer != null) {
+            mFinalizer.gone();
+        }
     }
 }
diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.java b/core/java/android/service/gatekeeper/GateKeeperResponse.java
index a512957..287dc76 100644
--- a/core/java/android/service/gatekeeper/GateKeeperResponse.java
+++ b/core/java/android/service/gatekeeper/GateKeeperResponse.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Response object for a GateKeeper verification request.
  * @hide
@@ -35,12 +37,28 @@
     private byte[] mPayload;
     private boolean mShouldReEnroll;
 
+    /** Default constructor for response with generic response code **/
     private GateKeeperResponse(int responseCode) {
         mResponseCode = responseCode;
     }
 
-    private GateKeeperResponse(int responseCode, int timeout) {
-        mResponseCode = responseCode;
+    @VisibleForTesting
+    public static GateKeeperResponse createGenericResponse(int responseCode) {
+        return new GateKeeperResponse(responseCode);
+    }
+
+    private static GateKeeperResponse createRetryResponse(int timeout) {
+        GateKeeperResponse response = new GateKeeperResponse(RESPONSE_RETRY);
+        response.mTimeout = timeout;
+        return response;
+    }
+
+    @VisibleForTesting
+    public static GateKeeperResponse createOkResponse(byte[] payload, boolean shouldReEnroll) {
+        GateKeeperResponse response = new GateKeeperResponse(RESPONSE_OK);
+        response.mPayload = payload;
+        response.mShouldReEnroll = shouldReEnroll;
+        return response;
     }
 
     @Override
@@ -53,17 +71,20 @@
         @Override
         public GateKeeperResponse createFromParcel(Parcel source) {
             int responseCode = source.readInt();
-            GateKeeperResponse response = new GateKeeperResponse(responseCode);
+            final GateKeeperResponse response;
             if (responseCode == RESPONSE_RETRY) {
-                response.setTimeout(source.readInt());
+                response = createRetryResponse(source.readInt());
             } else if (responseCode == RESPONSE_OK) {
-                response.setShouldReEnroll(source.readInt() == 1);
+                final boolean shouldReEnroll = source.readInt() == 1;
+                byte[] payload = null;
                 int size = source.readInt();
                 if (size > 0) {
-                    byte[] payload = new byte[size];
+                    payload = new byte[size];
                     source.readByteArray(payload);
-                    response.setPayload(payload);
                 }
+                response = createOkResponse(payload, shouldReEnroll);
+            } else {
+                response = createGenericResponse(responseCode);
             }
             return response;
         }
@@ -104,17 +125,4 @@
     public int getResponseCode() {
         return mResponseCode;
     }
-
-    private void setTimeout(int timeout) {
-        mTimeout = timeout;
-    }
-
-    private void setShouldReEnroll(boolean shouldReEnroll) {
-        mShouldReEnroll = shouldReEnroll;
-    }
-
-    private void setPayload(byte[] payload) {
-        mPayload = payload;
-    }
-
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index d7a02a8..cecdbee 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -119,6 +119,24 @@
     }
 
     /**
+     * Inform the notification manager about un-snoozing a specific notification.
+     * <p>
+     * This should only be used for notifications snoozed by this listener using
+     * {@link #snoozeNotification(String, String)}. Once un-snoozed, you will get a
+     * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
+     * notification.
+     * @param key The key of the notification to snooze
+     */
+    public final void unsnoozeNotification(String key) {
+        if (!isBound()) return;
+        try {
+            getNotificationInterface().unsnoozeNotificationFromAssistant(mWrapper, key);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    /**
      * Creates a notification channel that notifications can be posted to for a given package.
      *
      * @param pkg The package to create a channel for.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 694837e..417be60 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -183,8 +183,8 @@
     public static final int REASON_CHANNEL_BANNED = 17;
     /** Notification was snoozed. */
     public static final int REASON_SNOOZED = 18;
-    /** Notification no longer visible because of user switch */
-    public static final int REASON_USER_SWITCH = 19;
+    /** Notification was canceled due to timeout */
+    public static final int REASON_TIMEOUT = 19;
 
     /**
      * The full trim of the StatusBarNotification including all its features.
@@ -562,43 +562,6 @@
         }
     }
 
-    /**
-     * Inform the notification manager about snoozing a specific notification.
-     * <p>
-     * Use this to snooze a notification for an indeterminate time.  Upon being informed, the
-     * notification manager will actually remove the notification and you will get an
-     * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the
-     * snoozing period expires, you will get a
-     * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
-     * notification. Use {@link #unsnoozeNotification(String)} to restore the notification.
-     * @param key The key of the notification to snooze
-     */
-    public final void snoozeNotification(String key) {
-        if (!isBound()) return;
-        try {
-            getNotificationInterface().snoozeNotificationFromListener(mWrapper, key);
-        } catch (android.os.RemoteException ex) {
-            Log.v(TAG, "Unable to contact notification manager", ex);
-        }
-    }
-
-    /**
-     * Inform the notification manager about un-snoozing a specific notification.
-     * <p>
-     * This should only be used for notifications snoozed by this listener using
-     * {@link #snoozeNotification(String)}. Once un-snoozed, you will get a
-     * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
-     * notification.
-     * @param key The key of the notification to snooze
-     */
-    public final void unsnoozeNotification(String key) {
-        if (!isBound()) return;
-        try {
-            getNotificationInterface().unsnoozeNotificationFromListener(mWrapper, key);
-        } catch (android.os.RemoteException ex) {
-            Log.v(TAG, "Unable to contact notification manager", ex);
-        }
-    }
 
     /**
      * Inform the notification manager that these notifications have been viewed by the
@@ -663,6 +626,26 @@
     }
 
     /**
+     * Like {@link #getActiveNotifications()}, but returns the list of currently snoozed
+     * notifications, for all users this listener has access to.
+     *
+     * <p>The service should wait for the {@link #onListenerConnected()} event
+     * before performing this operation.
+     *
+     * @return An array of active notifications, sorted in natural order.
+     */
+    public final StatusBarNotification[] getSnoozedNotifications() {
+        try {
+            ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
+                    .getSnoozedNotificationsFromListener(mWrapper, TRIM_FULL);
+            return cleanUpNotificationList(parceledList);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+        return null;
+    }
+
+    /**
      * Request the list of outstanding notifications (that is, those that are visible to the
      * current user). Useful when you don't know what's already been posted.
      *
@@ -711,36 +694,41 @@
         try {
             ParceledListSlice<StatusBarNotification> parceledList = getNotificationInterface()
                     .getActiveNotificationsFromListener(mWrapper, keys, trim);
-            List<StatusBarNotification> list = parceledList.getList();
-            ArrayList<StatusBarNotification> corruptNotifications = null;
-            int N = list.size();
-            for (int i = 0; i < N; i++) {
-                StatusBarNotification sbn = list.get(i);
-                Notification notification = sbn.getNotification();
-                try {
-                    // convert icon metadata to legacy format for older clients
-                    createLegacyIconExtras(notification);
-                    // populate remote views for older clients.
-                    maybePopulateRemoteViews(notification);
-                } catch (IllegalArgumentException e) {
-                    if (corruptNotifications == null) {
-                        corruptNotifications = new ArrayList<>(N);
-                    }
-                    corruptNotifications.add(sbn);
-                    Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
-                            sbn.getPackageName());
-                }
-            }
-            if (corruptNotifications != null) {
-                list.removeAll(corruptNotifications);
-            }
-            return list.toArray(new StatusBarNotification[list.size()]);
+            return cleanUpNotificationList(parceledList);
         } catch (android.os.RemoteException ex) {
             Log.v(TAG, "Unable to contact notification manager", ex);
         }
         return null;
     }
 
+    private StatusBarNotification[] cleanUpNotificationList(
+            ParceledListSlice<StatusBarNotification> parceledList) {
+        List<StatusBarNotification> list = parceledList.getList();
+        ArrayList<StatusBarNotification> corruptNotifications = null;
+        int N = list.size();
+        for (int i = 0; i < N; i++) {
+            StatusBarNotification sbn = list.get(i);
+            Notification notification = sbn.getNotification();
+            try {
+                // convert icon metadata to legacy format for older clients
+                createLegacyIconExtras(notification);
+                // populate remote views for older clients.
+                maybePopulateRemoteViews(notification);
+            } catch (IllegalArgumentException e) {
+                if (corruptNotifications == null) {
+                    corruptNotifications = new ArrayList<>(N);
+                }
+                corruptNotifications.add(sbn);
+                Log.w(TAG, "get(Active/Snoozed)Notifications: can't rebuild notification from " +
+                        sbn.getPackageName());
+            }
+        }
+        if (corruptNotifications != null) {
+            list.removeAll(corruptNotifications);
+        }
+        return list.toArray(new StatusBarNotification[list.size()]);
+    }
+
     /**
      * Gets the set of hints representing current state.
      *
@@ -1166,11 +1154,12 @@
         // System specified group key.
         private String mOverrideGroupKey;
         // Notification assistant channel override.
-        private NotificationChannel mOverrideChannel;
+        private NotificationChannel mChannel;
         // Notification assistant people override.
         private ArrayList<String> mOverridePeople;
         // Notification assistant snooze criteria.
         private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+        private boolean mShowBadge;
 
         public Ranking() {}
 
@@ -1200,7 +1189,7 @@
         }
 
         /**
-         * Returns the user specificed visibility for the package that posted
+         * Returns the user specified visibility for the package that posted
          * this notification, or
          * {@link NotificationListenerService.Ranking#VISIBILITY_NO_OVERRIDE} if
          * no such preference has been expressed.
@@ -1233,7 +1222,7 @@
          * Returns the importance of the notification, which dictates its
          * modes of presentation, see: {@link NotificationManager#IMPORTANCE_DEFAULT}, etc.
          *
-         * @return the rank of the notification
+         * @return the importance of the notification
          */
         public @NotificationManager.Importance int getImportance() {
             return mImportance;
@@ -1258,12 +1247,11 @@
         }
 
         /**
-         * If the {@link NotificationAssistantService} has overridden the channel this notification
-         * was posted to, then this will not match the channel provided by the posting application
-         * and this should be used to determine the interruptiveness of the notification instead.
+         * Returns the notification channel this notification was posted to, which dictates
+         * notification behavior and presentation.
          */
         public NotificationChannel getChannel() {
-            return mOverrideChannel;
+            return mChannel;
         }
 
         /**
@@ -1283,11 +1271,20 @@
             return mSnoozeCriteria;
         }
 
+        /**
+         * Returns whether this notification can be displayed as a badge.
+         *
+         * @return true if the notification can be displayed as a badge, false otherwise.
+         */
+        public boolean canShowBadge() {
+            return mShowBadge;
+        }
+
         private void populate(String key, int rank, boolean matchesInterruptionFilter,
                 int visibilityOverride, int suppressedVisualEffects, int importance,
                 CharSequence explanation, String overrideGroupKey,
-                NotificationChannel overrideChannel, ArrayList<String> overridePeople,
-                ArrayList<SnoozeCriterion> snoozeCriteria) {
+                NotificationChannel channel, ArrayList<String> overridePeople,
+                ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1297,9 +1294,10 @@
             mImportance = importance;
             mImportanceExplanation = explanation;
             mOverrideGroupKey = overrideGroupKey;
-            mOverrideChannel = overrideChannel;
+            mChannel = channel;
             mOverridePeople = overridePeople;
             mSnoozeCriteria = snoozeCriteria;
+            mShowBadge = showBadge;
         }
 
         /**
@@ -1343,9 +1341,10 @@
         private ArrayMap<String, Integer> mImportance;
         private ArrayMap<String, String> mImportanceExplanation;
         private ArrayMap<String, String> mOverrideGroupKeys;
-        private ArrayMap<String, NotificationChannel> mOverrideChannels;
+        private ArrayMap<String, NotificationChannel> mChannels;
         private ArrayMap<String, ArrayList<String>> mOverridePeople;
         private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
+        private ArrayMap<String, Boolean> mShowBadge;
 
         private RankingMap(NotificationRankingUpdate rankingUpdate) {
             mRankingUpdate = rankingUpdate;
@@ -1373,7 +1372,8 @@
             outRanking.populate(key, rank, !isIntercepted(key),
                     getVisibilityOverride(key), getSuppressedVisualEffects(key),
                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
-                    getOverrideChannel(key), getOverridePeople(key), getSnoozeCriteria(key));
+                    getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
+                    getShowBadge(key));
             return rank >= 0;
         }
 
@@ -1453,13 +1453,13 @@
             return mOverrideGroupKeys.get(key);
         }
 
-        private NotificationChannel getOverrideChannel(String key) {
+        private NotificationChannel getChannel(String key) {
             synchronized (this) {
-                if (mOverrideChannels == null) {
-                    buildOverrideChannelsLocked();
+                if (mChannels == null) {
+                    buildChannelsLocked();
                 }
             }
-            return mOverrideChannels.get(key);
+            return mChannels.get(key);
         }
 
         private ArrayList<String> getOverridePeople(String key) {
@@ -1480,6 +1480,16 @@
             return mSnoozeCriteria.get(key);
         }
 
+        private boolean getShowBadge(String key) {
+            synchronized (this) {
+                if (mShowBadge == null) {
+                    buildShowBadgeLocked();
+                }
+            }
+            Boolean showBadge = mShowBadge.get(key);
+            return showBadge == null ? false : showBadge.booleanValue();
+        }
+
         // Locked by 'this'
         private void buildRanksLocked() {
             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1544,11 +1554,11 @@
         }
 
         // Locked by 'this'
-        private void buildOverrideChannelsLocked() {
-            Bundle overrideChannels = mRankingUpdate.getOverrideChannels();
-            mOverrideChannels = new ArrayMap<>(overrideChannels.size());
-            for (String key : overrideChannels.keySet()) {
-                mOverrideChannels.put(key, overrideChannels.getParcelable(key));
+        private void buildChannelsLocked() {
+            Bundle channels = mRankingUpdate.getChannels();
+            mChannels = new ArrayMap<>(channels.size());
+            for (String key : channels.keySet()) {
+                mChannels.put(key, channels.getParcelable(key));
             }
         }
 
@@ -1570,6 +1580,15 @@
             }
         }
 
+        // Locked by 'this'
+        private void buildShowBadgeLocked() {
+            Bundle showBadge = mRankingUpdate.getShowBadge();
+            mShowBadge = new ArrayMap<>(showBadge.size());
+            for (String key : showBadge.keySet()) {
+                mShowBadge.put(key, showBadge.getBoolean(key));
+            }
+        }
+
         // ----------- Parcelable
 
         @Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index a2cdeff..326b212 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -31,14 +31,16 @@
     private final int[] mImportance;
     private final Bundle mImportanceExplanation;
     private final Bundle mOverrideGroupKeys;
-    private final Bundle mOverrideChannels;
+    private final Bundle mChannels;
     private final Bundle mOverridePeople;
     private final Bundle mSnoozeCriteria;
+    private final Bundle mShowBadge;
 
     public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
             Bundle visibilityOverrides, Bundle suppressedVisualEffects,
             int[] importance, Bundle explanation, Bundle overrideGroupKeys,
-            Bundle overrideChannels, Bundle overridePeople, Bundle snoozeCriteria) {
+            Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
+            Bundle showBadge) {
         mKeys = keys;
         mInterceptedKeys = interceptedKeys;
         mVisibilityOverrides = visibilityOverrides;
@@ -46,9 +48,10 @@
         mImportance = importance;
         mImportanceExplanation = explanation;
         mOverrideGroupKeys = overrideGroupKeys;
-        mOverrideChannels = overrideChannels;
+        mChannels = channels;
         mOverridePeople = overridePeople;
         mSnoozeCriteria = snoozeCriteria;
+        mShowBadge = showBadge;
     }
 
     public NotificationRankingUpdate(Parcel in) {
@@ -60,9 +63,10 @@
         in.readIntArray(mImportance);
         mImportanceExplanation = in.readBundle();
         mOverrideGroupKeys = in.readBundle();
-        mOverrideChannels = in.readBundle();
+        mChannels = in.readBundle();
         mOverridePeople = in.readBundle();
         mSnoozeCriteria = in.readBundle();
+        mShowBadge = in.readBundle();
     }
 
     @Override
@@ -79,9 +83,10 @@
         out.writeIntArray(mImportance);
         out.writeBundle(mImportanceExplanation);
         out.writeBundle(mOverrideGroupKeys);
-        out.writeBundle(mOverrideChannels);
+        out.writeBundle(mChannels);
         out.writeBundle(mOverridePeople);
         out.writeBundle(mSnoozeCriteria);
+        out.writeBundle(mShowBadge);
     }
 
     public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -123,8 +128,8 @@
         return mOverrideGroupKeys;
     }
 
-    public Bundle getOverrideChannels() {
-        return mOverrideChannels;
+    public Bundle getChannels() {
+        return mChannels;
     }
 
     public Bundle getOverridePeople() {
@@ -134,4 +139,8 @@
     public Bundle getSnoozeCriteria() {
         return mSnoozeCriteria;
     }
+
+    public Bundle getShowBadge() {
+        return mShowBadge;
+    }
 }
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 6276af3..85baf4e 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -43,21 +43,18 @@
     private final Notification notification;
     private final UserHandle user;
     private final long postTime;
-    private final NotificationChannel channel;
 
     private Context mContext; // used for inflation & icon expansion
 
     /** @hide */
-    public StatusBarNotification(String pkg, String opPkg, NotificationChannel channel, int id,
+    public StatusBarNotification(String pkg, String opPkg, int id,
             String tag, int uid, int initialPid, Notification notification, UserHandle user,
             String overrideGroupKey, long postTime) {
         if (pkg == null) throw new NullPointerException();
         if (notification == null) throw new NullPointerException();
-        if (channel == null) throw new IllegalArgumentException();
 
         this.pkg = pkg;
         this.opPkg = opPkg;
-        this.channel = channel;
         this.id = id;
         this.tag = tag;
         this.uid = uid;
@@ -88,7 +85,6 @@
         this.postTime = postTime;
         this.key = key();
         this.groupKey = groupKey();
-        this.channel = null;
     }
 
     public StatusBarNotification(Parcel in) {
@@ -112,7 +108,6 @@
         }
         this.key = key();
         this.groupKey = groupKey();
-        this.channel = NotificationChannel.CREATOR.createFromParcel(in);
     }
 
     private String key() {
@@ -182,7 +177,6 @@
         } else {
             out.writeInt(0);
         }
-        this.channel.writeToParcel(out, flags);
     }
 
     public int describeContents() {
@@ -209,14 +203,14 @@
     public StatusBarNotification cloneLight() {
         final Notification no = new Notification();
         this.notification.cloneInto(no, false); // light copy
-        return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+        return new StatusBarNotification(this.pkg, this.opPkg,
                 this.id, this.tag, this.uid, this.initialPid,
                 no, this.user, this.overrideGroupKey, this.postTime);
     }
 
     @Override
     public StatusBarNotification clone() {
-        return new StatusBarNotification(this.pkg, this.opPkg, this.channel,
+        return new StatusBarNotification(this.pkg, this.opPkg,
                 this.id, this.tag, this.uid, this.initialPid,
                 this.notification.clone(), this.user, this.overrideGroupKey, this.postTime);
     }
@@ -336,13 +330,6 @@
     }
 
     /**
-     * Returns the channel this notification was posted to.
-     */
-    public NotificationChannel getNotificationChannel() {
-        return channel;
-    }
-
-    /**
      * @hide
      */
     public Context getPackageContext(Context context) {
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index 9920ea1..be5851c 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -164,7 +164,7 @@
         // all data from the audioTrack has been sent to the mixer, so
         // it's safe to release at this point.
         if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]");
-        synchronized(mAudioTrackLock) {
+        synchronized (mAudioTrackLock) {
             mAudioTrack = null;
         }
         track.release();
@@ -340,4 +340,25 @@
         return value < min ? min : (value < max ? value : max);
     }
 
+    /**
+     * @see
+     *     AudioTrack#setPlaybackPositionUpdateListener(AudioTrack.OnPlaybackPositionUpdateListener).
+     */
+    public void setPlaybackPositionUpdateListener(
+            AudioTrack.OnPlaybackPositionUpdateListener listener) {
+        synchronized (mAudioTrackLock) {
+            if (mAudioTrack != null) {
+                mAudioTrack.setPlaybackPositionUpdateListener(listener);
+            }
+        }
+    }
+
+    /** @see AudioTrack#setNotificationMarkerPosition(int). */
+    public void setNotificationMarkerPosition(int frames) {
+        synchronized (mAudioTrackLock) {
+            if (mAudioTrack != null) {
+                mAudioTrack.setNotificationMarkerPosition(frames);
+            }
+        }
+    }
 }
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 4e3acf6..edb6e48 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -83,4 +83,19 @@
      * callback.
      */
     void onAudioAvailable(String utteranceId, in byte[] audio);
+
+    /**
+     * Tells the client that the engine is about to speak the specified range of the utterance.
+     *
+     * <p>
+     * Only called if the engine supplies timing information by calling
+     * {@link SynthesisCallback#rangeStart(int, int, int)} and only when the request is played back
+     * by the service, not when using {@link android.speech.tts.TextToSpeech#synthesizeToFile}.
+     * </p>
+     *
+     * @param utteranceId Unique id identifying the synthesis request.
+     * @param start The start character index of the range in the utterance text.
+     * @param end The end character index of the range (exclusive) in the utterance text.
+     */
+    void onUtteranceRangeStart(String utteranceId, int start, int end);
 }
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 778aa86..9e24b09 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -271,4 +271,12 @@
             mStatusCode = errorCode;
         }
     }
+
+    public void rangeStart(int markerInFrames, int start, int end) {
+        if (mItem == null) {
+            Log.e(TAG, "mItem is null");
+            return;
+        }
+        mItem.rangeStart(markerInFrames, start, end);
+    }
 }
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index 2fd8499..8b74ed7 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -142,4 +142,26 @@
      * <p>Useful for checking if a fallback from network request is possible.
      */
     boolean hasFinished();
+
+    /**
+     * The service may call this method to provide timing information about the spoken text.
+     *
+     * <p>Calling this method means that at the given audio frame, the given range of the input is
+     * about to be spoken. If this method is called the client will receive a callback on the
+     * listener ({@link UtteranceProgressListener#onUtteranceRangeStart}) at the moment that frame
+     * has been reached by the playback head.
+     *
+     * <p>The markerInFrames is a frame index into the audio for this synthesis request, i.e. into
+     * the concatenation of the audio bytes sent to audioAvailable for this synthesis request. The
+     * definition of a frame depends on the format given by {@link #start}. See {@link AudioFormat}
+     * for more information.
+     *
+     * <p>This method should only be called on the synthesis thread, while in {@link
+     * TextToSpeechService#onSynthesizeText}.
+     *
+     * @param markerInFrames The position in frames in the audio where this range is spoken.
+     * @param start The start index of the range in the input text.
+     * @param end The end index (exclusive) of the range in the input text.
+     */
+    default void rangeStart(int markerInFrames, int start, int end) {}
 }
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index 7423933..cb5f220 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -17,18 +17,21 @@
 
 import android.speech.tts.TextToSpeechService.AudioOutputParams;
 import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
+import android.media.AudioTrack;
 import android.util.Log;
 
 import java.util.LinkedList;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 /**
- * Manages the playback of a list of byte arrays representing audio data
- * that are queued by the engine to an audio track.
+ * Manages the playback of a list of byte arrays representing audio data that are queued by the
+ * engine to an audio track.
  */
-final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
+final class SynthesisPlaybackQueueItem extends PlaybackQueueItem
+        implements AudioTrack.OnPlaybackPositionUpdateListener {
     private static final String TAG = "TTS.SynthQueueItem";
     private static final boolean DBG = false;
 
@@ -63,6 +66,10 @@
     private final BlockingAudioTrack mAudioTrack;
     private final AbstractEventLogger mLogger;
 
+    // Stores a queue of markers. When the marker in front is reached the client is informed and we
+    // wait for the next one.
+    private ConcurrentLinkedQueue<ProgressMarker> markerList = new ConcurrentLinkedQueue<>();
+
     SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
             int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
             Object callerIdentity, AbstractEventLogger logger) {
@@ -89,6 +96,8 @@
             return;
         }
 
+        mAudioTrack.setPlaybackPositionUpdateListener(this);
+
         try {
             byte[] buffer = null;
 
@@ -172,6 +181,55 @@
         }
     }
 
+    /** Convenience class for passing around TTS markers. */
+    private class ProgressMarker {
+        // The index in frames of this marker.
+        public final int frames;
+        // The start index in the text of the utterance.
+        public final int start;
+        // The end index (exclusive) in the text of the utterance.
+        public final int end;
+
+        public ProgressMarker(int frames, int start, int end) {
+            this.frames = frames;
+            this.start = start;
+            this.end = end;
+        }
+    }
+
+    /** Set a callback for the first marker in the queue. */
+    void updateMarker() {
+        ProgressMarker marker = markerList.peek();
+        if (marker != null) {
+            // Zero is used to disable the marker. The documentation recommends to use a non-zero
+            // position near zero such as 1.
+            int markerInFrames = marker.frames == 0 ? 1 : marker.frames;
+            mAudioTrack.setNotificationMarkerPosition(markerInFrames);
+        }
+    }
+
+    /** Informs us that at markerInFrames, the range between start and end is about to be spoken. */
+    void rangeStart(int markerInFrames, int start, int end) {
+        markerList.add(new ProgressMarker(markerInFrames, start, end));
+        updateMarker();
+    }
+
+    @Override
+    public void onMarkerReached(AudioTrack track) {
+        ProgressMarker marker = markerList.poll();
+        if (marker == null) {
+            Log.e(TAG, "onMarkerReached reached called but no marker in queue");
+            return;
+        }
+        // Inform the client.
+        getDispatcher().dispatchOnUtteranceRangeStart(marker.start, marker.end);
+        // Listen for the next marker.
+        // It's ok if this marker is in the past, in that case onMarkerReached will be called again.
+        updateMarker();
+    }
+
+    @Override
+    public void onPeriodicNotification(AudioTrack track) {}
 
     void put(byte[] buffer) throws InterruptedException {
         try {
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 24cad95..9a157b7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -2103,55 +2103,69 @@
 
         private boolean mEstablished;
 
-        private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
-            public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
-                UtteranceProgressListener listener = mUtteranceProgressListener;
-                if (listener != null) {
-                    listener.onStop(utteranceId, isStarted);
-                }
-            };
+        private final ITextToSpeechCallback.Stub mCallback =
+                new ITextToSpeechCallback.Stub() {
+                    public void onStop(String utteranceId, boolean isStarted)
+                            throws RemoteException {
+                        UtteranceProgressListener listener = mUtteranceProgressListener;
+                        if (listener != null) {
+                            listener.onStop(utteranceId, isStarted);
+                        }
+                    };
 
-            @Override
-            public void onSuccess(String utteranceId) {
-                UtteranceProgressListener listener = mUtteranceProgressListener;
-                if (listener != null) {
-                    listener.onDone(utteranceId);
-                }
-            }
+                    @Override
+                    public void onSuccess(String utteranceId) {
+                        UtteranceProgressListener listener = mUtteranceProgressListener;
+                        if (listener != null) {
+                            listener.onDone(utteranceId);
+                        }
+                    }
 
-            @Override
-            public void onError(String utteranceId, int errorCode) {
-                UtteranceProgressListener listener = mUtteranceProgressListener;
-                if (listener != null) {
-                    listener.onError(utteranceId);
-                }
-            }
+                    @Override
+                    public void onError(String utteranceId, int errorCode) {
+                        UtteranceProgressListener listener = mUtteranceProgressListener;
+                        if (listener != null) {
+                            listener.onError(utteranceId);
+                        }
+                    }
 
-            @Override
-            public void onStart(String utteranceId) {
-                UtteranceProgressListener listener = mUtteranceProgressListener;
-                if (listener != null) {
-                    listener.onStart(utteranceId);
-                }
-            }
+                    @Override
+                    public void onStart(String utteranceId) {
+                        UtteranceProgressListener listener = mUtteranceProgressListener;
+                        if (listener != null) {
+                            listener.onStart(utteranceId);
+                        }
+                    }
 
-            @Override
-            public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat,
-                                     int channelCount) {
-                UtteranceProgressListener listener = mUtteranceProgressListener;
-                if (listener != null) {
-                    listener.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
-                }
-            }
+                    @Override
+                    public void onBeginSynthesis(
+                            String utteranceId,
+                            int sampleRateInHz,
+                            int audioFormat,
+                            int channelCount) {
+                        UtteranceProgressListener listener = mUtteranceProgressListener;
+                        if (listener != null) {
+                            listener.onBeginSynthesis(
+                                    utteranceId, sampleRateInHz, audioFormat, channelCount);
+                        }
+                    }
 
-            @Override
-            public void onAudioAvailable(String utteranceId, byte[] audio) {
-                UtteranceProgressListener listener = mUtteranceProgressListener;
-                if (listener != null) {
-                    listener.onAudioAvailable(utteranceId, audio);
-                }
-            }
-        };
+                    @Override
+                    public void onAudioAvailable(String utteranceId, byte[] audio) {
+                        UtteranceProgressListener listener = mUtteranceProgressListener;
+                        if (listener != null) {
+                            listener.onAudioAvailable(utteranceId, audio);
+                        }
+                    }
+
+                    @Override
+                    public void onUtteranceRangeStart(String utteranceId, int start, int end) {
+                        UtteranceProgressListener listener = mUtteranceProgressListener;
+                        if (listener != null) {
+                            listener.onUtteranceRangeStart(utteranceId, start, end);
+                        }
+                    }
+                };
 
         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
             private final ComponentName mName;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 55da52b..80d3c8a 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -663,6 +663,8 @@
         void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
 
         void dispatchOnAudioAvailable(byte[] audio);
+
+        public void dispatchOnUtteranceRangeStart(int start, int end);
     }
 
     /** Set of parameters affecting audio output. */
@@ -882,6 +884,15 @@
             }
         }
 
+        @Override
+        public void dispatchOnUtteranceRangeStart(int start, int end) {
+            final String utteranceId = getUtteranceId();
+            if (utteranceId != null) {
+                mCallbacks.dispatchOnUtteranceRangeStart(
+                        getCallerIdentity(), utteranceId, start, end);
+            }
+        }
+
         abstract public String getUtteranceId();
 
         String getStringParam(Bundle params, String key, String defaultValue) {
@@ -1559,6 +1570,17 @@
             }
         }
 
+        public void dispatchOnUtteranceRangeStart(
+                Object callerIdentity, String utteranceId, int start, int end) {
+            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+            if (cb == null) return;
+            try {
+                cb.onUtteranceRangeStart(utteranceId, start, end);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Callback dispatchOnUtteranceRangeStart(String, int, int) failed: " + e);
+            }
+        }
+
         @Override
         public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
             IBinder caller = (IBinder) cookie;
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 72a5228..0ee3769 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -122,8 +122,24 @@
     }
 
     /**
-     * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
-     * progress listener.
+     * This is called when the TTS service is about to speak the specified range of the utterance
+     * with the given utteranceId.
+     *
+     * <p>This method is called when the audio is expected to start playing on the speaker. Note
+     * that this is different from {@link #onAudioAvailable} which is called as soon as the audio is
+     * generated.
+     *
+     * <p>Only called if the engine supplies timing information by calling {@link
+     * SynthesisCallback#rangeStart(int, int, int)}.
+     *
+     * @param utteranceId Unique id identifying the synthesis request.
+     * @param start The start index of the range in the utterance text.
+     * @param end The end index of the range (exclusive) in the utterance text.
+     */
+    public void onUtteranceRangeStart(String utteranceId, int start, int end) {}
+
+    /**
+     * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new progress listener.
      *
      * @hide
      */
diff --git a/core/java/android/text/LangId.java b/core/java/android/text/LangId.java
new file mode 100644
index 0000000..ed6e909
--- /dev/null
+++ b/core/java/android/text/LangId.java
@@ -0,0 +1,60 @@
+/*
+ * 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 android.text;
+
+/**
+ *  Java wrapper for LangId native library interface.
+ *  This class is used to detect languages in text.
+ *  @hide
+ */
+public final class LangId {
+    // TODO: Move this to android.view.textclassifier and make it package-private.
+    // We'll have to update the native library code to do this.
+
+    static {
+        System.loadLibrary("smart-selection_jni");
+    }
+
+    private final long mModelPtr;
+
+    /**
+     * Creates a new instance of LangId predictor, using the provided model image.
+     */
+    public LangId(int fd) {
+        mModelPtr = nativeNew(fd);
+    }
+
+    /**
+     * Detects the language for given text.
+     */
+    public String findLanguage(String text) {
+        return nativeFindLanguage(mModelPtr, text);
+    }
+
+    /**
+     * Frees up the allocated memory.
+     */
+    public void close() {
+        nativeClose(mModelPtr);
+    }
+
+    private static native long nativeNew(int fd);
+
+    private static native String nativeFindLanguage(long context, String text);
+
+    private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/SmartSelection.java b/core/java/android/text/SmartSelection.java
new file mode 100644
index 0000000..97ef514
--- /dev/null
+++ b/core/java/android/text/SmartSelection.java
@@ -0,0 +1,84 @@
+/*
+ * 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 android.text;
+
+/**
+ *  Java wrapper for SmartSelection native library interface.
+ *  This library is used for detecting entities in text.
+ *  @hide
+ */
+public final class SmartSelection {
+    // TODO: Move this to android.view.textclassifier and make it package-private.
+    // We'll have to update the native library code to do this.
+
+    static {
+        System.loadLibrary("smart-selection_jni");
+    }
+
+    private final long mCtx;
+
+    /**
+     * Creates a new instance of SmartSelect predictor, using the provided model image,
+     * given as a file descriptor.
+     */
+    public SmartSelection(int fd) {
+        mCtx = nativeNew(fd);
+    }
+
+    /**
+     * Given a string context and current selection, computes the SmartSelection suggestion.
+     *
+     * The begin and end are character indices into the context UTF8 string. selectionBegin is the
+     * character index where the selection begins, and selectionEnd is the index of one character
+     * past the selection span.
+     *
+     * The return value is an array of two ints: suggested selection beginning and end, with the
+     * same semantics as the input selectionBeginning and selectionEnd.
+     */
+    public int[] suggest(String context, int selectionBegin, int selectionEnd) {
+        return nativeSuggest(mCtx, context, selectionBegin, selectionEnd);
+    }
+
+    /**
+     * Given a string context and current selection, classifies the type of the selected text.
+     *
+     * The begin and end params are character indices in the context string.
+     *
+     * Returns the type of the selection, e.g. "email", "address", "phone".
+     */
+    public String classifyText(String context, int selectionBegin, int selectionEnd) {
+        return nativeClassifyText(mCtx, context, selectionBegin, selectionEnd);
+    }
+
+    /**
+     * Frees up the allocated memory.
+     */
+    public void close() {
+        nativeClose(mCtx);
+    }
+
+    private static native long nativeNew(int fd);
+
+    private static native int[] nativeSuggest(
+            long context, String text, int selectionBegin, int selectionEnd);
+
+    private static native String nativeClassifyText(
+            long context, String text, int selectionBegin, int selectionEnd);
+
+    private static native void nativeClose(long context);
+}
+
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 186d96b..5f01f7b 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -21,6 +21,7 @@
 import android.graphics.Paint;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -73,8 +74,6 @@
         mSpanFlags = EmptyArray.INT;
         mSpanMax = EmptyArray.INT;
         mSpanOrder = EmptyArray.INT;
-        mPrioSortBuffer = EmptyArray.INT;
-        mOrderSortBuffer = EmptyArray.INT;
 
         if (text instanceof Spanned) {
             Spanned sp = (Spanned) text;
@@ -856,14 +855,14 @@
      * @param queryStart Start index.
      * @param queryEnd End index.
      * @param kind Class type to search for.
-     * @param sort If true the results are sorted by the insertion order.
+     * @param sortByInsertionOrder If true the results are sorted by the insertion order.
      * @param <T>
      * @return Array of the spans. Empty array if no results are found.
      *
      * @hide
      */
     public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
-                                 boolean sort) {
+            boolean sortByInsertionOrder) {
         if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
         if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
         int count = countSpans(queryStart, queryEnd, kind, treeRoot());
@@ -873,13 +872,15 @@
 
         // Safe conversion, but requires a suppressWarning
         T[] ret = (T[]) Array.newInstance(kind, count);
-        if (sort) {
-            mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count);
-            mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count);
+        final int[] prioSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
+        final int[] orderSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT;
+        getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, prioSortBuffer,
+                orderSortBuffer, 0, sortByInsertionOrder);
+        if (sortByInsertionOrder) {
+            sort(ret, prioSortBuffer, orderSortBuffer);
+            recycle(prioSortBuffer);
+            recycle(orderSortBuffer);
         }
-        getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer,
-                mOrderSortBuffer, 0, sort);
-        if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer);
         return ret;
     }
 
@@ -992,15 +993,63 @@
     }
 
     /**
+     * Obtain a temporary sort buffer.
+     *
+     * @param elementCount the size of the int[] to be returned
+     * @return an int[] with elementCount length
+     */
+    private static int[] obtain(final int elementCount) {
+        int[] result = null;
+        synchronized (sCachedIntBuffer) {
+            // try finding a tmp buffer with length of at least elementCount
+            // if not get the first available one
+            int candidateIndex = -1;
+            for (int i = sCachedIntBuffer.length - 1; i >= 0; i--) {
+                if (sCachedIntBuffer[i] != null) {
+                    if (sCachedIntBuffer[i].length >= elementCount) {
+                        candidateIndex = i;
+                        break;
+                    } else if (candidateIndex == -1) {
+                        candidateIndex = i;
+                    }
+                }
+            }
+
+            if (candidateIndex != -1) {
+                result = sCachedIntBuffer[candidateIndex];
+                sCachedIntBuffer[candidateIndex] = null;
+            }
+        }
+        result = checkSortBuffer(result, elementCount);
+        return result;
+    }
+
+    /**
+     * Recycle sort buffer.
+     *
+     * @param buffer buffer to be recycled
+     */
+    private static void recycle(int[] buffer) {
+        synchronized (sCachedIntBuffer) {
+            for (int i = 0; i < sCachedIntBuffer.length; i++) {
+                if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) {
+                    sCachedIntBuffer[i] = buffer;
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
      * Check the size of the buffer and grow if required.
      *
-     * @param buffer Buffer to be checked.
-     * @param size Required size.
+     * @param buffer buffer to be checked.
+     * @param size   required size.
      * @return Same buffer instance if the current size is greater than required size. Otherwise a
      * new instance is created and returned.
      */
-    private final int[] checkSortBuffer(int[] buffer, int size) {
-        if(size > buffer.length) {
+    private static int[] checkSortBuffer(int[] buffer, int size) {
+        if (buffer == null || size > buffer.length) {
             return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
         }
         return buffer;
@@ -1025,16 +1074,19 @@
         }
 
         for (int i = size - 1; i > 0; i--) {
-            T v = array[0];
-            int prio = priority[0];
-            int insertOrder = insertionOrder[0];
+            final T tmpSpan =  array[0];
             array[0] = array[i];
+            array[i] = tmpSpan;
+
+            final int tmpPriority =  priority[0];
             priority[0] = priority[i];
+            priority[i] = tmpPriority;
+
+            final int tmpOrder =  insertionOrder[0];
             insertionOrder[0] = insertionOrder[i];
+            insertionOrder[i] = tmpOrder;
+
             siftDown(0, array, i, priority, insertionOrder);
-            array[i] = v;
-            priority[i] = prio;
-            insertionOrder[i] = insertOrder;
         }
     }
 
@@ -1050,10 +1102,6 @@
      */
     private final <T> void siftDown(int index, T[] array, int size, int[] priority,
                                     int[] insertionOrder) {
-        T v = array[index];
-        int prio = priority[index];
-        int insertOrder = insertionOrder[index];
-
         int left = 2 * index + 1;
         while (left < size) {
             if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
@@ -1062,15 +1110,22 @@
             if (compareSpans(index, left, priority, insertionOrder) >= 0) {
                 break;
             }
+
+            final T tmpSpan =  array[index];
             array[index] = array[left];
+            array[left] = tmpSpan;
+
+            final int tmpPriority =  priority[index];
             priority[index] = priority[left];
+            priority[left] = tmpPriority;
+
+            final int tmpOrder =  insertionOrder[index];
             insertionOrder[index] = insertionOrder[left];
+            insertionOrder[left] = tmpOrder;
+
             index = left;
             left = 2 * index + 1;
         }
-        array[index] = v;
-        priority[index] = prio;
-        insertionOrder[index] = insertOrder;
     }
 
     /**
@@ -1704,6 +1759,10 @@
     }
 
     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+
+    @GuardedBy("sCachedIntBuffer")
+    private static final int[][] sCachedIntBuffer = new int[6][0];
+
     private InputFilter[] mFilters = NO_FILTERS;
 
     private char[] mText;
@@ -1717,8 +1776,6 @@
     private int[] mSpanFlags;
     private int[] mSpanOrder;  // store the order of span insertion
     private int mSpanInsertCount;  // counter for the span insertion
-    private int[] mPrioSortBuffer;  // buffer used to sort getSpans result
-    private int[] mOrderSortBuffer;  // buffer used to sort getSpans result
 
     private int mSpanCount;
     private IdentityHashMap<Object, Integer> mIndexOfSpan;
diff --git a/core/java/android/text/TextAssistant.java b/core/java/android/text/TextAssistant.java
deleted file mode 100644
index b044981..0000000
--- a/core/java/android/text/TextAssistant.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-/**
- * Interface for providing text assistant features.
- */
-public interface TextAssistant {
-
-    /**
-     * NO_OP TextAssistant. This will act as the default TextAssistant until we implement a
-     * TextClassificationManager.
-     * @hide
-     */
-    TextAssistant NO_OP = new TextAssistant() {
-
-        private final TextSelection mTextSelection = new TextSelection();
-
-        @Override
-        public TextSelection suggestSelection(
-                CharSequence text, int selectionStartIndex, int selectionEndIndex) {
-            mTextSelection.mStartIndex = selectionStartIndex;
-            mTextSelection.mEndIndex = selectionEndIndex;
-            return mTextSelection;
-        }
-
-        @Override
-        public void addLinks(Spannable text, int linkMask) {}
-    };
-
-    /**
-     * Returns suggested text selection indices, recognized types and their associated confidence
-     * scores. The selections are ordered from highest to lowest scoring.
-     */
-    TextSelection suggestSelection(
-            CharSequence text, int selectionStartIndex, int selectionEndIndex);
-
-    /**
-     * Adds assistance clickable spans to the provided text.
-     */
-    void addLinks(Spannable text, int linkMask);
-}
diff --git a/core/java/android/text/TextClassification.java b/core/java/android/text/TextClassification.java
deleted file mode 100644
index bb226da..0000000
--- a/core/java/android/text/TextClassification.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Information about entities that a specific piece of text is classified as.
- */
-public class TextClassification {
-
-    /** @hide */
-    public static final TextClassification NO_OP = new TextClassification();
-
-    private Map<String, Float> mTypeConfidence = Collections.unmodifiableMap(Collections.EMPTY_MAP);
-
-    /**
-     * Returns a map of text classification types to their respective confidence scores.
-     * The scores range from 0 (low confidence) to 1 (high confidence). The items are ordered from
-     * high scoring items to low scoring items.
-     */
-    public Map<String, Float> getTypeConfidence() {
-        return mTypeConfidence;
-    }
-}
diff --git a/core/java/android/text/TextClassificationManager.java b/core/java/android/text/TextClassificationManager.java
deleted file mode 100644
index d4548f0..0000000
--- a/core/java/android/text/TextClassificationManager.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.NonNull;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Interface to the text classification service.
- * This class uses machine learning techniques to infer things about text.
- * Unless otherwise stated, methods of this class are blocking operations and should most likely not
- * be called on the UI thread.
- *
- * <p> You do not instantiate this class directly; instead, retrieve it through
- * {@link android.content.Context#getSystemService}.
- *
- * The TextClassificationManager serves as the default TextAssistant if none has been set.
- * @see android.app.Activity#setTextAssistant(TextAssistant).
- */
-public final class TextClassificationManager implements TextAssistant {
-    // TODO: Consider not making this class implement TextAssistant.
-
-    /** @hide */
-    public TextClassificationManager() {}
-
-    /**
-     * Returns information containing languages that were detected in the provided text.
-     * This is a blocking operation and should most likely not be called on the UI thread.
-     */
-    public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
-        // TODO: Implement this using the cld3 library.
-        return Collections.emptyList();
-    }
-
-    @Override
-    public TextSelection suggestSelection(
-            @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) {
-        // TODO: Implement.
-        return TextAssistant.NO_OP.suggestSelection(text, selectionStartIndex, selectionEndIndex);
-    }
-
-    @Override
-    public void addLinks(@NonNull Spannable text, int linkMask) {
-        // TODO: Implement.
-    }
-}
diff --git a/core/java/android/text/TextLanguage.java b/core/java/android/text/TextLanguage.java
deleted file mode 100644
index eb834f1..0000000
--- a/core/java/android/text/TextLanguage.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Specifies detected languages for a section of text indicated by a start and end index.
- */
-public final class TextLanguage {
-
-    private final int mStartIndex;
-    private final int mEndIndex;
-    private final Map<String, Float> mLanguageConfidence;
-
-    /**
-     * Initializes a TextLanguage object.
-     *
-     * @param startIndex the start index of the detected languages in the text provided to generate
-     *      this object.
-     * @param endIndex the end index of the detected languages in the text provided to generate this
-     *      object.
-     * @param languageConfidence a map of detected language to confidence score. The language string
-     *      is a BCP-47 language tag.
-     * @throws NullPointerException if languageConfidence is null or contains a null key or value.
-     */
-    public TextLanguage(int startIndex, int endIndex,
-            @NonNull Map<String, Float> languageConfidence) {
-        mStartIndex = startIndex;
-        mEndIndex = endIndex;
-
-        Map<String, Float> map = new LinkedHashMap<>();
-        Preconditions.checkNotNull(languageConfidence).entrySet().stream()
-                .sorted(Map.Entry.comparingByValue())
-                .forEach(entry -> map.put(
-                        Preconditions.checkNotNull(entry.getKey()),
-                        Preconditions.checkNotNull(entry.getValue())));
-        mLanguageConfidence = Collections.unmodifiableMap(map);
-    }
-
-    /**
-     * Returns the start index of the detected languages in the text provided to generate this
-     * object.
-     */
-    public int getStartIndex() {
-        return mStartIndex;
-    }
-
-    /**
-     * Returns the end index of the detected languages in the text provided to generate this object.
-     */
-    public int getEndIndex() {
-        return mEndIndex;
-    }
-
-    /**
-     * Returns an unmodifiable map of detected language to confidence score. The map entries are
-     * ordered from high confidence score (1) to low confidence score (0). The language string is a
-     * BCP-47 language tag.
-     */
-    @NonNull
-    public Map<String, Float> getLanguageConfidence() {
-        return mLanguageConfidence;
-    }
-}
diff --git a/core/java/android/text/TextSelection.java b/core/java/android/text/TextSelection.java
deleted file mode 100644
index 9400458..0000000
--- a/core/java/android/text/TextSelection.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-/**
- * Text selection information.
- */
-public class TextSelection {
-
-    /** @hide */
-    int mStartIndex;
-    /** @hide */
-    int mEndIndex;
-
-    private TextClassification mTextClassification = TextClassification.NO_OP;
-
-    /**
-     * Returns the start index of the text selection.
-     */
-    public int getSelectionStartIndex() {
-        return mStartIndex;
-    }
-
-    /**
-     * Returns the end index of the text selection.
-     */
-    public int getSelectionEndIndex() {
-        return mEndIndex;
-    }
-
-    /**
-     * Returns information about what the text selection is classified as.
-     */
-    public TextClassification getTextClassification() {
-        return mTextClassification;
-    }
-}
diff --git a/core/java/android/util/ByteStringUtils.java b/core/java/android/util/ByteStringUtils.java
index 7103e6d..333208d 100644
--- a/core/java/android/util/ByteStringUtils.java
+++ b/core/java/android/util/ByteStringUtils.java
@@ -33,7 +33,7 @@
    * @param bytes Byte array to encode.
    * @return Hex encoded string representation of bytes.
    */
-  public static String toString(byte[] bytes) {
+  public static String toHexString(byte[] bytes) {
     if (bytes == null || bytes.length == 0 || bytes.length % 2 != 0) {
       return null;
     }
@@ -55,7 +55,7 @@
    * @param str Hex encoded string to decode.
    * @return Decoded byte array representation of str.
    */
-  public static byte[] toByteArray(String str) {
+  public static byte[] fromHexToByteArray(String str) {
     if (str == null || str.length() == 0 || str.length() % 2 != 0) {
       return null;
     }
diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java
index e4c025d..be531ff 100644
--- a/core/java/android/util/KeyValueListParser.java
+++ b/core/java/android/util/KeyValueListParser.java
@@ -129,4 +129,22 @@
         }
         return def;
     }
+
+    /**
+     * Get the value for key as a boolean.
+     * @param key The key to lookup.
+     * @param def The value to return if the key was not found.
+     * @return the string value associated with the key.
+     */
+    public boolean getBoolean(String key, boolean def) {
+        String value = mValues.get(key);
+        if (value != null) {
+            try {
+                return Boolean.parseBoolean(value);
+            } catch (NumberFormatException e) {
+                // fallthrough
+            }
+        }
+        return def;
+    }
 }
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 3181979..0fe56f6 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -80,6 +80,6 @@
 
         messageDigest.update(data);
 
-        return ByteStringUtils.toString(messageDigest.digest());
+        return ByteStringUtils.toHexString(messageDigest.digest());
     }
 }
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 37d6757..0a294ab 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -19,7 +19,6 @@
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.os.SystemClock;
-import android.text.format.DateUtils;
 
 import com.android.internal.util.XmlUtils;
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 19edb5c..c789f8c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -135,9 +135,6 @@
     // ids that were affected by the update, ActivityManager should resize these stacks.
     int[] setNewDisplayOverrideConfiguration(in Configuration overrideConfig, int displayId);
 
-    // Retrieves the new bounds after the configuration update evaluated by window manager.
-    Rect getBoundsForNewConfiguration(int stackId);
-
     void startFreezingScreen(int exitAnim, int enterAnim);
     void stopFreezingScreen();
 
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index fc66697..ea6e63c 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -139,9 +139,6 @@
                 RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024);
     }
 
-    // Note: written by native when display lists are detached
-    private boolean mValid;
-
     // Do not access directly unless you are ThreadedRenderer
     final long mNativeRenderNode;
     private final View mOwningView;
@@ -233,7 +230,6 @@
         long displayList = canvas.finishRecording();
         nSetDisplayList(mNativeRenderNode, displayList);
         canvas.recycle();
-        mValid = true;
     }
 
     /**
@@ -242,10 +238,7 @@
      * obsolete resources after related resources are gone.
      */
     public void discardDisplayList() {
-        if (!mValid) return;
-
         nSetDisplayList(mNativeRenderNode, 0);
-        mValid = false;
     }
 
     /**
@@ -254,10 +247,12 @@
      *
      * @return boolean true if the display list is able to be replayed, false otherwise.
      */
-    public boolean isValid() { return mValid; }
+    public boolean isValid() {
+        return nIsValid(mNativeRenderNode);
+    }
 
     long getNativeDisplayList() {
-        if (!mValid) {
+        if (!isValid()) {
             throw new IllegalStateException("The display list is not valid.");
         }
         return mNativeRenderNode;
@@ -827,8 +822,7 @@
     // Regular JNI methods
     ///////////////////////////////////////////////////////////////////////////
 
-    // Intentionally not static because it acquires a reference to 'this'
-    private native long nCreate(String name);
+    private static native long nCreate(String name);
 
     private static native long nGetNativeFinalizer();
     private static native void nOutput(long renderNode);
@@ -853,6 +847,9 @@
     // @CriticalNative methods
     ///////////////////////////////////////////////////////////////////////////
 
+    @CriticalNative
+    private static native boolean nIsValid(long renderNode);
+
     // Matrix
 
     @CriticalNative
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index b0826a8..4ceb236 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -360,7 +360,6 @@
     void destroy() {
         mInitialized = false;
         updateEnabledState(null);
-        mRootNode.discardDisplayList();
         nDestroy(mNativeProxy, mRootNode.mNativeRenderNode);
     }
 
@@ -492,7 +491,6 @@
      */
     void destroyHardwareResources(View view) {
         destroyResources(view);
-        mRootNode.discardDisplayList();
         nDestroyHardwareResources(mNativeProxy);
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 13555f4..123e368 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -857,22 +857,39 @@
      */
     static boolean sCascadedDragDrop;
 
-    /**
-     * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
-     * calling setFlags.
-     */
-    private static final int NOT_FOCUSABLE = 0x00000000;
+    /** @hide */
+    @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Focusable {}
 
     /**
-     * This view wants keystrokes. Use with TAKES_FOCUS_MASK when calling
-     * setFlags.
+     * This view does not want keystrokes.
+     * <p>
+     * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+     * android:focusable}.
      */
-    private static final int FOCUSABLE = 0x00000001;
+    public static final int NOT_FOCUSABLE = 0x00000000;
+
+    /**
+     * This view wants keystrokes.
+     * <p>
+     * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+     * android:focusable}.
+     */
+    public static final int FOCUSABLE = 0x00000001;
+
+    /**
+     * This view determines focusability automatically. This is the default.
+     * <p>
+     * Use with {@link #setFocusable(int)} and <a href="#attr_android:focusable">{@code
+     * android:focusable}.
+     */
+    public static final int FOCUSABLE_AUTO = 0x00000010;
 
     /**
      * Mask for use with setFlags indicating bits used for focus.
      */
-    private static final int FOCUSABLE_MASK = 0x00000001;
+    private static final int FOCUSABLE_MASK = 0x00000011;
 
     /**
      * This view will adjust its padding to fit sytem windows (e.g. status bar)
@@ -1748,6 +1765,12 @@
      */
     int mAccessibilityViewId = NO_ID;
 
+    /**
+     * The stable ID of this view for auto-fill purposes.
+     */
+    private int mAutoFillId = NO_ID;
+
+
     private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
 
     SendViewStateChangedAccessibilityEvent mSendViewStateChangedAccessibilityEvent;
@@ -4136,7 +4159,7 @@
     public View(Context context) {
         mContext = context;
         mResources = context != null ? context.getResources() : null;
-        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
+        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
         // Set some flags defaults
         mPrivateFlags2 =
                 (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
@@ -4322,6 +4345,10 @@
 
         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
 
+        // Set default values.
+        viewFlagValues |= FOCUSABLE_AUTO;
+        viewFlagMasks |= FOCUSABLE_AUTO;
+
         final int N = a.getIndexCount();
         for (int i = 0; i < N; i++) {
             int attr = a.getIndex(i);
@@ -4434,8 +4461,8 @@
                     }
                     break;
                 case com.android.internal.R.styleable.View_focusable:
-                    if (a.getBoolean(attr, false)) {
-                        viewFlagValues |= FOCUSABLE;
+                    viewFlagValues = (viewFlagValues & ~FOCUSABLE_MASK) | getFocusableAttribute(a);
+                    if ((viewFlagValues & FOCUSABLE_AUTO) == 0) {
                         viewFlagMasks |= FOCUSABLE_MASK;
                     }
                     break;
@@ -5006,7 +5033,7 @@
             case GONE: out.append('G'); break;
             default: out.append('.'); break;
         }
-        out.append((mViewFlags&FOCUSABLE_MASK) == FOCUSABLE ? 'F' : '.');
+        out.append((mViewFlags & FOCUSABLE) == FOCUSABLE ? 'F' : '.');
         out.append((mViewFlags&ENABLED_MASK) == ENABLED ? 'E' : '.');
         out.append((mViewFlags&DRAW_MASK) == WILL_NOT_DRAW ? '.' : 'D');
         out.append((mViewFlags&SCROLLBARS_HORIZONTAL) != 0 ? 'H' : '.');
@@ -6911,8 +6938,8 @@
         if (forAutoFill) {
             // The auto-fill id needs to be unique, but its value doesn't matter, so it's better to
             // reuse the accessibility id to save space.
-            structure.setAutoFillId(getAccessibilityViewId());
-
+            mAutoFillId = getAccessibilityViewId();
+            structure.setAutoFillId(mAutoFillId);
             structure.setAutoFillType(getAutoFillType());
         }
 
@@ -7042,7 +7069,7 @@
     }
 
     /**
-     * Describes the auto-fill type that should be used on callas to
+     * Describes the auto-fill type that should be used on calls to
      * {@link #autoFill(AutoFillValue)} and
      * {@link VirtualViewDelegate#autoFill(int, AutoFillValue)}.
      *
@@ -7539,6 +7566,20 @@
     }
 
     /**
+     * Gets the unique identifier of this view for auto-fill purposes.
+     *
+     * <p>It's only set after {@link #onProvideAutoFillStructure(ViewStructure, int)} is called.
+     *
+     * @return The view autofill id or {@link #NO_ID} if
+     * {@link #onProvideAutoFillStructure(ViewStructure, int)}  was not called yet.
+     *
+     * @hide
+     */
+    public int getAutoFillViewId() {
+        return mAutoFillId;
+    }
+
+    /**
      * Gets the unique identifier of the window in which this View reseides.
      *
      * @return The window accessibility id.
@@ -8453,20 +8494,39 @@
 
     /**
      * Set whether this view can receive the focus.
-     *
+     * <p>
      * Setting this to false will also ensure that this view is not focusable
      * in touch mode.
      *
      * @param focusable If true, this view can receive the focus.
      *
      * @see #setFocusableInTouchMode(boolean)
+     * @see #setFocusable(int)
      * @attr ref android.R.styleable#View_focusable
      */
     public void setFocusable(boolean focusable) {
-        if (!focusable) {
+        setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE);
+    }
+
+    /**
+     * Sets whether this view can receive focus.
+     * <p>
+     * Setting this to {@link #FOCUSABLE_AUTO} tells the framework to determine focusability
+     * automatically based on the view's interactivity. This is the default.
+     * <p>
+     * Setting this to NOT_FOCUSABLE will ensure that this view is also not focusable
+     * in touch mode.
+     *
+     * @param focusable One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE},
+     *                  or {@link #FOCUSABLE_AUTO}.
+     * @see #setFocusableInTouchMode(boolean)
+     * @attr ref android.R.styleable#View_focusable
+     */
+    public void setFocusable(@Focusable int focusable) {
+        if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
             setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
         }
-        setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK);
+        setFlags(focusable, FOCUSABLE_MASK);
     }
 
     /**
@@ -9056,14 +9116,29 @@
 
 
     /**
-     * Returns whether this View is able to take focus.
+     * Returns whether this View is currently able to take focus.
      *
      * @return True if this view can take focus, or false otherwise.
-     * @attr ref android.R.styleable#View_focusable
      */
     @ViewDebug.ExportedProperty(category = "focus")
     public final boolean isFocusable() {
-        return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);
+        return FOCUSABLE == (mViewFlags & FOCUSABLE);
+    }
+
+    /**
+     * Returns the focusable setting for this view.
+     *
+     * @return One of {@link #NOT_FOCUSABLE}, {@link #FOCUSABLE}, or {@link #FOCUSABLE_AUTO}.
+     * @attr ref android.R.styleable#View_focusable
+     */
+    @ViewDebug.ExportedProperty(mapping = {
+            @ViewDebug.IntToString(from = NOT_FOCUSABLE, to = "NOT_FOCUSABLE"),
+            @ViewDebug.IntToString(from = FOCUSABLE, to = "FOCUSABLE"),
+            @ViewDebug.IntToString(from = FOCUSABLE_AUTO, to = "FOCUSABLE_AUTO")
+            })
+    @Focusable
+    public int getFocusable() {
+        return (mViewFlags & FOCUSABLE_AUTO) > 0 ? FOCUSABLE_AUTO : mViewFlags & FOCUSABLE;
     }
 
     /**
@@ -9615,8 +9690,8 @@
 
     private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
         // need to be focusable
-        if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
-                (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+        if ((mViewFlags & FOCUSABLE) != FOCUSABLE
+                || (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
             return false;
         }
 
@@ -11970,14 +12045,27 @@
         }
         int privateFlags = mPrivateFlags;
 
+        // If focusable is auto, update the FOCUSABLE bit.
+        if (((mViewFlags & FOCUSABLE_AUTO) != 0)
+                && (changed & (FOCUSABLE_MASK | CLICKABLE | FOCUSABLE_IN_TOUCH_MODE)) != 0) {
+            int newFocus = NOT_FOCUSABLE;
+            if ((mViewFlags & (CLICKABLE | FOCUSABLE_IN_TOUCH_MODE)) != 0) {
+                newFocus = FOCUSABLE;
+            } else {
+                mViewFlags = (mViewFlags & ~FOCUSABLE_IN_TOUCH_MODE);
+            }
+            mViewFlags = (mViewFlags & ~FOCUSABLE) | newFocus;
+            int focusChanged = (old & FOCUSABLE) ^ (newFocus & FOCUSABLE);
+            changed = (changed & ~FOCUSABLE) | focusChanged;
+        }
+
         /* Check if the FOCUSABLE bit has changed */
-        if (((changed & FOCUSABLE_MASK) != 0) &&
-                ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) {
-            if (((old & FOCUSABLE_MASK) == FOCUSABLE)
+        if (((changed & FOCUSABLE) != 0) && ((privateFlags & PFLAG_HAS_BOUNDS) != 0)) {
+            if (((old & FOCUSABLE) == FOCUSABLE)
                     && ((privateFlags & PFLAG_FOCUSED) != 0)) {
                 /* Give up focus if we are no longer focusable */
                 clearFocus();
-            } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE)
+            } else if (((old & FOCUSABLE) == NOT_FOCUSABLE)
                     && ((privateFlags & PFLAG_FOCUSED) == 0)) {
                 /*
                  * Tell the view system that we are now available to take focus
@@ -12120,7 +12208,7 @@
         }
 
         if (accessibilityEnabled) {
-            if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0
+            if ((changed & FOCUSABLE) != 0 || (changed & VISIBILITY_MASK) != 0
                     || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
                     || (changed & CONTEXT_CLICKABLE) != 0) {
                 if (oldIncludeForAccessibility != includeForAccessibility()) {
@@ -16525,11 +16613,6 @@
      */
     @CallSuper
     protected void destroyHardwareResources() {
-        // Although the Layer will be destroyed by RenderNode, we want to release
-        // the staging display list, which is also a signal to RenderNode that it's
-        // safe to free its copy of the display list as it knows that we will
-        // push an updated DisplayList if we try to draw again
-        resetDisplayList();
         if (mOverlay != null) {
             mOverlay.getOverlayView().destroyHardwareResources();
         }
@@ -16707,7 +16790,6 @@
 
     private void resetDisplayList() {
         mRenderNode.discardDisplayList();
-
         if (mBackgroundRenderNode != null) {
             mBackgroundRenderNode.discardDisplayList();
         }
@@ -18049,7 +18131,7 @@
     private static String printFlags(int flags) {
         String output = "";
         int numFlags = 0;
-        if ((flags & FOCUSABLE_MASK) == FOCUSABLE) {
+        if ((flags & FOCUSABLE) == FOCUSABLE) {
             output += "TAKES_FOCUS";
             numFlags++;
         }
@@ -24695,6 +24777,19 @@
                 ViewConfiguration.getLongPressTooltipHideTimeout());
     }
 
+    private int getFocusableAttribute(TypedArray attributes) {
+        TypedValue val = new TypedValue();
+        if (attributes.getValue(com.android.internal.R.styleable.View_focusable, val)) {
+            if (val.type == TypedValue.TYPE_INT_BOOLEAN) {
+                return (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE);
+            } else {
+                return val.data;
+            }
+        } else {
+            return FOCUSABLE_AUTO;
+        }
+    }
+
     /**
      * @return The content view of the tooltip popup currently being shown, or null if the tooltip
      * is not showing.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 480741e..f8a1c6b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3433,16 +3433,6 @@
         super.dispatchDetachedFromWindow();
     }
 
-    /** @hide */
-    @Override
-    protected void destroyHardwareResources() {
-        super.destroyHardwareResources();
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            getChildAt(i).destroyHardwareResources();
-        }
-    }
-
     /**
      * @hide
      */
@@ -4607,6 +4597,16 @@
         clearCachedLayoutMode();
     }
 
+    /** @hide */
+    @Override
+    protected void destroyHardwareResources() {
+        super.destroyHardwareResources();
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).destroyHardwareResources();
+        }
+    }
+
     /**
      * Adds a view during layout. This is useful if in your onLayout() method,
      * you need to add more views (as does the list view for example).
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3cbe82e..6987e09 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4421,14 +4421,12 @@
 
             int groupNavigationDirection = 0;
 
-            if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed()) {
-                final int character =
-                        event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK);
-                if (character == '+') {
+            if (event.getAction() == KeyEvent.ACTION_DOWN
+                    && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
+                if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
                     groupNavigationDirection = View.FOCUS_FORWARD;
-                }
-
-                if (character == '_') {
+                } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
+                        KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
                     groupNavigationDirection = View.FOCUS_BACKWARD;
                 }
             }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8bc988d..e2bdd97 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -27,6 +27,7 @@
 import android.annotation.StyleRes;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -1128,6 +1129,28 @@
     }
 
     /**
+     * <p>Set the color mode of the window. Setting the color mode might
+     * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+     *
+     * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+     * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+     */
+    public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+        final WindowManager.LayoutParams attrs = getAttributes();
+        attrs.setColorMode(colorMode);
+        dispatchWindowAttributesChanged(attrs);
+    }
+
+    /**
+     * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+     * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.
+     */
+    @ActivityInfo.ColorMode
+    public int getColorMode() {
+        return getAttributes().getColorMode();
+    }
+
+    /**
      * Set the amount of dim behind the window when using
      * {@link WindowManager.LayoutParams#FLAG_DIM_BEHIND}.  This overrides
      * the default dim amount of that is selected by the Window based on
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e5a6ebd..bf840e5 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -22,7 +22,6 @@
 import android.app.Presentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -1577,7 +1576,8 @@
 
         /**
          * The desired bitmap format.  May be one of the constants in
-         * {@link android.graphics.PixelFormat}.  Default is OPAQUE.
+         * {@link android.graphics.PixelFormat}. The choice of format
+         * might be overridden by {@link #setColorMode(int)}. Default is OPAQUE.
          */
         public int format;
 
@@ -1833,6 +1833,17 @@
          */
         public long hideTimeoutMilliseconds = -1;
 
+        /**
+         * The color mode requested by this window. The target display may
+         * not be able to honor the request. When the color mode is not set
+         * to {@link ActivityInfo#COLOR_MODE_DEFAULT}, it might override the
+         * pixel format specified in {@link #format}.
+         *
+         * @hide
+         */
+        @ActivityInfo.ColorMode
+        private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+
         public LayoutParams() {
             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
             type = TYPE_APPLICATION;
@@ -1912,6 +1923,31 @@
             preservePreviousSurfaceInsets = preservePrevious;
         }
 
+        /**
+         * <p>Set the color mode of the window. Setting the color mode might
+         * override the window's pixel {@link WindowManager.LayoutParams#format format}.</p>
+         *
+         * <p>The color mode must be one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+         * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or
+         * {@link ActivityInfo#COLOR_MODE_HDR}.</p>
+         *
+         * @see #getColorMode()
+         */
+        public void setColorMode(@ActivityInfo.ColorMode int colorMode) {
+            mColorMode = colorMode;
+        }
+
+        /**
+         * Returns the color mode of the window, one of {@link ActivityInfo#COLOR_MODE_DEFAULT},
+         * {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT} or {@link ActivityInfo#COLOR_MODE_HDR}.
+         *
+         * @see #setColorMode(int)
+         */
+        @ActivityInfo.ColorMode
+        public int getColorMode() {
+            return mColorMode;
+        }
+
         /** @hide */
         @SystemApi
         public final void setUserActivityTimeout(long timeout) {
@@ -2268,9 +2304,11 @@
             sb.append(',');
             sb.append(y);
             sb.append(")(");
-            sb.append((width== MATCH_PARENT ?"fill":(width==WRAP_CONTENT?"wrap":width)));
+            sb.append((width == MATCH_PARENT ? "fill" : (width == WRAP_CONTENT
+                    ? "wrap" : String.valueOf(width))));
             sb.append('x');
-            sb.append((height== MATCH_PARENT ?"fill":(height==WRAP_CONTENT?"wrap":height)));
+            sb.append((height == MATCH_PARENT ? "fill" : (height == WRAP_CONTENT
+                    ? "wrap" : String.valueOf(height))));
             sb.append(")");
             if (horizontalMargin != 0) {
                 sb.append(" hm=");
@@ -2367,6 +2405,7 @@
                 sb.append(" needsMenuKey=");
                 sb.append(needsMenuKey);
             }
+            sb.append(" colorMode=").append(mColorMode);
             sb.append('}');
             return sb.toString();
         }
diff --git a/core/java/android/view/autofill/AutoFillId.java b/core/java/android/view/autofill/AutoFillId.java
index b7b694d..e9c1c3b 100644
--- a/core/java/android/view/autofill/AutoFillId.java
+++ b/core/java/android/view/autofill/AutoFillId.java
@@ -43,6 +43,13 @@
     }
 
     /** @hide */
+    public AutoFillId(int parentId, int virtualChildId) {
+        mVirtual = true;
+        mViewId = parentId;
+        mVirtualId = virtualChildId;
+    }
+
+    /** @hide */
     public int getViewId() {
         return mViewId;
     }
diff --git a/core/java/android/view/autofill/AutoFillManager.java b/core/java/android/view/autofill/AutoFillManager.java
new file mode 100644
index 0000000..cd9842f
--- /dev/null
+++ b/core/java/android/view/autofill/AutoFillManager.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.view.autofill;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.service.autofill.IAutoFillManagerService;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * App entry point to the AutoFill Framework.
+ */
+// TODO(b/33197203): improve this javadoc
+public final class AutoFillManager {
+
+    private static final String TAG = "AutoFillManager";
+    private static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
+
+    /**
+     * Flag used to show the auto-fill UI affordance for a view.
+     */
+    public static final int FLAG_UPDATE_UI_SHOW = 1 << 0;
+
+    /**
+     * Flag used to hide the auto-fill UI affordance for a view.
+     */
+    public static final int FLAG_UPDATE_UI_HIDE = 1 << 1;
+
+    private final IAutoFillManagerService mService;
+
+    /**
+     * @hide
+     */
+    public AutoFillManager(@SuppressWarnings("unused") Context context,
+            IAutoFillManagerService service) {
+        mService = service;
+    }
+
+    /**
+     * Updates the auto-fill bar for a given {@link View}.
+     *
+     * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
+     * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
+     *
+     * @param view view to be updated.
+     * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
+     * {@link #FLAG_UPDATE_UI_HIDE}.
+     */
+    public void updateAutoFillInput(View view, int flags) {
+        if (DEBUG) {
+            Log.v(TAG, "updateAutoFillInput(" + view.getAutoFillViewId() + "): flags=" + flags);
+        }
+
+        updateAutoFillInput(view, false, View.NO_ID, null, flags);
+    }
+
+    /**
+     * Updates the auto-fill bar for a virtual child of a given {@link View}.
+     *
+     * <b>Typically called twice, with different flags ({@link #FLAG_UPDATE_UI_SHOW} and
+     * {@link #FLAG_UPDATE_UI_HIDE} respectively), as the user "entered" and "exited" a view.
+     *
+     * @param parent parent view.
+     * @param childId id identifying the virtual child inside the parent view.
+     * @param boundaries boundaries of the child (inside the parent; could be {@code null} when
+     * flag is {@link #FLAG_UPDATE_UI_HIDE}.
+     * @param flags either {@link #FLAG_UPDATE_UI_SHOW} or
+     * {@link #FLAG_UPDATE_UI_HIDE}.
+     */
+    public void updateAutoFillInput(View parent, int childId, @Nullable Rect boundaries,
+            int flags) {
+        if (DEBUG) {
+            Log.v(TAG, "updateAutoFillInput(" + parent.getAutoFillViewId() + ", " + childId
+                    + "): boundaries=" + boundaries + ", flags=" + flags);
+        }
+        updateAutoFillInput(parent, true, childId, boundaries, flags);
+    }
+
+    private void updateAutoFillInput(View view, boolean virtual, int childId, Rect boundaries,
+            int flags) {
+        if ((flags & FLAG_UPDATE_UI_SHOW) != 0) {
+            final int viewId = view.getAutoFillViewId();
+            final AutoFillId id = virtual
+                    ? new AutoFillId(viewId, childId)
+                    : new AutoFillId(viewId);
+            showAutoFillInput(id, boundaries);
+            return;
+        }
+        // TODO(b/33197203): handle FLAG_UPDATE_UI_HIDE
+    }
+
+    private void showAutoFillInput(AutoFillId id, Rect boundaries) {
+        final int autoFillViewId = id.getViewId();
+        /*
+         * TODO(b/33197203): currently SHOW_AUTO_FILL_BAR is only set once per activity (i.e, when
+         * the view does not have an auto-fill id), but it should be called again for views that
+         * were not part of the initial auto-fill dataset returned by the service. For example:
+         *
+         * 1.Activity has 4 fields, `first_name`, `last_name`, and `address`.
+         * 2.User taps `first_name`.
+         * 3.Service returns a dataset with ids for `first_name` and `last_name`.
+         * 4.When user taps `first_name` (again) or `last_name`, flag should not have
+         *   SHOW_AUTO_FILL_BAR set, but when user taps `address`, it should (since that field was
+         *   not part of the initial dataset).
+         *
+         * Similarly, once the activity is auto-filled, the flag logic should be reset (so if the
+         * user taps the view again, a new auto-fill request is made)
+         */
+        if (autoFillViewId != View.NO_ID) {
+            return;
+        }
+
+        try {
+            mService.showAutoFillInput(id, boundaries);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/view/autofill/Dataset.java b/core/java/android/view/autofill/Dataset.java
index a73eb774..b11eecc 100644
--- a/core/java/android/view/autofill/Dataset.java
+++ b/core/java/android/view/autofill/Dataset.java
@@ -19,12 +19,13 @@
 import static android.view.autofill.Helper.DEBUG;
 import static android.view.autofill.Helper.append;
 
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.assist.AssistStructure.ViewNode;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.service.autofill.AutoFillService;
 
 import com.android.internal.util.Preconditions;
 
@@ -50,12 +51,20 @@
     private final CharSequence mName;
     private final ArrayList<DatasetField> mFields;
     private final Bundle mExtras;
+    private final int mFlags;
+    private final boolean mRequiresAuth;
+    private final boolean mHasCryptoObject;
+    private final long mCryptoOpId;
 
     private Dataset(Dataset.Builder builder) {
         mName = builder.mName;
         // TODO(b/33197203): make an immutable copy of mFields?
         mFields = builder.mFields;
         mExtras = builder.mExtras;
+        mFlags = builder.mFlags;
+        mRequiresAuth = builder.mRequiresAuth;
+        mHasCryptoObject = builder.mHasCryptoObject;
+        mCryptoOpId = builder.mCryptoOpId;
     }
 
     /** @hide */
@@ -73,13 +82,41 @@
         return mExtras;
     }
 
+    /** @hide */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /** @hide */
+    public boolean isAuthRequired() {
+        return mRequiresAuth;
+    }
+
+    /** @hide */
+    public boolean isEmpty() {
+        return mFields.isEmpty();
+    }
+
+    /** @hide */
+    public boolean hasCryptoObject() {
+        return mHasCryptoObject;
+    }
+
+    /** @hide */
+    public long getCryptoObjectOpId() {
+        return mCryptoOpId;
+    }
+
     @Override
     public String toString() {
         if (!DEBUG) return super.toString();
 
         final StringBuilder builder = new StringBuilder("Dataset [name=").append(mName)
                 .append(", fields=").append(mFields).append(", extras=");
-        append(builder, mExtras);
+        append(builder, mExtras)
+            .append(", flags=").append(mFlags)
+            .append(", requiresAuth: ").append(mRequiresAuth)
+            .append(", hasCrypto: ").append(mHasCryptoObject);
         return builder.append(']').toString();
     }
 
@@ -90,6 +127,10 @@
         private CharSequence mName;
         private final ArrayList<DatasetField> mFields = new ArrayList<>();
         private Bundle mExtras;
+        private int mFlags;
+        private boolean mRequiresAuth;
+        private boolean mHasCryptoObject;
+        private long mCryptoOpId;
 
         /**
          * Creates a new builder.
@@ -103,6 +144,125 @@
         }
 
         /**
+         * Requires dataset authentication through the {@link
+         * android.service.autofill.AutoFillService} before auto-filling the activity with this
+         * dataset.
+         *
+         * <p>This method is typically called when the device (or the service) does not support
+         * fingerprint authentication (and hence it cannot use {@link
+         * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs
+         * to use a custom authentication UI for the dataset. For example, when a dataset contains
+         * credit card information (such as number, expiration date, and verification code), the
+         * service displays an authentication dialog asking for the verification code to unlock the
+         * rest of the data).
+         *
+         * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset
+         * name is masked (for example, "VISA....1234").
+         *
+         * <p>When the user selects this dataset, the Android System calls {@link
+         * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+         * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in
+         * the flags and the same {@code extras} passed to this method. The service can then
+         * displays its custom authentication UI, and then call the proper method on {@link
+         * android.service.autofill.FillCallback} depending on the authentication result and whether
+         * this dataset already contains the fields needed to auto-fill the activity:
+         *
+         * <ul>
+         *   <li>If authentication failed, call
+         *   {@link android.service.autofill.FillCallback#onDatasetAuthentication(Dataset,
+         *       int)} passing {@link
+         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags.
+         *   <li>If authentication succeeded and this datast is empty (no fields), call {@link
+         *       android.service.autofill.FillCallback#onSuccess(FillResponse)} with a new dataset
+         *       (with the proper fields).
+         *   <li>If authentication succeeded and this response is not empty, call {@link
+         *       android.service.autofill.FillCallback#onDatasetAuthentication(Dataset, int)}
+         *       passing
+         *       {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the
+         *       {@code flags} and {@code null} as the {@code dataset}.
+         * </ul>
+         *
+         * @param extras when set, will be passed back in the {@link
+         *     android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle,
+         *     int)}, call so it could be used by the service to handle state.
+         * @param flags optional parameters, currently ignored.
+         */
+        public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) {
+            return requiresAuthentication(null, extras, flags);
+        }
+
+        /**
+         * Requires dataset authentication through the Fingerprint sensor before auto-filling the
+         * activity with this dataset.
+         *
+         * <p>This method is typically called when the dataset contains sensitive information (for
+         * example, credit card information) and the provider requires the user to re-authenticate
+         * before using it.
+         *
+         * <p>Since the dataset is "locked" until the user authenticates it, typically this dataset
+         * name is masked (for example, "VISA....1234").
+         *
+         * <p>When the user selects this dataset, the Android System displays an UI affordance
+         * asking the user to use the fingerprint sensor unlock the dataset, and what happens after
+         * a successful fingerprint authentication depends on whether the dataset is empty (no
+         * fields, only the masked name) or not:
+         *
+         * <ul>
+         *   <li>If it's empty, the Android System will call {@link
+         *       android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle,
+         *       int)} passing {@link
+         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the
+         *       flags.
+         *   <li>If it's not empty, the activity will be auto-filled with its data.
+         * </ul>
+         *
+         * <p>If the fingerprint authentication fails, the Android System will call {@link
+         * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+         * passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the
+         * flags.
+         *
+         * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use
+         * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint
+         * authentication is available before using this method, and use other alternatives (such as
+         * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is
+         * called when fingerprint is not available, Android System will call {@link
+         * android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+         * passing {@link
+         * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}
+         * in the flags, but it would be wasting system resources (and worsening the user
+         * experience) in the process.
+         *
+         * @param crypto object that will be authenticated.
+         * @param extras when set, will be passed back in the {@link
+         *     android.service.autofill.AutoFillService#onDatasetAuthenticationRequest(Bundle, int)}
+         *     call so it could be used by the service to handle state.
+         * @param flags optional parameters, currently ignored.
+         */
+        public Builder requiresFingerprintAuthentication(CryptoObject crypto,
+                @Nullable Bundle extras, int flags) {
+            // TODO(b/33197203): should we allow crypto to be null?
+            Preconditions.checkArgument(crypto != null, "must pass a CryptoObject");
+            return requiresAuthentication(crypto, extras, flags);
+        }
+
+        private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras,
+                int flags) {
+            // There can be only one!
+            Preconditions.checkState(!mRequiresAuth,
+                    "requires-authentication methods already called");
+            // TODO(b/33197203): make sure that either this method or setExtras() is called, but
+            // not both
+            mExtras = extras;
+            mFlags = flags;
+            mRequiresAuth = true;
+            if (cryptoObject != null) {
+                mHasCryptoObject = true;
+                mCryptoOpId = cryptoObject.getOpId();
+            }
+            return this;
+        }
+
+        /**
          * Sets the value of a field.
          *
          * @param id id returned by {@link ViewNode#getAutoFillId()}.
@@ -121,15 +281,17 @@
         }
 
         /**
-         * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
-         * methods such as
-         * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
-         * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
-         * {@link AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
+         * Sets a {@link Bundle} that will be passed to subsequent calls to
+         * {@link android.service.autofill.AutoFillService} methods such as
+ * {@link android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
+         * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
+         * {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} as the key.
          *
          * <p>It can be used to keep service state in between calls.
          */
         public Builder setExtras(Bundle extras) {
+            // TODO(b/33197203): make sure that either this method or the requires-Authentication
+            // ones are called, but not both
             mExtras = Objects.requireNonNull(extras, "extras cannot be null");
             return this;
         }
@@ -158,6 +320,12 @@
         parcel.writeCharSequence(mName);
         parcel.writeList(mFields);
         parcel.writeBundle(mExtras);
+        parcel.writeInt(mFlags);
+        parcel.writeInt(mRequiresAuth ? 1 : 0);
+        parcel.writeInt(mHasCryptoObject ? 1 : 0);
+        if (mHasCryptoObject) {
+            parcel.writeLong(mCryptoOpId);
+        }
     }
 
     @SuppressWarnings("unchecked")
@@ -165,6 +333,10 @@
         mName = parcel.readCharSequence();
         mFields = parcel.readArrayList(null);
         mExtras = parcel.readBundle();
+        mFlags = parcel.readInt();
+        mRequiresAuth = parcel.readInt() == 1;
+        mHasCryptoObject = parcel.readInt() == 1;
+        mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0;
     }
 
     public static final Parcelable.Creator<Dataset> CREATOR = new Parcelable.Creator<Dataset>() {
diff --git a/core/java/android/view/autofill/FillResponse.java b/core/java/android/view/autofill/FillResponse.java
index 3a14767..67eb85a 100644
--- a/core/java/android/view/autofill/FillResponse.java
+++ b/core/java/android/view/autofill/FillResponse.java
@@ -18,11 +18,13 @@
 import static android.view.autofill.Helper.DEBUG;
 import static android.view.autofill.Helper.append;
 
+import android.annotation.Nullable;
 import android.app.Activity;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.service.autofill.AutoFillService;
+import android.service.autofill.FillCallback;
 
 import com.android.internal.util.Preconditions;
 
@@ -34,14 +36,16 @@
 import java.util.Set;
 
 /**
- * Response for a
- * {@link AutoFillService#onFillRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.FillCallback)}
- * request.
+ * Response for a {@link
+ * android.service.autofill.AutoFillService#onFillRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)} request.
  *
  * <p>The response typically contains one or more {@link Dataset}s, each representing a set of
- * fields that can be auto-filled together. For example, for a login page with username/password
- * where the user only have one account in the service, the response could be:
+ * fields that can be auto-filled together, and the Android System displays a dataset picker UI
+ * affordance that the user must use before the {@link Activity} is filled with the dataset.
+ *
+ * <p>For example, for a login page with username/password where the user only has one account in
+ * the service, the response could be:
  *
  * <pre class="prettyprint">
  *  new FillResponse.Builder()
@@ -67,9 +71,9 @@
  *      .build();
  * </pre>
  *
- * <p>If the user does not have any data associated with this {@link Activity} but the service
- * wants to offer the user the option to save the data that was entered, then the service could
- * populate the response with {@code savableIds} instead of {@link Dataset}s:
+ * <p>If the user does not have any data associated with this {@link Activity} but the service wants
+ * to offer the user the option to save the data that was entered, then the service could populate
+ * the response with {@code savableIds} instead of {@link Dataset}s:
  *
  * <pre class="prettyprint">
  *  new FillResponse.Builder()
@@ -79,8 +83,8 @@
  *
  * <p>Similarly, there might be cases where the user data on the service is enough to populate some
  * fields but not all, and the service would still be interested on saving the other fields. In this
- * scenario, the service could populate the response with both {@link Dataset}s and
- * {@code savableIds}:
+ * scenario, the service could populate the response with both {@link Dataset}s and {@code
+ * savableIds}:
  *
  * <pre class="prettyprint">
  *   new FillResponse.Builder()
@@ -101,9 +105,9 @@
  * <p>If the service has multiple {@link Dataset}s with multiple options for some fields on each
  * dataset (for example, multiple accounts with both a home and work address), then it should
  * "partition" the {@link Activity} in sections and populate the response with just a subset of the
- * data that would fulfill the first section; then once the user fills the first section and taps
- * a field from the next section, the Android system would issue another request for that section,
- * and so on. For example, the first response could be:
+ * data that would fulfill the first section; then once the user fills the first section and taps a
+ * field from the next section, the Android system would issue another request for that section, and
+ * so on. For example, the first response could be:
  *
  * <pre class="prettyprint">
  *  new FillResponse.Builder()
@@ -134,19 +138,31 @@
  *      .build();
  * </pre>
  *
- * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or
- * {@link Dataset.Builder#setExtras(Bundle)} methods to pass
- * a {@link Bundle} with service-specific data use to identify this response on future calls (like
- * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundle will be
- * available as the {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} extra in
- * that method's {@code extras} argument.
+ * <p>The service could require user authentication, either at the {@link FillResponse} or {@link
+ * Dataset} levels, prior to auto-filling the activity - see {@link
+ * FillResponse.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, {@link
+ * FillResponse.Builder#requiresCustomAuthentication(Bundle, int)}, {@link
+ * Dataset.Builder#requiresFingerprintAuthentication(CryptoObject, Bundle, int)}, and {@link
+ * Dataset.Builder#requiresCustomAuthentication(Bundle, int)} for details.
+ *
+ * <p>Finally, the service can use the {@link FillResponse.Builder#setExtras(Bundle)} and/or {@link
+ * Dataset.Builder#setExtras(Bundle)} methods to pass {@link Bundle}s with service-specific data use
+ * to identify this response on future calls (like {@link
+ * android.service.autofill.AutoFillService#onSaveRequest(android.app.assist.AssistStructure,
+ * Bundle, android.os.CancellationSignal, android.service.autofill.SaveCallback)}) - such bundles
+ * will be available as the {@link android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS}
+ * and {@link android.service.autofill.AutoFillService#EXTRA_DATASET_EXTRAS} extras in that method's
+ * {@code extras} argument.
  */
 public final class FillResponse implements Parcelable {
 
     private final List<Dataset> mDatasets;
     private final AutoFillId[] mSavableIds;
     private final Bundle mExtras;
+    private final int mFlags;
+    private final boolean mRequiresAuth;
+    private final boolean mHasCryptoObject;
+    private final long mCryptoOpId;
 
     private FillResponse(Builder builder) {
         // TODO(b/33197203): make it immutable?
@@ -158,6 +174,10 @@
             mSavableIds[i++] = id;
         }
         mExtras = builder.mExtras;
+        mFlags = builder.mFlags;
+        mRequiresAuth = builder.mRequiresAuth;
+        mHasCryptoObject = builder.mHasCryptoObject;
+        mCryptoOpId = builder.mCryptoOpId;
     }
 
     /** @hide */
@@ -175,13 +195,161 @@
         return mExtras;
     }
 
+    /** @hide */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /** @hide */
+    public boolean isAuthRequired() {
+        return mRequiresAuth;
+    }
+
+    /** @hide */
+    public boolean hasCryptoObject() {
+        return mHasCryptoObject;
+    }
+
+    /** @hide */
+    public long getCryptoObjectOpId() {
+        return mCryptoOpId;
+    }
+
     /**
      * Builder for {@link FillResponse} objects.
      */
     public static final class Builder {
         private final List<Dataset> mDatasets = new ArrayList<>();
         private final Set<AutoFillId> mSavableIds = new HashSet<>();
-        private  Bundle mExtras;
+        private Bundle mExtras;
+        private int mFlags;
+        private boolean mRequiresAuth;
+        private boolean mHasCryptoObject;
+        private long mCryptoOpId;
+
+        /**
+         * Requires user authentication through the {@link android.service.autofill.AutoFillService}
+         * before handling an auto-fill request.
+         *
+         * <p>This method is typically called when the device (or the service) does not support
+         * fingerprint authentication (and hence it cannot use {@link
+         * #requiresFingerprintAuthentication(CryptoObject, Bundle, int)}) or when the service needs
+         * to use a custom authentication UI and is used in 2 scenarios:
+         *
+         * <ol>
+         *   <li>When the user data is encrypted and the service must authenticate an object that
+         *       will be used to decrypt it.
+         *   <li>When the service already acquired the user data but wants to confirm the user's
+         *       identity before the activity is filled with it.
+         * </ol>
+         *
+         * <p>When this method is called, the Android System displays an UI affordance asking the
+         * user to tap it to auto-fill the activity; if the user taps it, the Android System calls
+         * {@link
+         * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+         * int)} passing {@link
+         * android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_REQUESTED} in the flags and
+         * the same {@code extras} passed to this method. The service can then displays its custom
+         * authentication UI, and then call the proper method on {@link FillCallback} depending on
+         * the authentication result and whether this response already contains the {@link Dataset}s
+         * need to auto-fill the activity:
+         *
+         * <ul>
+         *   <li>If authentication failed, call {@link
+         *       FillCallback#onFillResponseAuthentication(int)} passing {@link
+         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR} in the flags.
+         *   <li>If authentication succeeded and this response is empty (no datasets), call {@link
+         *       FillCallback#onSuccess(FillResponse)} with a new dataset (that does not require
+         *       authentication).
+         *   <li>If authentication succeeded and this response is not empty, call {@link
+         *       FillCallback#onFillResponseAuthentication(int)} passing {@link
+         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS} in the flags.
+         * </ul>
+         *
+         * @param extras when set, will be passed back in the {@link
+         *     android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+         *     int)} call so it could be used by the service to handle state.
+         * @param flags optional parameters, currently ignored.
+         */
+        public Builder requiresCustomAuthentication(@Nullable Bundle extras, int flags) {
+            return requiresAuthentication(null, extras, flags);
+        }
+
+        /**
+         * Requires user authentication through the Fingerprint sensor before handling an auto-fill
+         * request.
+         *
+         * <p>The {@link android.service.autofill.AutoFillService} typically uses this method in 2
+         * situations:
+         *
+         * <ol>
+         *   <li>When the user data is encrypted and the service must authenticate an object that
+         *       will be used to decrypt it.
+         *   <li>When the service already acquired the user data but wants to confirm the user's
+         *       identity before the activity is filled with it.
+         * </ol>
+         *
+         * <p>When this method is called, the Android System displays an UI affordance asking the
+         * user to use the fingerprint sensor to auto-fill the activity, and what happens after a
+         * successful fingerprint authentication depends on the number of {@link Dataset}s included
+         * in this response:
+         *
+         * <ul>
+         *   <li>If it's empty (scenario #1 above), the Android System will call {@link
+         *     android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+         *       int)} passing {@link
+         *       android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_SUCCESS}} in the
+         *       flags.
+         *   <li>If it contains one dataset, the activity will be auto-filled right away.
+         *   <li>If it contains many datasets, the Android System will show dataset picker UI, and
+         *       then auto-fill the activity once the user select the proper datased.
+         * </ul>
+         *
+         * <p>If the fingerprint authentication fails, the Android System will call {@link
+         * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+         * int)} passing {@link android.service.autofill.AutoFillService#FLAG_AUTHENTICATION_ERROR}
+         * in the flags.
+         *
+         * <p><strong>NOTE: </note> the {@link android.service.autofill.AutoFillService} should use
+         * the {@link android.hardware.fingerprint.FingerprintManager} to check if fingerpint
+         * authentication is available before using this method, and use other alternatives (such as
+         * {@link #requiresCustomAuthentication(Bundle, int)}) if it is not: if this method is
+         * called when fingerprint is not available, Android System will call {@link
+         * android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+         * int)} passing {@link
+         * android.service.autofill.AutoFillService#FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE}
+         * in the flags, but it would be wasting system resources (and worsening the user
+         * experience) in the process.
+         *
+         * @param crypto object that will be authenticated.
+         * @param extras when set, will be passed back in the {@link
+         *     android.service.autofill.AutoFillService#onFillResponseAuthenticationRequest(Bundle,
+         *     int)} call so it could be used by the service to handle state.
+         * @param flags optional parameters, currently ignored.
+         */
+        public Builder requiresFingerprintAuthentication(CryptoObject crypto,
+                @Nullable Bundle extras, int flags) {
+            // TODO(b/33197203): should we allow crypto to be null?
+            Preconditions.checkArgument(crypto != null, "must pass a CryptoObject");
+            return requiresAuthentication(crypto, extras, flags);
+        }
+
+        private Builder requiresAuthentication(CryptoObject cryptoObject, Bundle extras,
+                int flags) {
+            // There can be only one!
+            Preconditions.checkState(!mRequiresAuth,
+                    "requires-authentication methods already called");
+            // TODO(b/33197203): make sure that either this method or setExtras() is called, but
+            // not both
+            mExtras = extras;
+            mFlags = flags;
+            mRequiresAuth = true;
+            if (cryptoObject != null) {
+                mHasCryptoObject = true;
+                mCryptoOpId = cryptoObject.getOpId();
+            }
+            return this;
+        }
 
         /**
          * Adds a new {@link Dataset} to this response.
@@ -191,7 +359,6 @@
         public Builder addDataset(Dataset dataset) {
             Preconditions.checkNotNull(dataset, "dataset cannot be null");
             // TODO(b/33197203): check if name already exists
-            // TODO(b/33197203): check if authId already exists (and update javadoc)
             mDatasets.add(dataset);
             for (DatasetField field : dataset.getFields()) {
                 mSavableIds.add(field.getId());
@@ -201,13 +368,14 @@
 
         /**
          * Adds ids of additional fields that the service would be interested to save (through
-         * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
-         * android.os.CancellationSignal, android.service.autofill.SaveCallback)}) but were not
-         * indirectly set through {@link #addDataset(Dataset)}.
+         * {@link android.service.autofill.AutoFillService#onSaveRequest(
+         * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
+         * android.service.autofill.SaveCallback)}) but were not indirectly set through {@link
+         * #addDataset(Dataset)}.
          *
          * <p>See {@link FillResponse} for examples.
          */
-        public Builder addSavableFields(AutoFillId...ids) {
+        public Builder addSavableFields(AutoFillId... ids) {
             for (AutoFillId id : ids) {
                 mSavableIds.add(id);
             }
@@ -215,15 +383,18 @@
         }
 
         /**
-         * Sets a {@link Bundle} that will be passed to subsequent calls to {@link AutoFillService}
-         * methods such as
-         * {@link AutoFillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
-         * android.os.CancellationSignal, android.service.autofill.SaveCallback)}, using
-         * {@link AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
+         * Sets a {@link Bundle} that will be passed to subsequent calls to {@link
+         * android.service.autofill.AutoFillService} methods such as {@link
+         * android.service.autofill.AutoFillService#onSaveRequest(
+         * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal,
+         * android.service.autofill.SaveCallback)}, using {@link
+         * android.service.autofill.AutoFillService#EXTRA_RESPONSE_EXTRAS} as the key.
          *
          * <p>It can be used when to keep service state in between calls.
          */
         public Builder setExtras(Bundle extras) {
+            // TODO(b/33197203): make sure that either this method or the requires-Authentication
+            // ones are called, but not both
             mExtras = Objects.requireNonNull(extras, "extras cannot be null");
             return this;
         }
@@ -246,7 +417,10 @@
         final StringBuilder builder = new StringBuilder("FillResponse: [datasets=")
                 .append(mDatasets).append(", savableIds=").append(Arrays.toString(mSavableIds))
                 .append(", extras=");
-        append(builder, mExtras);
+        append(builder, mExtras)
+            .append(", flags=").append(mFlags)
+            .append(", requiresAuth: ").append(mRequiresAuth)
+            .append(", hasCrypto: ").append(mHasCryptoObject);
         return builder.append(']').toString();
     }
 
@@ -264,6 +438,12 @@
         parcel.writeList(mDatasets);
         parcel.writeParcelableArray(mSavableIds, 0);
         parcel.writeBundle(mExtras);
+        parcel.writeInt(mFlags);
+        parcel.writeInt(mRequiresAuth ? 1 : 0);
+        parcel.writeInt(mHasCryptoObject ? 1 : 0);
+        if (mHasCryptoObject) {
+            parcel.writeLong(mCryptoOpId);
+        }
     }
 
     private FillResponse(Parcel parcel) {
@@ -271,6 +451,10 @@
         parcel.readList(mDatasets, null);
         mSavableIds = parcel.readParcelableArray(null, AutoFillId.class);
         mExtras = parcel.readBundle();
+        mFlags = parcel.readInt();
+        mRequiresAuth = parcel.readInt() == 1;
+        mHasCryptoObject = parcel.readInt() == 1;
+        mCryptoOpId = mHasCryptoObject ? parcel.readLong() : 0;
     }
 
     public static final Parcelable.Creator<FillResponse> CREATOR =
diff --git a/core/java/android/view/autofill/Helper.java b/core/java/android/view/autofill/Helper.java
index 772710e..14cf9e8 100644
--- a/core/java/android/view/autofill/Helper.java
+++ b/core/java/android/view/autofill/Helper.java
@@ -18,28 +18,33 @@
 
 import android.os.Bundle;
 
+import java.util.Arrays;
+import java.util.Objects;
 import java.util.Set;
 
 /** @hide */
 public final class Helper {
 
-    // TODO(b/33197203): set to false when stable
-    static final boolean DEBUG = true;
+    static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
     static final String REDACTED = "[REDACTED]";
 
-    static void append(StringBuilder builder, Bundle bundle) {
+    static StringBuilder append(StringBuilder builder, Bundle bundle) {
         if (bundle == null) {
             builder.append("N/A");
         } else if (!DEBUG) {
             builder.append(REDACTED);
         } else {
             final Set<String> keySet = bundle.keySet();
-            builder.append("[bundle with ").append(keySet.size()).append(" extras:");
+            builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
             for (String key : keySet) {
-                builder.append(' ').append(key).append('=').append(bundle.get(key)).append(',');
+                final Object value = bundle.get(key);
+                builder.append(' ').append(key).append('=');
+                builder.append((value instanceof Object[])
+                        ? Arrays.toString((Objects[]) value) : value);
             }
             builder.append(']');
         }
+        return builder;
     }
 
     private Helper() {
diff --git a/core/java/android/view/autofill/VirtualViewDelegate.java b/core/java/android/view/autofill/VirtualViewDelegate.java
index a19b4e5..278bf4f 100644
--- a/core/java/android/view/autofill/VirtualViewDelegate.java
+++ b/core/java/android/view/autofill/VirtualViewDelegate.java
@@ -15,6 +15,8 @@
  */
 package android.view.autofill;
 
+import android.annotation.Nullable;
+import android.graphics.Rect;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewStructure;
@@ -71,20 +73,18 @@
     public abstract static class Callback {
 
         /**
-         * Sent when the focus inside the hierarchy changed.
+         * Sent when the auto-fill bar for a child must be updated.
          *
-         * <p>Typically callled twice - for the nodes that lost and gained focus.
-         *
-         * <p>This method should only be called when the change was not caused by the AutoFill
-         * Framework itselft (i.e, through {@link VirtualViewDelegate#autoFill(int, AutoFillValue)},
-         * but by external causes (for example, when the user changed the value through the view's
-         * UI).
-         *
-         * @param virtualId id of the node whose focus changed.
-         * @param hasFocus {@code true} when focus was gained, {@code false} when it was lost.
+         * See {@link AutoFillManager#updateAutoFillInput(View, int, android.graphics.Rect, int)}
+         * for more details.
          */
-        public void onFocusChanged(int virtualId, boolean hasFocus) {
-            if (DEBUG) Log.d(TAG, "onFocusChanged() for " + virtualId + ": " + hasFocus);
+        // TODO(b/33197203): do we really need it, or should the parent view just call
+        // AutoFillManager.updateAutoFillInput() directly?
+        public void onAutoFillInputUpdated(int virtualId, @Nullable Rect boundaries, int flags) {
+            if (DEBUG) {
+                Log.v(TAG, "onAutoFillInputUpdated(): virtualId=" + virtualId + ", boundaries="
+                        + boundaries + ", flags=" + flags);
+            }
         }
 
         /**
diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java
new file mode 100644
index 0000000..7aab71f
--- /dev/null
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -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.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper object for setting and getting entity scores for classified text.
+ *
+ * @param <T> the entity type.
+ * @hide
+ */
+final class EntityConfidence<T> {
+
+    private final Map<T, Float> mEntityConfidence = new HashMap<>();
+
+    private final Comparator<T> mEntityComparator = (e1, e2) -> {
+        float score1 = mEntityConfidence.get(e1);
+        float score2 = mEntityConfidence.get(e2);
+        if (score1 > score2) {
+            return 1;
+        }
+        if (score1 < score2) {
+            return -1;
+        }
+        return 0;
+    };
+
+    EntityConfidence() {}
+
+    EntityConfidence(@NonNull EntityConfidence<T> source) {
+        Preconditions.checkNotNull(source);
+        mEntityConfidence.putAll(source.mEntityConfidence);
+    }
+
+    /**
+     * Sets an entity type for the classified text and assigns a confidence score.
+     *
+     * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+     *      0 implies the entity does not exist for the classified text.
+     *      Values greater than 1 are clamped to 1.
+     */
+    public void setEntityType(
+            @NonNull T type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+        Preconditions.checkNotNull(type);
+        if (confidenceScore > 0) {
+            mEntityConfidence.put(type, Math.min(1, confidenceScore));
+        } else {
+            mEntityConfidence.remove(type);
+        }
+    }
+
+    /**
+     * Returns an immutable list of entities found in the classified text ordered from
+     * high confidence to low confidence.
+     */
+    @NonNull
+    public List<T> getEntities() {
+        List<T> entities = new ArrayList<>(mEntityConfidence.size());
+        entities.addAll(mEntityConfidence.keySet());
+        entities.sort(mEntityComparator);
+        return Collections.unmodifiableList(entities);
+    }
+
+    /**
+     * Returns the confidence score for the specified entity. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+     * classified text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(T entity) {
+        if (mEntityConfidence.containsKey(entity)) {
+            return mEntityConfidence.get(entity);
+        }
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return mEntityConfidence.toString();
+    }
+}
diff --git a/core/java/android/view/textclassifier/LinksInfo.java b/core/java/android/view/textclassifier/LinksInfo.java
new file mode 100644
index 0000000..3acbdc0
--- /dev/null
+++ b/core/java/android/view/textclassifier/LinksInfo.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.view.textclassifier;
+
+import android.annotation.NonNull;
+
+/**
+ * Link information that can be applied to text. See: {@link #apply(CharSequence)}.
+ * Typical implementations of this interface will annotate spannable text with e.g
+ * {@link android.text.style.ClickableSpan}s or other annotations.
+ */
+public interface LinksInfo {
+
+    /**
+     * @hide
+     */
+    LinksInfo NO_OP = text -> false;
+
+    /**
+     * Applies link annotations to the specified text.
+     * These annotations are not guaranteed to be applied. For example, the annotations may not be
+     * applied if the text has changed from what it was when the link spec was generated for it.
+     *
+     * @return Whether or not the link annotations were successfully applied.
+     */
+    boolean apply(@NonNull CharSequence text);
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
new file mode 100644
index 0000000..4673c50
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -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.
+ */
+
+package android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.text.LangId;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Interface to the text classification service.
+ *
+ * <p>You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService}.
+ */
+public final class TextClassificationManager {
+
+    private static final String LOG_TAG = "TextClassificationManager";
+
+    private final Context mContext;
+    // TODO: Implement a way to close the file descriptor.
+    private ParcelFileDescriptor mFd;
+    private TextClassifier mDefault;
+    private LangId mLangId;
+
+    /** @hide */
+    public TextClassificationManager(Context context) {
+        mContext = Preconditions.checkNotNull(context);
+    }
+
+    /**
+     * Returns the default text classifier.
+     */
+    public TextClassifier getDefaultTextClassifier() {
+        if (mDefault == null) {
+            try {
+                mFd = ParcelFileDescriptor.open(
+                        new File("/etc/assistant/smart-selection.model"),
+                        ParcelFileDescriptor.MODE_READ_ONLY);
+                mDefault = new TextClassifierImpl(mContext, mFd);
+            } catch (FileNotFoundException e) {
+                Log.e(LOG_TAG, "Error accessing 'text classifier selection' model file.", e);
+                mDefault = TextClassifier.NO_OP;
+            }
+        }
+        return mDefault;
+    }
+
+    /**
+     * Returns information containing languages that were detected in the provided text.
+     * This is a blocking operation you should avoid calling it on the UI thread.
+     *
+     * @throws IllegalArgumentException if text is null
+     */
+    public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
+        Preconditions.checkArgument(text != null);
+        try {
+            if (text.length() > 0) {
+                final String language = getLanguageDetector().findLanguage(text.toString());
+                final Locale locale = new Locale.Builder().setLanguageTag(language).build();
+                return Collections.unmodifiableList(Arrays.asList(
+                        new TextLanguage.Builder(0, text.length())
+                                .setLanguage(locale, 1.0f /* confidence */)
+                                .build()));
+            }
+        } catch (Throwable t) {
+            // Avoid throwing from this method. Log the error.
+            Log.e(LOG_TAG, "Error detecting languages for text. Returning empty result.", t);
+        }
+        // Getting here means something went wrong. Return an empty result.
+        return Collections.emptyList();
+    }
+
+    private LangId getLanguageDetector() {
+        if (mLangId == null) {
+            // TODO: Use a file descriptor as soon as we start to depend on a model file
+            // for language detection.
+            mLangId = new LangId(0);
+        }
+        return mLangId;
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassificationResult.java b/core/java/android/view/textclassifier/TextClassificationResult.java
new file mode 100644
index 0000000..6af0efb
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassificationResult.java
@@ -0,0 +1,233 @@
+/*
+ * 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 android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.view.View.OnClickListener;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information for generating a widget to handle classified text.
+ */
+public final class TextClassificationResult {
+
+    /**
+     * @hide
+     */
+    static final TextClassificationResult EMPTY = new TextClassificationResult.Builder().build();
+
+    @NonNull private final String mText;
+    @Nullable private final Drawable mIcon;
+    @Nullable private final String mLabel;
+    @Nullable private final Intent mIntent;
+    @Nullable private final OnClickListener mOnClickListener;
+    @NonNull private final EntityConfidence<String> mEntityConfidence;
+    @NonNull private final List<String> mEntities;
+
+    private TextClassificationResult(
+            @NonNull String text,
+            Drawable icon,
+            String label,
+            Intent intent,
+            OnClickListener onClickListener,
+            @NonNull EntityConfidence<String> entityConfidence) {
+        mText = text;
+        mIcon = icon;
+        mLabel = label;
+        mIntent = intent;
+        mOnClickListener = onClickListener;
+        mEntityConfidence = new EntityConfidence<>(entityConfidence);
+        mEntities = mEntityConfidence.getEntities();
+    }
+
+    /**
+     * Gets the classified text.
+     */
+    @NonNull
+    public String getText() {
+        return mText;
+    }
+
+    /**
+     * Returns the number of entities found in the classified text.
+     */
+    @IntRange(from = 0)
+    public int getEntityCount() {
+        return mEntities.size();
+    }
+
+    /**
+     * Returns the entity at the specified index. Entities are ordered from high confidence
+     * to low confidence.
+     *
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getEntityCount() for the number of entities available.
+     */
+    @NonNull
+    public @EntityType String getEntity(int index) {
+        return mEntities.get(index);
+    }
+
+    /**
+     * Returns the confidence score for the specified entity. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+     * classified text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(@EntityType String entity) {
+        return mEntityConfidence.getConfidenceScore(entity);
+    }
+
+    /**
+     * Returns an icon that may be rendered on a widget used to act on the classified text.
+     */
+    @Nullable
+    public Drawable getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Returns a label that may be rendered on a widget used to act on the classified text.
+     */
+    @Nullable
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Returns an intent that may be fired to act on the classified text.
+     */
+    @Nullable
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Returns an OnClickListener that may be triggered to act on the classified text.
+     */
+    @Nullable
+    public OnClickListener getOnClickListener() {
+        return mOnClickListener;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("TextClassificationResult {"
+                        + "text=%s, entities=%s, label=%s, intent=%s}",
+                mText, mEntityConfidence, mLabel, mIntent);
+    }
+
+    /**
+     * Creates an OnClickListener that starts an activity with the specified intent.
+     *
+     * @throws IllegalArgumentException if context or intent is null
+     * @hide
+     */
+    @NonNull
+    public static OnClickListener createStartActivityOnClick(
+            @NonNull final Context context, @NonNull final Intent intent) {
+        Preconditions.checkArgument(context != null);
+        Preconditions.checkArgument(intent != null);
+        return v -> context.startActivity(intent);
+    }
+
+    /**
+     * Builder for building {@link TextClassificationResult}s.
+     */
+    public static final class Builder {
+
+        @NonNull private String mText;
+        @Nullable private Drawable mIcon;
+        @Nullable private String mLabel;
+        @Nullable private Intent mIntent;
+        @Nullable private OnClickListener mOnClickListener;
+        @NonNull private final EntityConfidence<String> mEntityConfidence =
+                new EntityConfidence<>();
+
+        /**
+         * Sets the classified text.
+         */
+        public Builder setText(@NonNull String text) {
+            mText = Preconditions.checkNotNull(text);
+            return this;
+        }
+
+        /**
+         * Sets an entity type for the classification result and assigns a confidence score.
+         *
+         * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+         *      0 implies the entity does not exist for the classified text.
+         *      Values greater than 1 are clamped to 1.
+         */
+        public Builder setEntityType(
+                @NonNull @EntityType String type,
+                @FloatRange(from = 0.0, to = 1.0)float confidenceScore) {
+            mEntityConfidence.setEntityType(type, confidenceScore);
+            return this;
+        }
+
+        /**
+         * Sets an icon that may be rendered on a widget used to act on the classified text.
+         */
+        public Builder setIcon(@Nullable Drawable icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets a label that may be rendered on a widget used to act on the classified text.
+         */
+        public Builder setLabel(@Nullable String label) {
+            mLabel = label;
+            return this;
+        }
+
+        /**
+         * Sets an intent that may be fired to act on the classified text.
+         */
+        public Builder setIntent(@Nullable Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Sets an OnClickListener that may be triggered to act on the classified text.
+         */
+        public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
+            mOnClickListener = onClickListener;
+            return this;
+        }
+
+        /**
+         * Builds an returns a {@link TextClassificationResult}.
+         */
+        public TextClassificationResult build() {
+            return new TextClassificationResult(
+                    mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence);
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
new file mode 100644
index 0000000..b84e2ae
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -0,0 +1,114 @@
+/*
+ * 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 android.view.textclassifier;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for providing text classification related features.
+ *
+ * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
+ * avoid calling them on the UI thread.
+ */
+public interface TextClassifier {
+
+    String TYPE_OTHER = "other";
+    String TYPE_EMAIL = "email";
+    String TYPE_PHONE = "phone";
+    String TYPE_ADDRESS = "address";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({
+            TYPE_OTHER, TYPE_EMAIL, TYPE_PHONE, TYPE_ADDRESS
+    })
+    @interface EntityType {}
+
+    /**
+     * No-op TextClassifier.
+     * This may be used to turn off TextClassifier features.
+     */
+    TextClassifier NO_OP = new TextClassifier() {
+
+        @Override
+        public TextSelection suggestSelection(
+                CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+            return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+        }
+
+        @Override
+        public TextClassificationResult getTextClassificationResult(
+                CharSequence text, int startIndex, int endIndex) {
+            return TextClassificationResult.EMPTY;
+        }
+
+        @Override
+        public LinksInfo getLinks(CharSequence text, int linkMask) {
+            return LinksInfo.NO_OP;
+        }
+    };
+
+    /**
+     * Returns suggested text selection indices, recognized types and their associated confidence
+     * scores. The selections are ordered from highest to lowest scoring.
+     *
+     * @param text text providing context for the selected text (which is specified
+     *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
+     * @param selectionStartIndex start index of the selected part of text
+     * @param selectionEndIndex end index of the selected part of text
+     *
+     * @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
+     *      selectionEndIndex is greater than text.length() or less than selectionStartIndex
+     */
+    @NonNull
+    TextSelection suggestSelection(
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int selectionStartIndex,
+            @IntRange(from = 0) int selectionEndIndex);
+
+    /**
+     * Returns a {@link TextClassificationResult} object that can be used to generate a widget for
+     * handling the classified text.
+     *
+     * @param text text providing context for the text to classify (which is specified
+     *      by the sub sequence starting at startIndex and ending at endIndex)
+     * @param startIndex start index of the text to classify
+     * @param endIndex end index of the text to classify
+     *
+     * @throws IllegalArgumentException if text is null; startIndex is negative;
+     *      endIndex is greater than text.length() or less than startIndex
+     */
+    @NonNull
+    TextClassificationResult getTextClassificationResult(
+            @NonNull CharSequence text, int startIndex, int endIndex);
+
+    /**
+     * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+     * information.
+     *
+     * @param text the text to generate annotations for
+     * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
+     *      specified. Subclasses of this interface may specify additional linkMasks
+     *
+     * @throws IllegalArgumentException if text is null
+     */
+    LinksInfo getLinks(@NonNull CharSequence text, int linkMask);
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
new file mode 100644
index 0000000..0657067
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -0,0 +1,379 @@
+/*
+ * 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 android.view.textclassifier;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.icu.text.BreakIterator;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.SmartSelection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.WordIterator;
+import android.text.style.ClickableSpan;
+import android.text.util.Linkify;
+import android.util.Log;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Default implementation of the {@link TextClassifier} interface.
+ *
+ * <p>This class uses machine learning to recognize entities in text.
+ * Unless otherwise stated, methods of this class are blocking operations and should most
+ * likely not be called on the UI thread.
+ *
+ * @hide
+ */
+final class TextClassifierImpl implements TextClassifier {
+
+    private static final String LOG_TAG = "TextClassifierImpl";
+
+    private final Context mContext;
+    private final ParcelFileDescriptor mFd;
+    private SmartSelection mSmartSelection;
+
+    TextClassifierImpl(Context context, ParcelFileDescriptor fd) {
+        mContext = Preconditions.checkNotNull(context);
+        mFd = Preconditions.checkNotNull(fd);
+    }
+
+    @Override
+    public TextSelection suggestSelection(
+            @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+        validateInput(text, selectionStartIndex, selectionEndIndex);
+        try {
+            if (text.length() > 0) {
+                final String string = text.toString();
+                final int[] startEnd = getSmartSelection()
+                        .suggest(string, selectionStartIndex, selectionEndIndex);
+                final int start = startEnd[0];
+                final int end = startEnd[1];
+                if (start >= 0 && end <= string.length() && start <= end) {
+                    final String type = getSmartSelection().classifyText(string, start, end);
+                    return new TextSelection.Builder(start, end)
+                            .setEntityType(type, 1.0f)
+                            .build();
+                } else {
+                    // We can not trust the result. Log the issue and ignore the result.
+                    Log.d(LOG_TAG, "Got bad indices for input text. Ignoring result.");
+                }
+            }
+        } catch (Throwable t) {
+            // Avoid throwing from this method. Log the error.
+            Log.e(LOG_TAG,
+                    "Error suggesting selection for text. No changes to selection suggested.",
+                    t);
+        }
+        // Getting here means something went wrong, return a NO_OP result.
+        return TextClassifier.NO_OP.suggestSelection(
+                text, selectionStartIndex, selectionEndIndex);
+    }
+
+    @Override
+    public TextClassificationResult getTextClassificationResult(
+            @NonNull CharSequence text, int startIndex, int endIndex) {
+        validateInput(text, startIndex, endIndex);
+        try {
+            if (text.length() > 0) {
+                final CharSequence classified = text.subSequence(startIndex, endIndex);
+                String type = getSmartSelection()
+                        .classifyText(text.toString(), startIndex, endIndex);
+                if (!TextUtils.isEmpty(type)) {
+                    type = type.toLowerCase().trim();
+                    // TODO: Added this log for debug only. Remove before release.
+                    Log.d(LOG_TAG, String.format("Classification type: %s", type));
+                    return createClassificationResult(type, classified);
+                }
+            }
+        } catch (Throwable t) {
+            // Avoid throwing from this method. Log the error.
+            Log.e(LOG_TAG, "Error getting assist info.", t);
+        }
+        // Getting here means something went wrong, return a NO_OP result.
+        return TextClassifier.NO_OP.getTextClassificationResult(text, startIndex, endIndex);
+    }
+
+
+    @Override
+    public LinksInfo getLinks(CharSequence text, int linkMask) {
+        Preconditions.checkArgument(text != null);
+        try {
+            return LinksInfoFactory.create(
+                    mContext, getSmartSelection(), text.toString(), linkMask);
+        } catch (Throwable t) {
+            // Avoid throwing from this method. Log the error.
+            Log.e(LOG_TAG, "Error getting links info.", t);
+        }
+        // Getting here means something went wrong, return a NO_OP result.
+        return TextClassifier.NO_OP.getLinks(text, linkMask);
+    }
+
+    private synchronized SmartSelection getSmartSelection() throws FileNotFoundException {
+        if (mSmartSelection == null) {
+            mSmartSelection = new SmartSelection(mFd.getFd());
+        }
+        return mSmartSelection;
+    }
+
+    private TextClassificationResult createClassificationResult(String type, CharSequence text) {
+        final Intent intent = IntentFactory.create(type, text.toString());
+        if (intent == null) {
+            return TextClassificationResult.EMPTY;
+        }
+
+        final TextClassificationResult.Builder builder = new TextClassificationResult.Builder()
+                .setText(text.toString())
+                .setEntityType(type, 1.0f /* confidence */)
+                .setIntent(intent)
+                .setOnClickListener(TextClassificationResult.createStartActivityOnClick(
+                        mContext, intent))
+                .setLabel(IntentFactory.getLabel(mContext, type));
+        final PackageManager pm = mContext.getPackageManager();
+        final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+        // TODO: If the resolveInfo is the "chooser", do not set the package name and use a default
+        // icon for this classification type.
+        intent.setPackage(resolveInfo.activityInfo.packageName);
+        Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+        if (icon == null) {
+            icon = resolveInfo.loadIcon(pm);
+        }
+        builder.setIcon(icon);
+        return builder.build();
+    }
+
+    /**
+     * @throws IllegalArgumentException if text is null; startIndex is negative;
+     *      endIndex is greater than text.length() or less than startIndex
+     */
+    private static void validateInput(@NonNull CharSequence text, int startIndex, int endIndex) {
+        Preconditions.checkArgument(text != null);
+        Preconditions.checkArgument(startIndex >= 0);
+        Preconditions.checkArgument(endIndex <= text.length());
+        Preconditions.checkArgument(endIndex >= startIndex);
+    }
+
+    /**
+     * Detects and creates links for specified text.
+     */
+    private static final class LinksInfoFactory {
+
+        private LinksInfoFactory() {}
+
+        public static LinksInfo create(
+                Context context, SmartSelection smartSelection, String text, int linkMask) {
+            final WordIterator wordIterator = new WordIterator();
+            wordIterator.setCharSequence(text, 0, text.length());
+            final List<SpanSpec> spans = new ArrayList<>();
+            int start = 0;
+            int end;
+            while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
+                final String token = text.substring(start, end);
+                if (TextUtils.isEmpty(token)) {
+                    continue;
+                }
+
+                final int[] selection = smartSelection.suggest(text, start, end);
+                final int selectionStart = selection[0];
+                final int selectionEnd = selection[1];
+                if (selectionStart >= 0 && selectionEnd <= text.length()
+                        && selectionStart <= selectionEnd) {
+                    final String type =
+                            smartSelection.classifyText(text, selectionStart, selectionEnd);
+                    if (matches(type, linkMask)) {
+                        final ClickableSpan span = createSpan(
+                                context, type, text.substring(selectionStart, selectionEnd));
+                        spans.add(new SpanSpec(selectionStart, selectionEnd, span));
+                    }
+                }
+                start = end;
+            }
+            return new LinksInfoImpl(text, avoidOverlaps(spans, text));
+        }
+
+        /**
+         * Returns true if the classification type matches the specified linkMask.
+         */
+        private static boolean matches(String type, int linkMask) {
+            if ((linkMask & Linkify.PHONE_NUMBERS) != 0
+                    && TextClassifier.TYPE_PHONE.equals(type)) {
+                return true;
+            }
+            if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
+                    && TextClassifier.TYPE_EMAIL.equals(type)) {
+                return true;
+            }
+            if ((linkMask & Linkify.MAP_ADDRESSES) != 0
+                    && TextClassifier.TYPE_ADDRESS.equals(type)) {
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Trim the number of spans so that no two spans overlap.
+         *
+         * This algorithm first ensures that there is only one span per start index, then it
+         * makes sure that no two spans overlap.
+         */
+        private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
+            Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
+            // Group spans by start index. Take the longest span.
+            final Map<Integer, SpanSpec> reps = new LinkedHashMap<>();  // order matters.
+            final int size = spans.size();
+            for (int i = 0; i < size; i++) {
+                final SpanSpec span = spans.get(i);
+                final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
+                if (rep == null || rep.mEnd < span.mEnd) {
+                    reps.put(span.mStart, span);
+                }
+            }
+            // Avoid span intersections. Take the longer span.
+            final LinkedList<SpanSpec> result = new LinkedList<>();
+            for (SpanSpec rep : reps.values()) {
+                if (result.isEmpty()) {
+                    result.add(rep);
+                    continue;
+                }
+
+                final SpanSpec last = result.getLast();
+                if (rep.mStart < last.mEnd) {
+                    // Spans intersect. Use the one with characters.
+                    if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
+                        result.set(result.size() - 1, rep);
+                    }
+                } else {
+                    result.add(rep);
+                }
+            }
+            return result;
+        }
+
+        private static ClickableSpan createSpan(
+                final Context context, final String type, final String text) {
+            return new ClickableSpan() {
+                // TODO: Style this span.
+                @Override
+                public void onClick(View widget) {
+                    context.startActivity(IntentFactory.create(type, text));
+                }
+            };
+        }
+
+        /**
+         * Implementation of LinksInfo that adds ClickableSpans to the specified text.
+         */
+        private static final class LinksInfoImpl implements LinksInfo {
+
+            private final CharSequence mOriginalText;
+            private final List<SpanSpec> mSpans;
+
+            LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
+                mOriginalText = originalText;
+                mSpans = spans;
+            }
+
+            @Override
+            public boolean apply(@NonNull CharSequence text) {
+                Preconditions.checkArgument(text != null);
+                if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
+                    Spannable spannable = (Spannable) text;
+                    final int size = mSpans.size();
+                    for (int i = 0; i < size; i++) {
+                        final SpanSpec span = mSpans.get(i);
+                        spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
+                    }
+                    return true;
+                }
+                return false;
+            }
+        }
+
+        /**
+         * Span plus its start and end index.
+         */
+        private static final class SpanSpec {
+
+            private final int mStart;
+            private final int mEnd;
+            private final ClickableSpan mSpan;
+
+            SpanSpec(int start, int end, ClickableSpan span) {
+                mStart = start;
+                mEnd = end;
+                mSpan = span;
+            }
+        }
+    }
+
+    /**
+     * Creates intents based on the classification type.
+     */
+    private static final class IntentFactory {
+
+        private IntentFactory() {}
+
+        @Nullable
+        public static Intent create(String type, String text) {
+            switch (type) {
+                case TextClassifier.TYPE_EMAIL:
+                    return new Intent(Intent.ACTION_SENDTO)
+                            .setData(Uri.parse(String.format("mailto:%s", text)));
+                case TextClassifier.TYPE_PHONE:
+                    return new Intent(Intent.ACTION_DIAL)
+                            .setData(Uri.parse(String.format("tel:%s", text)));
+                case TextClassifier.TYPE_ADDRESS:
+                    return new Intent(Intent.ACTION_VIEW)
+                            .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+                default:
+                    return null;
+                // TODO: Add other classification types.
+            }
+        }
+
+        @Nullable
+        public static String getLabel(Context context, String type) {
+            switch (type) {
+                case TextClassifier.TYPE_EMAIL:
+                    return context.getString(com.android.internal.R.string.email);
+                case TextClassifier.TYPE_PHONE:
+                    return context.getString(com.android.internal.R.string.dial);
+                case TextClassifier.TYPE_ADDRESS:
+                    return context.getString(com.android.internal.R.string.map);
+                default:
+                    return null;
+                // TODO: Add other classification types.
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
new file mode 100644
index 0000000..d94d163
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -0,0 +1,139 @@
+/*
+ * 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 android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Specifies detected languages for a section of text indicated by a start and end index.
+ */
+public final class TextLanguage {
+
+    private final int mStartIndex;
+    private final int mEndIndex;
+    @NonNull private final EntityConfidence<Locale> mLanguageConfidence;
+    @NonNull private final List<Locale> mLanguages;
+
+    private TextLanguage(
+            int startIndex, int endIndex, @NonNull EntityConfidence<Locale> languageConfidence) {
+        mStartIndex = startIndex;
+        mEndIndex = endIndex;
+        mLanguageConfidence = new EntityConfidence<>(languageConfidence);
+        mLanguages = mLanguageConfidence.getEntities();
+    }
+
+    /**
+     * Returns the start index of the detected languages in the text provided to generate this
+     * object.
+     */
+    public int getStartIndex() {
+        return mStartIndex;
+    }
+
+    /**
+     * Returns the end index of the detected languages in the text provided to generate this object.
+     */
+    public int getEndIndex() {
+        return mEndIndex;
+    }
+
+    /**
+     * Returns the number of languages found in the classified text.
+     */
+    @IntRange(from = 0)
+    public int getLanguageCount() {
+        return mLanguages.size();
+    }
+
+    /**
+     * Returns the language locale at the specified index.
+     * Language locales are ordered from high confidence to low confidence.
+     *
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getLanguageCount() for the number of language locales available.
+     */
+    @NonNull
+    public Locale getLanguage(int index) {
+        return mLanguages.get(index);
+    }
+
+    /**
+     * Returns the confidence score for the specified language. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the language was
+     * not found for the classified text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(@Nullable Locale language) {
+        return mLanguageConfidence.getConfidenceScore(language);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("TextLanguage {%d, %d, %s}",
+                mStartIndex, mEndIndex, mLanguageConfidence);
+    }
+
+    /**
+     * Builder to build {@link TextLanguage} objects.
+     */
+    public static final class Builder {
+
+        private final int mStartIndex;
+        private final int mEndIndex;
+        @NonNull private final EntityConfidence<Locale> mLanguageConfidence =
+                new EntityConfidence<>();
+
+        /**
+         * Creates a builder to build {@link TextLanguage} objects.
+         *
+         * @param startIndex the start index of the detected languages in the text provided
+         *      to generate the result
+         * @param endIndex the end index of the detected languages in the text provided
+         *      to generate the result. Must be greater than startIndex
+         */
+        public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+            Preconditions.checkArgument(startIndex >= 0);
+            Preconditions.checkArgument(endIndex > startIndex);
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+        }
+
+        /**
+         * Sets a language locale with the associated confidence score.
+         */
+        public Builder setLanguage(
+                @NonNull Locale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+            mLanguageConfidence.setEntityType(locale, confidenceScore);
+            return this;
+        }
+
+        /**
+         * Builds and returns a {@link TextLanguage}.
+         */
+        public TextLanguage build() {
+            return new TextLanguage(mStartIndex, mEndIndex, mLanguageConfidence);
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
new file mode 100644
index 0000000..3172c13
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -0,0 +1,140 @@
+/*
+ * 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 android.view.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.view.textclassifier.TextClassifier.EntityType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Information about where text selection should be.
+ */
+public final class TextSelection {
+
+    private final int mStartIndex;
+    private final int mEndIndex;
+    @NonNull private final EntityConfidence<String> mEntityConfidence;
+    @NonNull private final List<String> mEntities;
+
+    private TextSelection(
+            int startIndex, int endIndex, @NonNull EntityConfidence<String> entityConfidence) {
+        mStartIndex = startIndex;
+        mEndIndex = endIndex;
+        mEntityConfidence = new EntityConfidence<>(entityConfidence);
+        mEntities = mEntityConfidence.getEntities();
+    }
+
+    /**
+     * Returns the start index of the text selection.
+     */
+    public int getSelectionStartIndex() {
+        return mStartIndex;
+    }
+
+    /**
+     * Returns the end index of the text selection.
+     */
+    public int getSelectionEndIndex() {
+        return mEndIndex;
+    }
+
+    /**
+     * Returns the number of entities found in the classified text.
+     */
+    @IntRange(from = 0)
+    public int getEntityCount() {
+        return mEntities.size();
+    }
+
+    /**
+     * Returns the entity at the specified index. Entities are ordered from high confidence
+     * to low confidence.
+     *
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getEntityCount() for the number of entities available.
+     */
+    @NonNull
+    public @EntityType String getEntity(int index) {
+        return mEntities.get(index);
+    }
+
+    /**
+     * Returns the confidence score for the specified entity. The value ranges from
+     * 0 (low confidence) to 1 (high confidence). 0 indicates that the entity was not found for the
+     * classified text.
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(@EntityType String entity) {
+        return mEntityConfidence.getConfidenceScore(entity);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("TextSelection {%d, %d, %s}",
+                mStartIndex, mEndIndex, mEntityConfidence);
+    }
+
+    /**
+     * Builder used to build {@link TextSelection} objects.
+     */
+    public static final class Builder {
+
+        private final int mStartIndex;
+        private final int mEndIndex;
+        @NonNull private final EntityConfidence<String> mEntityConfidence =
+                new EntityConfidence<>();
+
+        /**
+         * Creates a builder used to build {@link TextSelection} objects.
+         *
+         * @param startIndex the start index of the text selection.
+         * @param endIndex the end index of the text selection. Must be greater than startIndex
+         */
+        public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
+            Preconditions.checkArgument(startIndex >= 0);
+            Preconditions.checkArgument(endIndex > startIndex);
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+        }
+
+        /**
+         * Sets an entity type for the classified text and assigns a confidence score.
+         *
+         * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence).
+         *      0 implies the entity does not exist for the classified text.
+         *      Values greater than 1 are clamped to 1.
+         */
+        public Builder setEntityType(
+                @NonNull @EntityType String type,
+                @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
+            mEntityConfidence.setEntityType(type, confidenceScore);
+            return this;
+        }
+
+        /**
+         * Builds and returns {@link TextSelection} object.
+         */
+        public TextSelection build() {
+            return new TextSelection(mStartIndex, mEndIndex, mEntityConfidence);
+        }
+    }
+}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index faf0379..6706790 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -169,8 +169,6 @@
             Log.w(VIEW_LOG_TAG, "EditText.autoFill(): no text on AutoFillValue");
             return;
         }
-        // TODO(b/33197203): once auto-fill is triggered by the IME, we'll need a new setText()
-        // or setAutoFillText() method on TextView to avoid re-triggering it.
         setText(text);
     }
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2fc8ec9..f7f9a81 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -60,8 +60,6 @@
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.StaticLayout;
-import android.text.TextClassification;
-import android.text.TextSelection;
 import android.text.TextUtils;
 import android.text.method.KeyListener;
 import android.text.method.MetaKeyKeyListener;
@@ -108,6 +106,8 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationResult;
+import android.view.textclassifier.TextSelection;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.TextView.Drawables;
 import android.widget.TextView.OnEditorActionListener;
@@ -149,7 +149,7 @@
     private static final String UNDO_OWNER_TAG = "Editor";
 
     // Ordering constants used to place the Action Mode or context menu items in their menu.
-    // Reserve 1 for the app that the ASSIST logic suggests as the best app to handle the selection.
+    private static final int MENU_ITEM_ORDER_ASSIST = 1;
     private static final int MENU_ITEM_ORDER_UNDO = 2;
     private static final int MENU_ITEM_ORDER_REDO = 3;
     private static final int MENU_ITEM_ORDER_SHARE = 4;
@@ -238,6 +238,8 @@
     private boolean mPreserveSelection;
     private boolean mRestartActionModeOnNextRefresh;
 
+    private TextClassificationResult mTextClassificationResult;
+
     boolean mIsBeingLongClicked;
 
     private SuggestionsPopupWindow mSuggestionsPopupWindow;
@@ -1889,7 +1891,7 @@
             mInsertionPointCursorController.invalidateHandle();
         }
         if (mTextActionMode != null) {
-            mTextActionMode.invalidate();
+            invalidateActionMode(getTextClassifierInfo(false));
         }
     }
 
@@ -1982,12 +1984,12 @@
                 if (mRestartActionModeOnNextRefresh) {
                     // To avoid distraction, newly start action mode only when selection action
                     // mode is being restarted.
-                    startSelectionActionMode();
+                    startSelectionActionMode(getTextClassifierInfo(true));
                 }
             } else if (selectionController == null || !selectionController.isActive()) {
                 // Insertion action mode is active. Avoid dismissing the selection.
                 stopTextActionModeWithPreservingSelection();
-                startSelectionActionMode();
+                startSelectionActionMode(getTextClassifierInfo(true));
             } else {
                 mTextActionMode.invalidateContentRect();
             }
@@ -2031,7 +2033,8 @@
      *
      * @return true if the selection mode was actually started.
      */
-    boolean startSelectionActionMode() {
+    boolean startSelectionActionMode(@Nullable TextClassificationResult textClassificationResult) {
+        mTextClassificationResult = textClassificationResult;
         boolean selectionStarted = startSelectionActionModeInternal();
         if (selectionStarted) {
             getSelectionController().show();
@@ -2040,6 +2043,40 @@
         return selectionStarted;
     }
 
+    private boolean startSelectionActionModeWithTextAssistant() {
+        return startSelectionActionMode(getTextClassifierInfo(true));
+    }
+
+    private void invalidateActionMode(TextClassificationResult textClassificationResult) {
+        mTextClassificationResult = textClassificationResult;
+        mTextActionMode.invalidate();
+    }
+
+    // TODO: Make this a non-blocking call.
+    private TextClassificationResult getTextClassifierInfo(boolean updateSelection) {
+        // TODO: Trim the text so that only text necessary to provide context of the selected
+        // text is sent to the assistant.
+        final int trimStartIndex = 0;
+        final int trimEndIndex = mTextView.getText().length();
+        CharSequence trimmedText =
+                mTextView.getText().subSequence(trimStartIndex, trimEndIndex);
+        int startIndex = mTextView.getSelectionStart() - trimStartIndex;
+        int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
+
+        if (updateSelection) {
+            TextSelection textSelection = mTextView.getTextClassifier()
+                    .suggestSelection(trimmedText, startIndex, endIndex);
+            startIndex = Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex);
+            endIndex = Math.min(mTextView.getText().length(),
+                    textSelection.getSelectionEndIndex() + trimStartIndex);
+            Selection.setSelection((Spannable) mTextView.getText(), startIndex, endIndex);
+            return getTextClassifierInfo(false);
+        }
+
+        return mTextView.getTextClassifier()
+                .getTextClassificationResult(trimmedText, startIndex, endIndex);
+    }
+
     /**
      * If the TextView allows text selection, selects the current word when no existing selection
      * was available and starts a drag.
@@ -2086,7 +2123,7 @@
         }
         if (mTextActionMode != null) {
             // Text action mode is already started
-            mTextActionMode.invalidate();
+            invalidateActionMode(getTextClassifierInfo(false));
             return false;
         }
 
@@ -3744,8 +3781,7 @@
         private final Path mSelectionPath = new Path();
         private final RectF mSelectionBounds = new RectF();
         private final boolean mHasSelection;
-
-        private int mHandleHeight;
+        private final int mHandleHeight;
 
         public TextActionModeCallback(boolean hasSelection) {
             mHasSelection = hasSelection;
@@ -3765,18 +3801,19 @@
                 if (insertionController != null) {
                     insertionController.getHandle();
                     mHandleHeight = mSelectHandleCenter.getMinimumHeight();
+                } else {
+                    mHandleHeight = 0;
                 }
             }
         }
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-            TextClassification textClassification = updateSelectionWithTextAssistant();
-
             mode.setTitle(null);
             mode.setSubtitle(null);
             mode.setTitleOptionalHint(true);
-            populateMenuWithItems(menu, textClassification);
+            populateMenuWithItems(menu);
+            updateAssistMenuItem(menu, mTextClassificationResult);
 
             Callback customCallback = getCustomCallback();
             if (customCallback != null) {
@@ -3802,30 +3839,13 @@
             }
         }
 
-        private TextClassification updateSelectionWithTextAssistant() {
-            // Trim the text so that only text necessary to provide context of the selected text is
-            // sent to the assistant.
-            CharSequence trimmedText = mTextView.getText();
-            int textLength = mTextView.getText().length();
-            int trimStartIndex = 0;
-            int startIndex = mTextView.getSelectionStart() - trimStartIndex;
-            int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
-            TextSelection textSelection = mTextView.getTextAssistant()
-                    .suggestSelection(trimmedText, startIndex, endIndex);
-            Selection.setSelection(
-                    (Spannable) mTextView.getText(),
-                    Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex),
-                    Math.min(textLength, textSelection.getSelectionEndIndex() + trimStartIndex));
-            return textSelection.getTextClassification();
-        }
-
         private Callback getCustomCallback() {
             return mHasSelection
                     ? mCustomSelectionActionModeCallback
                     : mCustomInsertionActionModeCallback;
         }
 
-        private void populateMenuWithItems(Menu menu, TextClassification textClassification) {
+        private void populateMenuWithItems(Menu menu) {
             if (mTextView.canCut()) {
                 menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
                         com.android.internal.R.string.cut)
@@ -3855,13 +3875,13 @@
 
             updateSelectAllItem(menu);
             updateReplaceItem(menu);
-            updateAssistMenuItem(menu, textClassification);
         }
 
         @Override
         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
             updateSelectAllItem(menu);
             updateReplaceItem(menu);
+            updateAssistMenuItem(menu, mTextClassificationResult);
 
             Callback customCallback = getCustomCallback();
             if (customCallback != null) {
@@ -3894,10 +3914,16 @@
             }
         }
 
-        private void updateAssistMenuItem(Menu menu, TextClassification textClassification) {
-            // TODO: Find the best app available to handle the selected text based on information in
-            // the TextClassification object.
-            // Add app icon + intent to trigger app to the menu.
+        private void updateAssistMenuItem(
+                Menu menu, TextClassificationResult textClassificationResult) {
+            menu.removeItem(TextView.ID_ASSIST);
+            if (textClassificationResult != null
+                    && textClassificationResult.getIcon() != null
+                    && textClassificationResult.getOnClickListener() != null) {
+                menu.add(Menu.NONE, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST, null)
+                        .setIcon(textClassificationResult.getIcon())
+                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+            }
         }
 
         @Override
@@ -3909,6 +3935,10 @@
             if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
                 return true;
             }
+            if (TextView.ID_ASSIST == item.getItemId() && mTextClassificationResult != null) {
+                mTextClassificationResult.getOnClickListener().onClick(mTextView);
+                stopTextActionMode();
+            }
             return mTextView.onTextContextMenuItem(item.getItemId());
         }
 
@@ -3916,6 +3946,7 @@
         public void onDestroyActionMode(ActionMode mode) {
             // Clear mTextActionMode not to recursively destroy action mode by clearing selection.
             mTextActionMode = null;
+            mTextClassificationResult = null;
             Callback customCallback = getCustomCallback();
             if (customCallback != null) {
                 customCallback.onDestroyActionMode(mode);
@@ -4733,7 +4764,7 @@
             }
             positionAtCursorOffset(offset, false);
             if (mTextActionMode != null) {
-                mTextActionMode.invalidate();
+                invalidateActionMode(getTextClassifierInfo(false));
             }
         }
 
@@ -4817,7 +4848,7 @@
             }
             updateDrawable();
             if (mTextActionMode != null) {
-                mTextActionMode.invalidate();
+                invalidateActionMode(getTextClassifierInfo(false));
             }
         }
 
@@ -5465,7 +5496,8 @@
                     resetDragAcceleratorState();
 
                     if (mTextView.hasSelection()) {
-                        startSelectionActionMode();
+                        // TODO: Do not invoke the text assistant if this was a drag selection.
+                        startSelectionActionMode(getTextClassifierInfo(true));
                     }
                     break;
             }
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 3b7fe86..70b70bc 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -150,7 +150,11 @@
      */
     public void setIsIndicator(boolean isIndicator) {
         mIsUserSeekable = !isIndicator;
-        setFocusable(!isIndicator);
+        if (isIndicator) {
+            setFocusable(FOCUSABLE_AUTO);
+        } else {
+            setFocusable(FOCUSABLE);
+        }
     }
 
     /**
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 9139361..3822138 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -357,9 +357,9 @@
             setInputType(inputType);
         }
 
-        boolean focusable = true;
-        focusable = a.getBoolean(R.styleable.SearchView_focusable, focusable);
-        setFocusable(focusable);
+        if (getFocusable() == FOCUSABLE_AUTO) {
+            setFocusable(FOCUSABLE);
+        }
 
         a.recycle();
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b1fc67e..072fe4a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -77,8 +77,6 @@
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.StaticLayout;
-import android.text.TextAssistant;
-import android.text.TextClassificationManager;
 import android.text.TextDirectionHeuristic;
 import android.text.TextDirectionHeuristics;
 import android.text.TextPaint;
@@ -138,6 +136,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AnimationUtils;
+import android.view.autofill.AutoFillManager;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
@@ -146,6 +145,8 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
 import android.view.textservice.SpellCheckerSubtype;
 import android.view.textservice.TextServicesManager;
 import android.widget.RemoteViews.RemoteView;
@@ -264,6 +265,7 @@
 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
     static final String LOG_TAG = "TextView";
     static final boolean DEBUG_EXTRACT = false;
+    static final boolean DEBUG_AUTOFILL = false;
 
     // Enum for the "typeface" XML parameter.
     // TODO: How can we get this from the XML instead of hardcoding it here?
@@ -352,6 +354,8 @@
     private boolean mPreDrawRegistered;
     private boolean mPreDrawListenerDetached;
 
+    private TextClassifier mTextClassifier;
+
     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
@@ -1513,26 +1517,22 @@
         if (hint != null) setHint(hint);
 
         /*
-         * Views are not normally focusable unless specified to be.
+         * Views are not normally clickable unless specified to be.
          * However, TextViews that have input or movement methods *are*
-         * focusable by default.
+         * clickable by default. By setting clickable here, we implicitly set focusable as well
+         * if not overridden by the developer.
          */
         a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
-
-        boolean focusable = mMovement != null || getKeyListener() != null;
-        boolean clickable = focusable || isClickable();
-        boolean longClickable = focusable || isLongClickable();
+        boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
+        boolean clickable = canInputOrMove || isClickable();
+        boolean longClickable = canInputOrMove || isLongClickable();
 
         n = a.getIndexCount();
         for (int i = 0; i < n; i++) {
             int attr = a.getIndex(i);
 
             switch (attr) {
-                case com.android.internal.R.styleable.View_focusable:
-                    focusable = a.getBoolean(attr, focusable);
-                    break;
-
                 case com.android.internal.R.styleable.View_clickable:
                     clickable = a.getBoolean(attr, clickable);
                     break;
@@ -1544,7 +1544,6 @@
         }
         a.recycle();
 
-        setFocusable(focusable);
         setClickable(clickable);
         setLongClickable(longClickable);
 
@@ -2153,11 +2152,11 @@
 
     private void fixFocusableAndClickableSettings() {
         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
-            setFocusable(true);
+            setFocusable(FOCUSABLE);
             setClickable(true);
             setLongClickable(true);
         } else {
-            setFocusable(false);
+            setFocusable(FOCUSABLE_AUTO);
             setClickable(false);
             setLongClickable(false);
         }
@@ -6124,7 +6123,7 @@
 
         mEditor.mTextIsSelectable = selectable;
         setFocusableInTouchMode(selectable);
-        setFocusable(selectable);
+        setFocusable(FOCUSABLE_AUTO);
         setClickable(selectable);
         setLongClickable(selectable);
 
@@ -9027,6 +9026,15 @@
                 Spannable sp = (Spannable) mText;
                 MetaKeyKeyListener.resetMetaState(sp);
             }
+        } else {
+            final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
+            if (afm != null) {
+                if (DEBUG_AUTOFILL) {
+                    Log.v(LOG_TAG, "onFocusChanged(): id=" + getAutoFillViewId() + ", focused= "
+                            + focused);
+                }
+                afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_HIDE);
+            }
         }
 
         startStopMarquee(focused);
@@ -9890,7 +9898,7 @@
                         Selection.setSelection((Spannable) text, start, end);
                         // Make sure selection mode is engaged.
                         if (mEditor != null) {
-                            mEditor.startSelectionActionMode();
+                            mEditor.startSelectionActionMode(null);
                         }
                         return true;
                     }
@@ -10034,6 +10042,7 @@
     static final int ID_SHARE = android.R.id.shareText;
     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
     static final int ID_REPLACE = android.R.id.replaceText;
+    static final int ID_ASSIST = android.R.id.textAssist;
 
     /**
      * Called when a context menu option for the text view is selected.  Currently
@@ -10258,33 +10267,30 @@
         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
     }
 
-    private TextAssistant mTextAssistant;
-
     /**
-     * Sets the {@link TextAssistant} for this TextView.
-     * If null, this TextView uses the default TextAssistant which comes from the Activity.
+     * Sets the {@link TextClassifier} for this TextView.
      */
-    public void setTextAssistant(TextAssistant textAssistant) {
-        mTextAssistant = textAssistant;
+    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+        mTextClassifier = textClassifier;
     }
 
     /**
-     * Returns the {@link TextAssistant} used by this TextView.
-     * If no TextAssistant is set, it'll use the one from this TextView's {@link Activity} or
-     * {@link Context}. If no TextAssistant is found, it'll use a no-op TextAssistant.
+     * Returns the {@link TextClassifier} used by this TextView.
+     * If no TextClassifier has been set, this TextView uses the default set by the
+     * {@link TextClassificationManager}.
      */
-    public TextAssistant getTextAssistant() {
-        if (mTextAssistant != null) {
-            return mTextAssistant;
+    @NonNull
+    public TextClassifier getTextClassifier() {
+        if (mTextClassifier == null) {
+            TextClassificationManager tcm =
+                    mContext.getSystemService(TextClassificationManager.class);
+            if (tcm != null) {
+                mTextClassifier = tcm.getDefaultTextClassifier();
+            } else {
+                mTextClassifier = TextClassifier.NO_OP;
+            }
         }
-        if (mContext instanceof Activity) {
-            mTextAssistant = ((Activity) mContext).getTextAssistant();
-        } else {
-            // The context of this TextView should be an Activity. If it is not and no
-            // text assistant has been set, return the TextClassificationManager.
-            mTextAssistant = mContext.getSystemService(TextClassificationManager.class);
-        }
-        return  mTextAssistant;
+        return mTextClassifier;
     }
 
     /**
@@ -10607,6 +10613,14 @@
      * @hide
      */
     protected void viewClicked(InputMethodManager imm) {
+        final AutoFillManager afm = mContext.getSystemService(AutoFillManager.class);
+        if (afm != null) {
+            if (DEBUG_AUTOFILL) Log.v(LOG_TAG, "viewClicked(): id=" + getAutoFillViewId());
+
+            // TODO(b/33197203): integrate with onFocus and/or move to view?
+            afm.updateAutoFillInput(this, AutoFillManager.FLAG_UPDATE_UI_SHOW);
+        }
+
         if (imm != null) {
             imm.viewClicked(this);
         }
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 00faf65..4071ff4 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -69,14 +69,14 @@
     }
 
     @VisibleForTesting
-    ResolveInfo getLastChosen() throws RemoteException {
+    public ResolveInfo getLastChosen() throws RemoteException {
         return AppGlobals.getPackageManager().getLastChosenActivity(
                 mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
                 PackageManager.MATCH_DEFAULT_ONLY);
     }
 
     @VisibleForTesting
-    void setLastChosen(Intent intent, IntentFilter filter, int match)
+    public void setLastChosen(Intent intent, IntentFilter filter, int match)
             throws RemoteException {
         AppGlobals.getPackageManager().setLastChosenActivity(intent,
                 intent.resolveType(mContext.getContentResolver()),
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
index 8d11783..a94b161 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -95,39 +95,32 @@
             }
         }
 
+        private static int compareNullableCharSequences(@Nullable CharSequence c1,
+                @Nullable CharSequence c2) {
+            // For historical reasons, an empty text needs to put at the last.
+            final boolean empty1 = TextUtils.isEmpty(c1);
+            final boolean empty2 = TextUtils.isEmpty(c2);
+            if (empty1 || empty2) {
+                return (empty1 ? 1 : 0) - (empty2 ? 1 : 0);
+            }
+            return c1.toString().compareTo(c2.toString());
+        }
+
         @Override
         public int compareTo(ImeSubtypeListItem other) {
-            if (TextUtils.isEmpty(mImeName)) {
-                return 1;
+            int result = compareNullableCharSequences(mImeName, other.mImeName);
+            if (result != 0) {
+                return result;
             }
-            if (TextUtils.isEmpty(other.mImeName)) {
-                return -1;
+            result = compareNullableCharSequences(mSubtypeName, other.mSubtypeName);
+            if (result != 0) {
+                return result;
             }
-            if (!TextUtils.equals(mImeName, other.mImeName)) {
-                return mImeName.toString().compareTo(other.mImeName.toString());
+            result = (mIsSystemLocale ? -1 : 0) - (other.mIsSystemLocale ? -1 : 0);
+            if (result != 0) {
+                return result;
             }
-            if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
-                return 0;
-            }
-            if (mIsSystemLocale) {
-                return -1;
-            }
-            if (other.mIsSystemLocale) {
-                return 1;
-            }
-            if (mIsSystemLanguage) {
-                return -1;
-            }
-            if (other.mIsSystemLanguage) {
-                return 1;
-            }
-            if (TextUtils.isEmpty(mSubtypeName)) {
-                return 1;
-            }
-            if (TextUtils.isEmpty(other.mSubtypeName)) {
-                return -1;
-            }
-            return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
+            return (mIsSystemLanguage ? -1 : 0) - (other.mIsSystemLanguage ? -1 : 0);
         }
 
         @Override
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 16c2719..c8bf302 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -16,6 +16,7 @@
 package com.android.internal.logging;
 
 import android.content.Context;
+import android.metrics.LogMaker;
 import android.os.Build;
 import android.view.View;
 
@@ -37,7 +38,7 @@
         }
         EventLogTags.writeSysuiViewVisibility(category, 100);
         EventLogTags.writeSysuiMultiAction(
-                new LogBuilder(category)
+                new LogMaker(category)
                         .setType(MetricsEvent.TYPE_OPEN)
                         .serialize());
     }
@@ -48,7 +49,7 @@
         }
         EventLogTags.writeSysuiViewVisibility(category, 0);
         EventLogTags.writeSysuiMultiAction(
-                new LogBuilder(category)
+                new LogMaker(category)
                         .setType(MetricsEvent.TYPE_CLOSE)
                         .serialize());
     }
@@ -70,7 +71,7 @@
     public static void action(Context context, int category) {
         EventLogTags.writeSysuiAction(category, "");
         EventLogTags.writeSysuiMultiAction(
-                new LogBuilder(category)
+                new LogMaker(category)
                         .setType(MetricsEvent.TYPE_ACTION)
                         .serialize());
     }
@@ -78,7 +79,7 @@
     public static void action(Context context, int category, int value) {
         EventLogTags.writeSysuiAction(category, Integer.toString(value));
         EventLogTags.writeSysuiMultiAction(
-                new LogBuilder(category)
+                new LogMaker(category)
                         .setType(MetricsEvent.TYPE_ACTION)
                         .setSubtype(value)
                         .serialize());
@@ -87,16 +88,13 @@
     public static void action(Context context, int category, boolean value) {
         EventLogTags.writeSysuiAction(category, Boolean.toString(value));
         EventLogTags.writeSysuiMultiAction(
-                new LogBuilder(category)
+                new LogMaker(category)
                         .setType(MetricsEvent.TYPE_ACTION)
                         .setSubtype(value ? 1 : 0)
                         .serialize());
     }
 
-    public static void action(LogBuilder content) {
-        //EventLog.writeEvent(524292, content.serialize());
-        // Below would be the *right* way to do this, using the generated
-        // EventLogTags method, but that doesn't work.
+    public static void action(LogMaker content) {
         if (content.getType() == MetricsEvent.TYPE_UNKNOWN) {
             content.setType(MetricsEvent.TYPE_ACTION);
         }
@@ -109,7 +107,7 @@
             throw new IllegalArgumentException("Must define metric category");
         }
         EventLogTags.writeSysuiAction(category, pkg);
-        EventLogTags.writeSysuiMultiAction(new LogBuilder(category)
+        EventLogTags.writeSysuiMultiAction(new LogMaker(category)
                 .setType(MetricsEvent.TYPE_ACTION)
                 .setPackageName(pkg)
                 .serialize());
@@ -119,7 +117,7 @@
     public static void count(Context context, String name, int value) {
         EventLogTags.writeSysuiCount(name, value);
         EventLogTags.writeSysuiMultiAction(
-                new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+                new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
                         .setCounterName(name)
                         .setCounterValue(value)
                         .serialize());
@@ -129,7 +127,7 @@
     public static void histogram(Context context, String name, int bucket) {
         EventLogTags.writeSysuiHistogram(name, bucket);
         EventLogTags.writeSysuiMultiAction(
-                new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+                new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
                         .setCounterName(name)
                         .setCounterBucket(bucket)
                         .setCounterValue(1)
diff --git a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
index 7381ff0..91e968b 100644
--- a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
+++ b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java
@@ -15,9 +15,7 @@
  */
 package com.android.internal.logging.legacy;
 
-import android.os.Bundle;
-
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.util.HashMap;
@@ -34,20 +32,20 @@
     public static final int TYPE_HISTOGRAM = 2;
     public static final int TYPE_EVENT = 3;
 
-    private final Queue<LogBuilder> mQueue;
+    private final Queue<LogMaker> mQueue;
     private HashMap<String, Boolean> mConfig;
 
     public LegacyConversionLogger() {
         mQueue = new LinkedList<>();
     }
 
-    public Queue<LogBuilder> getEvents() {
+    public Queue<LogMaker> getEvents() {
         return mQueue;
     }
 
     @Override
     public void increment(String counterName) {
-        LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+        LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
                 .setCounterName(counterName)
                 .setCounterValue(1);
         mQueue.add(b);
@@ -55,7 +53,7 @@
 
     @Override
     public void incrementBy(String counterName, int value) {
-        LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
+        LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER)
                 .setCounterName(counterName)
                 .setCounterValue(value);
         mQueue.add(b);
@@ -63,7 +61,7 @@
 
     @Override
     public void incrementIntHistogram(String counterName, int bucket) {
-        LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+        LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
                 .setCounterName(counterName)
                 .setCounterBucket(bucket)
                 .setCounterValue(1);
@@ -72,7 +70,7 @@
 
     @Override
     public void incrementLongHistogram(String counterName, long bucket) {
-        LogBuilder b = new LogBuilder(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
+        LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM)
                 .setCounterName(counterName)
                 .setCounterBucket(bucket)
                 .setCounterValue(1);
@@ -80,16 +78,16 @@
     }
 
     @Override
-    public LogBuilder obtain() {
-        return new LogBuilder(MetricsEvent.VIEW_UNKNOWN);
+    public LogMaker obtain() {
+        return new LogMaker(MetricsEvent.VIEW_UNKNOWN);
     }
 
     @Override
-    public void dispose(LogBuilder proto) {
+    public void dispose(LogMaker proto) {
     }
 
     @Override
-    public void addEvent(LogBuilder proto) {
+    public void addEvent(LogMaker proto) {
         mQueue.add(proto);
     }
 
diff --git a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
index 6bede24..df08ee0 100644
--- a/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
+++ b/core/java/com/android/internal/logging/legacy/LockscreenGestureParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -62,7 +62,7 @@
                     category = GESTURE_TYPE_MAP[type];
                 }
                 if (category != MetricsEvent.VIEW_UNKNOWN) {
-                    LogBuilder proto = logger.obtain();
+                    LogMaker proto = logger.obtain();
                     proto.setCategory(category);
                     proto.setType(MetricsEvent.TYPE_ACTION);
                     proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
index 67b84e9..79f3eb8 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationActionClickedParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -47,7 +47,7 @@
                 if (mKey.parse((String) operands[0])) {
                     int index = (Integer) operands[1];
                     parseTimes(operands, 2);
-                    LogBuilder proto = logger.obtain();
+                    LogMaker proto = logger.obtain();
                     proto.setCategory(MetricsEvent.NOTIFICATION_ITEM_ACTION);
                     proto.setType(MetricsEvent.TYPE_ACTION);
                     proto.setSubtype(index);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
index 761197b..9548fb0 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationAlertParser.java
@@ -17,8 +17,8 @@
 
 import android.util.Log;
 
+import android.metrics.LogMaker;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.LogBuilder;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -58,7 +58,7 @@
                 final boolean blink = ((Integer) operands[3]) == 1;
 
                 if (mKey.parse(keyString)) {
-                    LogBuilder proto = logger.obtain();
+                    LogMaker proto = logger.obtain();
                     proto.setCategory(MetricsEvent.NOTIFICATION_ALERT);
                     proto.setType(MetricsEvent.TYPE_OPEN);
                     proto.setSubtype((buzz ? BUZZ : 0) | (beep ? BEEP : 0) | (blink ? BLINK : 0));
diff --git a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
index 0cab1a8..80eb004 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationCanceledParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -78,7 +78,7 @@
 
                 if (mKey.parse(keyString)) {
                     if (intentional) {
-                        LogBuilder proto = logger.obtain();
+                        LogMaker proto = logger.obtain();
                         proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
                         proto.setType(MetricsEvent.TYPE_DISMISS);
                         proto.setSubtype(reason);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
index eeae0a8..eee4701 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationClickedParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -46,7 +46,7 @@
             try {
                 if (mKey.parse((String) operands[0])) {
                     parseTimes(operands, 1);
-                    LogBuilder proto = logger.obtain();
+                    LogMaker proto = logger.obtain();
                     proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
                     proto.setType(MetricsEvent.TYPE_ACTION);
                     proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
index d44b8b1..84cd999 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationExpansionParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -52,7 +52,7 @@
                     if (!byUser || !expanded) {
                         return;
                     }
-                    LogBuilder proto = logger.obtain();
+                    LogMaker proto = logger.obtain();
                     proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
                     proto.setType(MetricsEvent.TYPE_DETAIL);
                     proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
index 662295b..a064a2e 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationPanelHiddenParser.java
@@ -15,7 +15,7 @@
  */
 package com.android.internal.logging.legacy;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -33,7 +33,7 @@
 
     @Override
     public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
-        LogBuilder proto = logger.obtain();
+        LogMaker proto = logger.obtain();
         proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
         proto.setType(MetricsEvent.TYPE_CLOSE);
         proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
index 0566f0b..4d19564 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationPanelRevealedParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -47,7 +47,7 @@
             }
         }
 
-        LogBuilder proto = logger.obtain();
+        LogMaker proto = logger.obtain();
         proto.setCategory(MetricsEvent.NOTIFICATION_PANEL);
         proto.setType(MetricsEvent.TYPE_OPEN);
         proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
index 9185b91..2d2cd909 100644
--- a/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
+++ b/core/java/com/android/internal/logging/legacy/NotificationVisibilityParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -53,7 +53,7 @@
                 }
 
                 if (mKey.parse(keyString)) {
-                    LogBuilder proto = logger.obtain();
+                    LogMaker proto = logger.obtain();
                     proto.setCategory(MetricsEvent.NOTIFICATION_ITEM);
                     proto.setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
                     proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
index 3bf0f9d..e9baf9b 100644
--- a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
+++ b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -48,7 +48,7 @@
                 boolean state = (((Integer) operands[0]).intValue()) == 1;
                 int why = ((Integer) operands[1]).intValue();
 
-                LogBuilder proto = logger.obtain();
+                LogMaker proto = logger.obtain();
                 proto.setCategory(MetricsEvent.SCREEN);
                 proto.setType(state ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
                 proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
index 23abec4..226253f 100644
--- a/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
+++ b/core/java/com/android/internal/logging/legacy/StatusBarStateParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -56,7 +56,7 @@
                     view = MetricsEvent.BOUNCER;
                 }
 
-                LogBuilder proto = logger.obtain();
+                LogMaker proto = logger.obtain();
                 proto.setCategory(view);
                 proto.setType(type);
                 proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
index 7f91f92..1148ee5 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiActionParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -60,7 +60,7 @@
             }
             if (operands.length > 0) {
                 int category = ((Integer) operands[0]).intValue();
-                LogBuilder proto = logger.obtain();
+                LogMaker proto = logger.obtain();
                 proto.setCategory(category);
                 proto.setType(MetricsEvent.TYPE_ACTION);
                 proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
index f9b2f49..0c77b7a 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java
@@ -17,8 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.metrics.LogMaker;
 
 /**
  * ...and one parser to rule them all.
@@ -39,7 +38,7 @@
     public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) {
         final boolean debug = Util.debug();
         try {
-            logger.addEvent(new LogBuilder(operands).setTimestamp(eventTimeMs));
+            logger.addEvent(new LogMaker(operands).setTimestamp(eventTimeMs));
         } catch (ClassCastException e) {
             if (debug) {
                 Log.e(TAG, "unexpected operand type: ", e);
diff --git a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
index 5d5aec0..1223b8d 100644
--- a/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
+++ b/core/java/com/android/internal/logging/legacy/SysuiViewVisibilityParser.java
@@ -17,7 +17,7 @@
 
 import android.util.Log;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -41,7 +41,7 @@
                 int category = ((Integer) operands[0]).intValue();
                 boolean visibility = ((Integer) operands[1]).intValue() != 0;
 
-                LogBuilder proto = logger.obtain();
+                LogMaker proto = logger.obtain();
                 proto.setCategory(category);
                 proto.setType(visibility ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE);
                 proto.setTimestamp(eventTimeMs);
diff --git a/core/java/com/android/internal/logging/legacy/TagParser.java b/core/java/com/android/internal/logging/legacy/TagParser.java
index c62d084..3bffdd5 100755
--- a/core/java/com/android/internal/logging/legacy/TagParser.java
+++ b/core/java/com/android/internal/logging/legacy/TagParser.java
@@ -17,8 +17,8 @@
 
 import android.util.Log;
 
+import android.metrics.LogMaker;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.LogBuilder;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -87,7 +87,7 @@
         }
     }
 
-   public void filltimes(LogBuilder proto) {
+   public void filltimes(LogMaker proto) {
         if (mSinceCreationMillis != 0) {
             proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS,
                     mSinceCreationMillis);
diff --git a/core/java/com/android/internal/logging/legacy/TronLogger.java b/core/java/com/android/internal/logging/legacy/TronLogger.java
index dabe314..ee9341a 100644
--- a/core/java/com/android/internal/logging/legacy/TronLogger.java
+++ b/core/java/com/android/internal/logging/legacy/TronLogger.java
@@ -15,7 +15,7 @@
  */
 package com.android.internal.logging.legacy;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 
 /**
  * An entity that knows how to log events and counters.
@@ -34,12 +34,12 @@
     void incrementLongHistogram(String counterName, long bucket);
 
     /** Obtain a SystemUiEvent proto, must release this with dispose() or addEvent(). */
-    LogBuilder obtain();
+    LogMaker obtain();
 
-    void dispose(LogBuilder proto);
+    void dispose(LogMaker proto);
 
     /** Submit an event to be logged. Logger will dispose of proto. */
-    void addEvent(LogBuilder proto);
+    void addEvent(LogMaker proto);
 
     /** Get a config flag. */
     boolean getConfig(String configName);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5fd68e6..c34c379 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -9713,7 +9713,7 @@
                 resetAllStatsLocked();
                 if (chargeUAh > 0) {
                     // Only use the reported coulomb charge value if it is supported and reported.
-                    mEstimatedBatteryCapacity = (int) ((level / 100.0) * (chargeUAh / 1000));
+                    mEstimatedBatteryCapacity = (int) ((chargeUAh / 1000) / (level / 100.0));
                 }
                 mDischargeStartLevel = level;
                 reset = true;
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index fb0edea..ebc2c71 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -84,7 +84,8 @@
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+                MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+                        event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                 return true;
             }
 
@@ -215,7 +216,8 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
                 if (!event.isCanceled()) {
-                    MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+                    MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+                            event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                 }
                 return true;
             }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e68ebc4..84195b2 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -1856,27 +1856,25 @@
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                int direction = 0;
-                switch (keyCode) {
-                    case KeyEvent.KEYCODE_VOLUME_UP:
-                        direction = AudioManager.ADJUST_RAISE;
-                        break;
-                    case KeyEvent.KEYCODE_VOLUME_DOWN:
-                        direction = AudioManager.ADJUST_LOWER;
-                        break;
-                    case KeyEvent.KEYCODE_VOLUME_MUTE:
-                        direction = AudioManager.ADJUST_TOGGLE_MUTE;
-                        break;
-                }
                 // If we have a session send it the volume command, otherwise
                 // use the suggested stream.
                 if (mMediaController != null) {
+                    int direction = 0;
+                    switch (keyCode) {
+                        case KeyEvent.KEYCODE_VOLUME_UP:
+                            direction = AudioManager.ADJUST_RAISE;
+                            break;
+                        case KeyEvent.KEYCODE_VOLUME_DOWN:
+                            direction = AudioManager.ADJUST_LOWER;
+                            break;
+                        case KeyEvent.KEYCODE_VOLUME_MUTE:
+                            direction = AudioManager.ADJUST_TOGGLE_MUTE;
+                            break;
+                    }
                     mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
                 } else {
-                    MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
-                            mVolumeControlStreamType, direction,
-                            AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
-                                    | AudioManager.FLAG_FROM_KEY);
+                    MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+                            event, mVolumeControlStreamType, false);
                 }
                 return true;
             }
@@ -1954,15 +1952,15 @@
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN: {
-                final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
-                        | AudioManager.FLAG_FROM_KEY;
                 // If we have a session send it the volume command, otherwise
                 // use the suggested stream.
                 if (mMediaController != null) {
+                    final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
+                            | AudioManager.FLAG_FROM_KEY;
                     mMediaController.adjustVolume(0, flags);
                 } else {
-                    MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
-                            mVolumeControlStreamType, 0, flags);
+                    MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+                            event, mVolumeControlStreamType, false);
                 }
                 return true;
             }
@@ -1971,7 +1969,8 @@
                 // doesn't have one of these.  In this case, we execute it here and
                 // eat the event instead, because we have mVolumeControlStreamType
                 // and they don't.
-                getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
+                MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
+                        event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                 return true;
             }
             // These are all the recognized media key codes in
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 087383d..0fe580a 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -33,6 +33,8 @@
 import android.graphics.drawable.VectorDrawable;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
 import android.text.style.TextAppearanceSpan;
 import android.util.Log;
 import android.util.Pair;
@@ -184,8 +186,24 @@
             SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
             for (Object span : spans) {
                 Object resultSpan = span;
-                if (span instanceof TextAppearanceSpan) {
-                    resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span);
+                if (resultSpan instanceof CharacterStyle) {
+                    resultSpan = ((CharacterStyle) span).getUnderlying();
+                }
+                if (resultSpan instanceof TextAppearanceSpan) {
+                    TextAppearanceSpan processedSpan = processTextAppearanceSpan(
+                            (TextAppearanceSpan) span);
+                    if (processedSpan != resultSpan) {
+                        resultSpan = processedSpan;
+                    } else {
+                        // we need to still take the orgininal for wrapped spans
+                        resultSpan = span;
+                    }
+                } else if (resultSpan instanceof ForegroundColorSpan) {
+                    ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
+                    int foregroundColor = originalSpan.getForegroundColor();
+                    resultSpan = new ForegroundColorSpan(processColor(foregroundColor));
+                } else {
+                    resultSpan = span;
                 }
                 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
                         ss.getSpanFlags(span));
@@ -416,6 +434,48 @@
         return color;
     }
 
+    public static int resolvePrimaryColor(Context context, int backgroundColor) {
+        boolean useDark = shouldUseDark(backgroundColor);
+        if (useDark) {
+            return context.getColor(
+                    com.android.internal.R.color.notification_primary_text_color_light);
+        } else {
+            return context.getColor(
+                    com.android.internal.R.color.notification_primary_text_color_dark);
+        }
+    }
+
+    public static int resolveSecondaryColor(Context context, int backgroundColor) {
+        boolean useDark = shouldUseDark(backgroundColor);
+        if (useDark) {
+            return context.getColor(
+                    com.android.internal.R.color.notification_secondary_text_color_light);
+        } else {
+            return context.getColor(
+                    com.android.internal.R.color.notification_secondary_text_color_dark);
+        }
+    }
+
+    public static int resolveActionBarColor(int backgroundColor) {
+        boolean useDark = shouldUseDark(backgroundColor);
+        final double[] result = ColorUtilsFromCompat.getTempDouble3Array();
+        ColorUtilsFromCompat.colorToLAB(backgroundColor, result);
+        if (useDark && result[0] < 97 || !useDark && result[0] < 4) {
+            result[0] = Math.min(100, result[0] + 7);
+        } else {
+            result[0] = Math.max(0, result[0] - 7);
+        }
+        return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
+    }
+
+    private static boolean shouldUseDark(int backgroundColor) {
+        boolean useDark = backgroundColor == Notification.COLOR_DEFAULT;
+        if (!useDark) {
+            useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
+        }
+        return useDark;
+    }
+
     /**
      * Framework copy of functions needed from android.support.v4.graphics.ColorUtils.
      */
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index ece5540..58fb145 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -43,6 +43,7 @@
 import android.util.Log;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.google.android.collect.Lists;
 
 import libcore.util.HexEncoding;
@@ -239,7 +240,8 @@
         mHandler = looper != null ? new Handler(looper) : null;
     }
 
-    private ILockSettings getLockSettings() {
+    @VisibleForTesting
+    public ILockSettings getLockSettings() {
         if (mLockSettingsService == null) {
             ILockSettings service = ILockSettings.Stub.asInterface(
                     ServiceManager.getService("lock_settings"));
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index ac8f413..0c863fd 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -39,27 +39,60 @@
 
 namespace android {
 
-static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
-    if (lang == NULL) {
-        return (jlong)new minikin::FontFamily(variant);
+struct NativeFamilyBuilder {
+    uint32_t langId;
+    int variant;
+    std::vector<minikin::Font> fonts;
+};
+
+static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
+    NativeFamilyBuilder* builder = new NativeFamilyBuilder();
+    if (lang != nullptr) {
+        ScopedUtfChars str(env, lang);
+        builder->langId = minikin::FontStyle::registerLanguageList(str.c_str());
+    } else {
+        builder->langId = minikin::FontStyle::registerLanguageList("");
     }
-    ScopedUtfChars str(env, lang);
-    uint32_t langId = minikin::FontStyle::registerLanguageList(str.c_str());
-    return (jlong)new minikin::FontFamily(langId, variant);
+    builder->variant = variant;
+    return reinterpret_cast<jlong>(builder);
 }
 
-static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) {
+static jlong FontFamily_create(jlong builderPtr) {
+    if (builderPtr == 0) {
+        return 0;
+    }
+    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+    minikin::FontFamily* family = new minikin::FontFamily(
+            builder->langId, builder->variant, std::move(builder->fonts));
+    delete builder;
+    return reinterpret_cast<jlong>(family);
+}
+
+static void FontFamily_abort(jlong builderPtr) {
+    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+    minikin::Font::clearElementsWithLock(&builder->fonts);
+    delete builder;
+}
+
+static void FontFamily_unref(jlong familyPtr) {
     minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
     fontFamily->Unref();
 }
 
-static jboolean addSkTypeface(minikin::FontFamily* family, sk_sp<SkTypeface> face,
-        const void* fontData, size_t fontSize, int ttcIndex) {
+static void addSkTypeface(jlong builderPtr, sk_sp<SkTypeface> face, const void* fontData,
+        size_t fontSize, int ttcIndex) {
     minikin::MinikinFont* minikinFont =
             new MinikinFontSkia(std::move(face), fontData, fontSize, ttcIndex);
-    bool result = family->addFont(minikinFont);
+    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+    int weight;
+    bool italic;
+    if (!minikin::FontFamily::analyzeStyle(minikinFont, &weight, &italic)) {
+        ALOGE("analyzeStyle failed. Using default style");
+        weight = 400;
+        italic = false;
+    }
+    builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight / 100, italic)));
     minikinFont->Unref();
-    return result;
 }
 
 static void release_global_ref(const void* /*data*/, void* context) {
@@ -85,7 +118,7 @@
     }
 }
 
-static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, jobject bytebuf,
+static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf,
         jint ttcIndex) {
     NPE_CHECK_RETURN_ZERO(env, bytebuf);
     const void* fontPtr = env->GetDirectBufferAddress(bytebuf);
@@ -112,8 +145,8 @@
         ALOGE("addFont failed to create font");
         return false;
     }
-    minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
-    return addSkTypeface(fontFamily, std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
+    addSkTypeface(builderPtr, std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
+    return true;
 }
 
 static struct {
@@ -126,7 +159,7 @@
     jfieldID mStyleValue;
 } gAxisClassInfo;
 
-static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong familyPtr,
+static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr,
         jobject font, jint ttcIndex, jobject listOfAxis, jint weight, jboolean isItalic) {
     NPE_CHECK_RETURN_ZERO(env, font);
 
@@ -178,10 +211,11 @@
         ALOGE("addFont failed to create font, invalid request");
         return false;
     }
-    minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
     minikin::MinikinFont* minikinFont =
-            new MinikinFontSkia(std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
-    fontFamily->addFont(minikinFont, minikin::FontStyle(weight / 100, isItalic));
+            new MinikinFontSkia(std::move(face), fontPtr, fontSize, ttcIndex);
+    NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+    builder->fonts.push_back(minikin::Font(minikinFont,
+            minikin::FontStyle(weight / 100, isItalic)));
     minikinFont->Unref();
     return true;
 }
@@ -190,7 +224,7 @@
     delete static_cast<Asset*>(context);
 }
 
-static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong familyPtr,
+static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong builderPtr,
         jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset) {
     NPE_CHECK_RETURN_ZERO(env, jassetMgr);
     NPE_CHECK_RETURN_ZERO(env, jpath);
@@ -233,14 +267,17 @@
         ALOGE("addFontFromAsset failed to create font %s", str.c_str());
         return false;
     }
-    minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
-    return addSkTypeface(fontFamily, std::move(face), buf, bufSize, /* ttcIndex */ 0);
+
+    addSkTypeface(builderPtr, std::move(face), buf, bufSize, 0 /* ttc index */);
+    return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gFontFamilyMethods[] = {
-    { "nCreateFamily",         "(Ljava/lang/String;I)J", (void*)FontFamily_create },
+    { "nInitBuilder",          "(Ljava/lang/String;I)J", (void*)FontFamily_initBuilder },
+    { "nCreateFamily",         "(J)J", (void*)FontFamily_create },
+    { "nAbort",                "(J)V", (void*)FontFamily_abort },
     { "nUnrefFamily",          "(J)V", (void*)FontFamily_unref },
     { "nAddFont",              "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont },
     { "nAddFontWeightStyle",   "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z",
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 74527d9..fadf8a4 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -223,16 +223,18 @@
 
 uint32_t android_hardware_HardwareBuffer_convertFromPixelFormat(uint32_t format) {
     switch (format) {
-        case PIXEL_FORMAT_RGBA_8888:
+        case HAL_PIXEL_FORMAT_RGBA_8888:
             return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
-        case PIXEL_FORMAT_RGBX_8888:
+        case HAL_PIXEL_FORMAT_RGBX_8888:
             return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM;
-        case PIXEL_FORMAT_RGB_565:
+        case HAL_PIXEL_FORMAT_RGB_565:
             return AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
-        case PIXEL_FORMAT_RGB_888:
+        case HAL_PIXEL_FORMAT_RGB_888:
             return AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM;
-        case PIXEL_FORMAT_RGBA_FP16:
+        case HAL_PIXEL_FORMAT_RGBA_FP16:
             return AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT;
+        case HAL_PIXEL_FORMAT_BLOB:
+            return AHARDWAREBUFFER_FORMAT_BLOB;
         default:
             ALOGE("Unknown pixel format %u", format);
             return 0;
@@ -242,15 +244,17 @@
 uint32_t android_hardware_HardwareBuffer_convertToPixelFormat(uint32_t format) {
     switch (format) {
         case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
-            return PIXEL_FORMAT_RGBA_8888;
+            return HAL_PIXEL_FORMAT_RGBA_8888;
         case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
-            return PIXEL_FORMAT_RGBX_8888;
+            return HAL_PIXEL_FORMAT_RGBX_8888;
         case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
-            return PIXEL_FORMAT_RGB_565;
+            return HAL_PIXEL_FORMAT_RGB_565;
         case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
-            return PIXEL_FORMAT_RGB_888;
+            return HAL_PIXEL_FORMAT_RGB_888;
         case AHARDWAREBUFFER_FORMAT_R16G16B16A16_SFLOAT:
-            return PIXEL_FORMAT_RGBA_FP16;
+            return HAL_PIXEL_FORMAT_RGBA_FP16;
+        case AHARDWAREBUFFER_FORMAT_BLOB:
+            return HAL_PIXEL_FORMAT_BLOB;
         default:
             ALOGE("Unknown AHardwareBuffer format %u", format);
             return 0;
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 271f24b..bc2fc1c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -26,6 +26,7 @@
 #include <gui/Sensor.h>
 #include <gui/SensorEventQueue.h>
 #include <gui/SensorManager.h>
+#include <cutils/native_handle.h>
 #include <utils/Log.h>
 #include <utils/Looper.h>
 #include <utils/Vector.h>
@@ -243,6 +244,54 @@
     return mgr->isDataInjectionEnabled();
 }
 
+static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+        jlong size, jint channelType, jlongArray channelData) {
+    jint ret = -1;
+    jsize len = _env->GetArrayLength(channelData);
+    if (len > 2) {
+        jlong *data = _env->GetLongArrayElements(channelData, NULL);
+        if (data != nullptr) {
+            // construct native handle from jlong*
+            jlong numFd = data[0];
+            jlong numInt = data[1];
+            if ((numFd + numInt + 2) == len) {
+                native_handle_t *nativeHandle = native_handle_create(numFd, numInt);
+                if (nativeHandle != nullptr) {
+                    const jlong *readPointer = data + 2;
+                    int *writePointer = nativeHandle->data;
+                    size_t n = static_cast<size_t>(numFd + numInt);
+                    while (n--) {
+                        // native type of data is int, jlong is just to ensure Java does not
+                        // truncate data on 64-bit system. The cast here is safe.
+                        *writePointer++ = static_cast<int>(*readPointer++);
+                    }
+
+                    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+                    ret = mgr->createDirectChannel(size, channelType, nativeHandle);
+
+                    // do not native_handle_close() here as handle is owned by java
+                    native_handle_delete(nativeHandle);
+                }
+            }
+            // unidirectional parameter passing, thus JNI_ABORT
+            _env->ReleaseLongArrayElements(channelData, data, JNI_ABORT);
+        }
+    }
+    return ret;
+}
+
+static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+        jint channelHandle) {
+    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+    mgr->destroyDirectChannel(channelHandle);
+}
+
+static jint nativeConfigDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+        jint channelHandle, jint sensorHandle, jint rate) {
+    SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+    return mgr->configureDirectChannel(channelHandle, sensorHandle, rate);
+}
+
 //----------------------------------------------------------------------------
 
 class Receiver : public LooperCallback {
@@ -447,7 +496,19 @@
 
     {"nativeIsDataInjectionEnabled",
             "(J)Z",
-            (void*)nativeIsDataInjectionEnabled},
+            (void*)nativeIsDataInjectionEnabled },
+
+    {"nativeCreateDirectChannel",
+            "(JJI[J)I",
+            (void*)nativeCreateDirectChannel },
+
+    {"nativeDestroyDirectChannel",
+            "(JI)V",
+            (void*)nativeDestroyDirectChannel },
+
+    {"nativeConfigDirectChannel",
+            "(JIII)I",
+            (void*)nativeConfigDirectChannel },
 };
 
 static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 793d132..0c7f5a1 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -509,7 +509,7 @@
     sp<MemoryDealer> memoryDealer;
     sp<IMemory> memory;
     size_t size;
-    sound_model_handle_t handle;
+    sound_model_handle_t handle = 0;
     jobject jUuid;
     jstring jUuidString;
     const char *nUuidString;
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 86c4df7..b2c8168 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -972,6 +972,18 @@
 }
 
 // ----------------------------------------------------------------------------
+static jint android_media_AudioTrack_get_flags(JNIEnv *env,  jobject thiz) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+            "Unable to retrieve AudioTrack pointer for getFlags()");
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    return (jint)lpTrack->getFlags();
+}
+
+// ----------------------------------------------------------------------------
 static jint android_media_AudioTrack_get_timestamp(JNIEnv *env,  jobject thiz, jlongArray jTimestamp) {
     sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
 
@@ -1212,6 +1224,7 @@
     {"native_get_position",  "()I",      (void *)android_media_AudioTrack_get_position},
     {"native_get_latency",   "()I",      (void *)android_media_AudioTrack_get_latency},
     {"native_get_underrun_count", "()I",      (void *)android_media_AudioTrack_get_underrun_count},
+    {"native_get_flags",     "()I",      (void *)android_media_AudioTrack_get_flags},
     {"native_get_timestamp", "([J)I",    (void *)android_media_AudioTrack_get_timestamp},
     {"native_set_loop",      "(III)I",   (void *)android_media_AudioTrack_set_loop},
     {"native_reload_static", "()I",      (void *)android_media_AudioTrack_reload},
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 3eccc42..f221392 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -43,58 +43,6 @@
         ? (reinterpret_cast<RenderNode*>(renderNodePtr)->setPropertyFieldsDirty(dirtyFlag), true) \
         : false)
 
-static JNIEnv* getenv(JavaVM* vm) {
-    JNIEnv* env;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
-    }
-    return env;
-}
-
-static jfieldID gRenderNode_validFieldID;
-
-class RenderNodeContext : public VirtualLightRefBase {
-public:
-    RenderNodeContext(JNIEnv* env, jobject jobjRef) {
-        env->GetJavaVM(&mVm);
-        // This holds a weak ref because otherwise there's a cyclic global ref
-        // with this holding a strong global ref to the view which holds
-        // a strong ref to RenderNode which holds a strong ref to this.
-        mWeakRef = env->NewWeakGlobalRef(jobjRef);
-    }
-
-    virtual ~RenderNodeContext() {
-        JNIEnv* env = getenv(mVm);
-        env->DeleteWeakGlobalRef(mWeakRef);
-    }
-
-    jobject acquireLocalRef(JNIEnv* env) {
-        return env->NewLocalRef(mWeakRef);
-    }
-
-private:
-    JavaVM* mVm;
-    jweak mWeakRef;
-};
-
-// Called by ThreadedRenderer's JNI layer
-void onRenderNodeRemoved(JNIEnv* env, RenderNode* node) {
-    auto context = reinterpret_cast<RenderNodeContext*>(node->getUserContext());
-    if (!context) return;
-    jobject jnode = context->acquireLocalRef(env);
-    if (!jnode) {
-        // The owning node has been GC'd, release the context
-        node->setUserContext(nullptr);
-        return;
-    }
-
-    node->setStagingDisplayList(nullptr, nullptr);
-    // Update the valid field, since native has already removed
-    // the staging DisplayList
-    env->SetBooleanField(jnode, gRenderNode_validFieldID, false);
-    env->DeleteLocalRef(jnode);
-}
-
 // ----------------------------------------------------------------------------
 // DisplayList view properties
 // ----------------------------------------------------------------------------
@@ -109,8 +57,7 @@
     return renderNode->getDebugSize();
 }
 
-static jlong android_view_RenderNode_create(JNIEnv* env, jobject thiz,
-        jstring name) {
+static jlong android_view_RenderNode_create(JNIEnv* env, jobject, jstring name) {
     RenderNode* renderNode = new RenderNode();
     renderNode->incStrong(0);
     if (name != NULL) {
@@ -118,7 +65,6 @@
         renderNode->setName(textArray);
         env->ReleaseStringUTFChars(name, textArray);
     }
-    renderNode->setUserContext(new RenderNodeContext(env, thiz));
     return reinterpret_cast<jlong>(renderNode);
 }
 
@@ -133,22 +79,13 @@
 
 static void android_view_RenderNode_setDisplayList(JNIEnv* env,
         jobject clazz, jlong renderNodePtr, jlong displayListPtr) {
-    class RemovedObserver : public TreeObserver {
-    public:
-        virtual void onMaybeRemovedFromTree(RenderNode* node) override {
-            maybeRemovedNodes.insert(sp<RenderNode>(node));
-        }
-        std::set< sp<RenderNode> > maybeRemovedNodes;
-    };
-
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     DisplayList* newData = reinterpret_cast<DisplayList*>(displayListPtr);
-    RemovedObserver observer;
-    renderNode->setStagingDisplayList(newData, &observer);
-    for (auto& node : observer.maybeRemovedNodes) {
-        if (node->hasParents()) continue;
-        onRenderNodeRemoved(env, node.get());
-    }
+    renderNode->setStagingDisplayList(newData);
+}
+
+static jboolean android_view_RenderNode_isValid(jlong renderNodePtr) {
+    return reinterpret_cast<RenderNode*>(renderNodePtr)->isValid();
 }
 
 // ----------------------------------------------------------------------------
@@ -622,6 +559,7 @@
 // ----------------------------------------------------------------------------
 // Critical JNI via @CriticalNative annotation in RenderNode.java
 // ----------------------------------------------------------------------------
+    { "nIsValid",              "(J)Z",   (void*) android_view_RenderNode_isValid },
     { "nSetLayerType",         "(JI)Z",  (void*) android_view_RenderNode_setLayerType },
     { "nSetLayerPaint",        "(JJ)Z",  (void*) android_view_RenderNode_setLayerPaint },
     { "nSetStaticMatrix",      "(JJ)Z",  (void*) android_view_RenderNode_setStaticMatrix },
@@ -692,8 +630,6 @@
             "updateWindowPosition_renderWorker", "(JIIII)V");
     gSurfaceViewPositionLostMethod = GetMethodIDOrDie(env, clazz,
             "windowPositionLost_uiRtSync", "(J)V");
-    clazz = FindClassOrDie(env, "android/view/RenderNode");
-    gRenderNode_validFieldID = GetFieldIDOrDie(env, clazz, "mValid", "Z");
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
 
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 96e6f81..bc5f847 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -532,7 +532,7 @@
     UiFrameInfoBuilder(proxy->frameInfo())
             .setVsync(vsync, vsync)
             .addFlag(FrameInfoFlags::SurfaceCanvas);
-    proxy->syncAndDrawFrame(nullptr);
+    proxy->syncAndDrawFrame();
 }
 
 static void destroy(JNIEnv* env, jclass clazz, jlong rendererPtr) {
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index c00bcd4..d37f96a 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -71,31 +71,6 @@
     return env;
 }
 
-// TODO: Clean this up, it's a bit odd to need to call over to
-// rendernode's jni layer. Probably means RootRenderNode should be pulled
-// into HWUI with appropriate callbacks for the various JNI hooks so
-// that RenderNode's JNI layer can handle its own thing
-void onRenderNodeRemoved(JNIEnv* env, RenderNode* node);
-
-class ScopedRemovedRenderNodeObserver : public TreeObserver {
-public:
-    explicit ScopedRemovedRenderNodeObserver(JNIEnv* env) : mEnv(env) {}
-    ~ScopedRemovedRenderNodeObserver() {
-        for (auto& node : mMaybeRemovedNodes) {
-            if (node->hasParents()) continue;
-            onRenderNodeRemoved(mEnv, node.get());
-        }
-    }
-
-    virtual void onMaybeRemovedFromTree(RenderNode* node) override {
-        mMaybeRemovedNodes.insert(sp<RenderNode>(node));
-    }
-
-private:
-    JNIEnv* mEnv;
-    std::set< sp<RenderNode> > mMaybeRemovedNodes;
-};
-
 class OnFinishedEvent {
 public:
     OnFinishedEvent(BaseRenderNodeAnimator* animator, AnimationListener* listener)
@@ -715,18 +690,16 @@
             "Mismatched size expectations, given %d expected %d",
             frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    ScopedRemovedRenderNodeObserver observer(env);
     env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
-    return proxy->syncAndDrawFrame(&observer);
+    return proxy->syncAndDrawFrame();
 }
 
 static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jlong rootNodePtr) {
-    ScopedRemovedRenderNodeObserver observer(env);
     RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
     rootRenderNode->destroy();
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    proxy->destroy(&observer);
+    proxy->destroy();
 }
 
 static void android_view_ThreadedRenderer_registerAnimatingRenderNode(JNIEnv* env, jobject clazz,
@@ -758,10 +731,9 @@
 
 static void android_view_ThreadedRenderer_buildLayer(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jlong nodePtr) {
-    ScopedRemovedRenderNodeObserver observer(env);
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
     RenderNode* node = reinterpret_cast<RenderNode*>(nodePtr);
-    proxy->buildLayer(node, &observer);
+    proxy->buildLayer(node);
 }
 
 static jboolean android_view_ThreadedRenderer_copyLayerInto(JNIEnv* env, jobject clazz,
@@ -796,9 +768,8 @@
 
 static void android_view_ThreadedRenderer_destroyHardwareResources(JNIEnv* env, jobject clazz,
         jlong proxyPtr) {
-    ScopedRemovedRenderNodeObserver observer(env);
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    proxy->destroyHardwareResources(&observer);
+    proxy->destroyHardwareResources();
 }
 
 static void android_view_ThreadedRenderer_trimMemory(JNIEnv* env, jobject clazz,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 070a2d9..5c65241 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -247,6 +247,11 @@
 
 static void DropCapabilitiesBoundingSet(JNIEnv* env) {
   for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
+    // Keep CAP_SYS_PTRACE in our bounding set so crash_dump can gain it.
+    if (i == CAP_SYS_PTRACE) {
+      continue;
+    }
+
     int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
     if (rc == -1) {
       if (errno == EINVAL) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6d48862..1e7baafa 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -443,6 +443,7 @@
     <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
     <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
 
+    <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
     <protected-broadcast android:name="EventConditionProvider.EVALUATE" />
     <protected-broadcast android:name="SnoozeHelper.EVALUATE" />
@@ -514,6 +515,7 @@
     <!-- Added in O -->
     <!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
     <protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
+    <protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -1607,6 +1609,16 @@
     <permission android:name="android.permission.RECEIVE_STK_COMMANDS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by an ImsService to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature|privileged
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_IMS_SERVICE"
+        android:protectionLevel="signature|privileged" />
+
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -2645,6 +2657,21 @@
     <permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to set the volume key long-press listener.
+         <p>When it's set, the application will receive the volume key long-press event
+         instead of changing volume.</p>
+         <p>Not for use by third-party applications</p> -->
+    <permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"
+        android:protectionLevel="signature|privileged|development" />
+
+    <!-- @SystemApi @hide Allows an application to set media key event listener.
+         <p>When it's set, the application will receive the media key event before
+         any other media sessions. If the event is handled by the listener, other sessions
+         cannot get the event.</p>
+         <p>Not for use by third-party applications</p> -->
+    <permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- @SystemApi Required to be able to disable the device (very dangerous!).
          <p>Not for use by third-party applications.
          @hide
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 516f252..182ba24 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2016 The Android Open Source Project
+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.
@@ -14,24 +14,27 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="512dp"
-        android:height="512dp"
+        android:width="480dp"
+        android:height="480dp"
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
     <path
-        android:fillColor="#FFc7d4b6"
-        android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+        android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
+        android:fillAlpha="0.066"
+        android:fillColor="#000000"/>
     <path
-        android:fillColor="#FFfbd3cb"
-        android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+        android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
+        android:fillColor="#FFC107"/>
     <path
-        android:fillColor="#40000000"
-        android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
+        android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
+        android:fillColor="#FE9F00"/>
     <path
-        android:fillColor="#40000000"
-        android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
+        android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
+        android:fillColor="#FED44F"/>
     <path
-        android:fillColor="#FFe0e0d6"
-        android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+        android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
+        android:fillColor="#FFC107"/>
+    <path
+        android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
+        android:fillColor="#FFFFFF"/>
 </vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 5043cba..89e42e6 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2016 The Android Open Source Project
+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.
@@ -16,21 +16,17 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="24dp"
         android:height="24dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
     <path
-        android:fillColor="#A0FFFFFF"
-        android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+        android:fillColor="#FF000000"
+        android:pathData="M12.0,12.0m-10.0,0.0a10.0,10.0,0,1,1,20.0,0.0a10.0,10.0,0,1,1,-20.0,0.0"
+        android:fillAlpha="0.25"/>
     <path
-        android:fillColor="#A0FFFFFF"
-        android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+        android:fillColor="#FF000000"
+        android:pathData="M12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 Z M12,18.5 C15.5898509,18.5 18.5,15.5898509 18.5,12 C18.5,8.41014913 15.5898509,5.5 12,5.5 C8.41014913,5.5 5.5,8.41014913 5.5,12 C5.5,15.5898509 8.41014913,18.5 12,18.5 Z"/>
     <path
-        android:fillColor="#40000000"
-        android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
-    <path
-        android:fillColor="#40000000"
-        android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+        android:fillColor="#FF000000"
+        android:pathData="M12,18.5 C8.41014913,18.5 5.5,15.5898509 5.5,12 C5.5,8.41014913 8.41014913,5.5 12,5.5 C15.5898509,5.5 18.5,8.41014913 18.5,12 C18.5,15.5898509 15.5898509,18.5 12,18.5 Z M12,15 C13.6568542,15 15,13.6568542 15,12 C15,10.3431458 13.6568542,9 12,9 C10.3431458,9 9,10.3431458 9,12 C9,13.6568542 10.3431458,15 12,15 Z"
+        android:fillAlpha="0.25"/>
 </vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
index bd1eb41..606b686 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
index aedb12c..a4c5bc2 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
index 6f07cb5..9c27833 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
index c41a8ca..6d693f1 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
index ec0a52f..c48fa36 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
index 78bd0a0..0868845 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
index 78085c2f..657f5ed 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
index f660ab7..e2971aa 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
index 43b8653..b073be3 100644
--- a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
+++ b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
@@ -13,11 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:viewportWidth="18"
     android:viewportHeight="18"
-    android:width="18dp"
-    android:height="18dp">
+    android:width="26dp"
+    android:height="24dp">
     <group
         android:translateX="386"
         android:translateY="-298">
diff --git a/core/res/res/drawable/ic_wifi_signal_0.xml b/core/res/res/drawable/ic_wifi_signal_0.xml
new file mode 100644
index 0000000..e732a8d
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:width="26dp"
+  android:height="24dp"
+  android:viewportWidth="26"
+  android:viewportHeight="24">
+  <path
+    android:fillAlpha="0.3"
+    android:fillColor="#FFFFFF"
+    android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_wifi_signal_1.xml b/core/res/res/drawable/ic_wifi_signal_1.xml
new file mode 100644
index 0000000..3d006953
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_1.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="26dp"
+    android:height="24dp"
+    android:viewportWidth="26"
+    android:viewportHeight="24">
+    <path
+        android:fillAlpha="0.3"
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.1,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.5,6.5L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.1,22.0l5.5,-6.8c-0.2,-0.2 -2.3,-1.9 -5.5,-1.9s-5.3,1.8 -5.5,1.9L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0L13.1,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_2.xml b/core/res/res/drawable/ic_wifi_signal_2.xml
new file mode 100644
index 0000000..2cce9e9
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_2.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="26dp"
+    android:height="24dp"
+    android:viewportWidth="26"
+    android:viewportHeight="24">
+    <path
+        android:fillAlpha="0.3"
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.0,22.0l7.6,-9.4C20.3,12.4 17.4,10.0 13.0,10.0s-7.3,2.4 -7.6,2.7L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_3.xml b/core/res/res/drawable/ic_wifi_signal_3.xml
new file mode 100644
index 0000000..d3b3d3a
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_3.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="26dp"
+    android:height="24dp"
+    android:viewportWidth="26"
+    android:viewportHeight="24">
+    <path
+        android:fillAlpha="0.3"
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.0,22.0l9.2,-11.4c-0.4,-0.3 -3.9,-3.2 -9.2,-3.2s-8.9,3.0 -9.2,3.2L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_wifi_signal_4.xml b/core/res/res/drawable/ic_wifi_signal_4.xml
new file mode 100644
index 0000000..aca4551
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_signal_4.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2015 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="26dp"
+    android:height="24dp"
+    android:viewportWidth="26"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.0,22.0L25.6,6.5C25.1,6.1 20.3,2.1 13.0,2.1S0.9,6.1 0.4,6.5L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0L13.0,22.0z"/>
+</vector>
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 1f71a18..0dfeb62 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -20,10 +20,11 @@
     android:id="@+id/notification_header"
     android:orientation="horizontal"
     android:layout_width="wrap_content"
-    android:layout_height="53dp"
+    android:layout_height="48dp"
     android:clipChildren="false"
     android:paddingTop="10dp"
-    android:paddingBottom="16dp"
+    android:paddingBottom="11dp"
+    android:layout_marginBottom="5dp"
     android:paddingStart="@dimen/notification_content_margin_start"
     android:paddingEnd="16dp">
     <com.android.internal.widget.CachingIconView
diff --git a/core/res/res/values-mcc214-mnc01/config.xml b/core/res/res/values-mcc214-mnc01/config.xml
index 895b770..24150a7 100644
--- a/core/res/res/values-mcc214-mnc01/config.xml
+++ b/core/res/res/values-mcc214-mnc01/config.xml
@@ -40,27 +40,4 @@
       <item>INTERNET,airtelnet.es,,,vodafone,vodafone,,,,,214,01,1,DUN</item>
     </string-array>
 
-    <string-array translatable="false" name="config_operatorConsideredNonRoaming">
-        <item>21402</item>
-        <item>21403</item>
-        <item>21404</item>
-        <item>21405</item>
-        <item>21406</item>
-        <item>21407</item>
-        <item>21408</item>
-        <item>21409</item>
-        <item>21410</item>
-        <item>21411</item>
-        <item>21412</item>
-        <item>21413</item>
-        <item>21414</item>
-        <item>21415</item>
-        <item>21416</item>
-        <item>21417</item>
-        <item>21418</item>
-        <item>21419</item>
-        <item>21420</item>
-        <item>21421</item>
-    </string-array>
-
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index df7a5f5..c333629 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2274,13 +2274,16 @@
         <!-- Sets the padding, in pixels, of the end edge; see {@link android.R.attr#padding}. -->
         <attr name="paddingEnd" format="dimension" />
 
-        <!-- Boolean that controls whether a view can take focus.  By default the user can not
-             move focus to a view; by setting this attribute to true the view is
-             allowed to take focus.  This value does not impact the behavior of
+        <!-- Controls whether a view can take focus.  By default, this is "auto" which lets the
+             framework determine whether a user can move focus to a view.  By setting this attribute
+             to true the view is allowed to take focus. By setting it to "false" the view will not
+             take focus. This value does not impact the behavior of
              directly calling {@link android.view.View#requestFocus}, which will
              always request focus regardless of this view.  It only impacts where
              focus navigation will try to move focus. -->
-        <attr name="focusable" format="boolean" />
+        <attr name="focusable" format="boolean|enum">
+            <enum name="auto" value="0x00000010" />
+        </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
@@ -5836,6 +5839,14 @@
         <attr name="color" />
     </declare-styleable>
 
+    <!-- Drawable used to draw masked icons with foreground and background layers. -->
+    <declare-styleable name="MaskableIconDrawableLayer">
+        <!-- The color to use for the layer, only if drawable is not defined. -->
+        <attr name="color" />
+        <!-- The drawable to use for the layer. -->
+        <attr name="drawable" />
+     </declare-styleable>
+
     <!-- Drawable used to show animated touch feedback. -->
     <declare-styleable name="RippleDrawable">
         <!-- The color to use for ripple effects. This attribute is required. -->
@@ -7912,7 +7923,6 @@
         <attr name="queryBackground" format="reference" />
         <!-- Background for the section containing the action (e.g. voice search) -->
         <attr name="submitBackground" format="reference" />
-        <attr name="focusable" />
     </declare-styleable>
 
     <declare-styleable name="Switch">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 5235116..0dde91b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -864,9 +864,8 @@
         <flag name="density" value="0x1000" />
         <!-- The layout direction has changed. For example going from LTR to RTL. -->
         <flag name="layoutDirection" value="0x2000" />
-        <!-- The colorimetry capabilities of the screen have changed (color gamut
-             or dynamic range). -->
-        <flag name="colorimetry" value="0x4000" />
+        <!-- The color mode of the screen has changed (color gamut or dynamic range). -->
+        <flag name="colorMode" value="0x4000" />
         <!-- The font scaling factor has changed, that is the user has
              selected a new global font size. -->
         <flag name="fontScale" value="0x40000000" />
@@ -1002,6 +1001,13 @@
         <enum name="preferExternal" value="2" />
     </attr>
 
+    <!-- If set to <code>true</code>, indicates to the platform that any split APKs
+         installed for this application should be loaded into their own Context
+         objects and not appear in the base application's Context.
+
+         <p>The default value of this attribute is <code>false</code>. -->
+    <attr name="isolatedSplits" format="boolean" />
+
     <!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
          {@code <application>} tag. If specified on the {@code <application>}
          tag these will be considered defaults for all activities in the
@@ -1159,18 +1165,15 @@
          resizeable activities when in multi-window mode. -->
     <attr name="resizeableActivity" format="boolean" />
 
-    <!-- Indicates that the activity supports the picture-in-picture (PiP) form of multi-window.
-         While it makes sense to be able to resize most activities types in multi-window mode when
-         {@link android.R.attr#resizeableActivity} is set. It only makes sense to put specific types
-         of activities in PiP mode of multi-window. For example, activities that play video. When
-         set the activity will be allowed to enter PiP mode when the system deems it appropriate on
-         devices that support PiP.
+    <!-- Indicates that the activity specifically supports the picture-in-picture form of
+         multi-window. If true, this activity will support entering picture-in-picture, but will
+         only support split-screen and other forms of multi-window if
+         {@link android.R.attr#resizeableActivity} is also set to true.
 
-         <p>The default value is <code>false</code> for applications with
-         <code>targetSdkVersion</code> lesser than {@link android.os.Build.VERSION_CODES#N} and
-         <code>true</code> otherwise.
+         Note that your activity may still be resized even if this attribute is true and
+         {@link android.R.attr#resizeableActivity} is false.
 
-         <p>NOTE: Attribute is only used if {@link android.R.attr#resizeableActivity} is true. -->
+         <p>The default value is <code>false</code>.  -->
     <attr name="supportsPictureInPicture" format="boolean" />
 
     <!-- This value indicates how tasks rooted at this activity will behave in lockTask mode.
@@ -1240,6 +1243,10 @@
     <!-- An XML resource with the application's Network Security Config. -->
     <attr name="networkSecurityConfig" format="reference" />
 
+    <!-- When an application is partitioned into splits, this is the name of the
+         split that contains the defined component. -->
+    <attr name="splitName" format="string" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -1266,6 +1273,7 @@
         <attr name="sharedUserId" />
         <attr name="sharedUserLabel" />
         <attr name="installLocation" />
+        <attr name="isolatedSplits" />
     </declare-styleable>
 
     <!-- The <code>application</code> tag describes application-level components
@@ -1823,6 +1831,8 @@
         <attr name="singleUser" />
         <attr name="directBootAware" />
         <attr name="visibleToInstantApps" />
+        <!-- The code for this component is located in the given split. -->
+        <attr name="splitName" />
     </declare-styleable>
 
     <!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -1913,6 +1923,8 @@
              must also be {@link android.R.attr#exported} if this flag is set. -->
         <attr name="externalService" format="boolean" />
         <attr name="visibleToInstantApps" />
+        <!-- The code for this component is located in the given split. -->
+        <attr name="splitName" />
     </declare-styleable>
 
     <!-- The <code>receiver</code> tag declares an
@@ -2036,6 +2048,18 @@
         <attr name="onTopLauncher" format="boolean" />
         <attr name="rotationAnimation" />
         <attr name="visibleToInstantApps" />
+        <!-- The code for this component is located in the given split. -->
+        <attr name="splitName" />
+        <!-- Specify the color mode the activity desires. The requested color mode may be ignored
+             depending on the capabilities of the display the activity is displayed on. -->
+        <attr name="colorMode">
+            <!-- The default color mode (typically sRGB, low-dynamic range). -->
+            <enum name="default" value="0" />
+            <!-- Wide color gamut color mode. -->
+            <enum name="wideColorGamut" value="1" />
+            <!-- High dynamic range color mode. -->
+            <enum name="hdr" value="2" />
+        </attr>
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
@@ -2446,4 +2470,8 @@
         <attr name="hash" format="string" />
     </declare-styleable>
 
+    <declare-styleable name="AndroidManifestUsesSplit" parent="AndroidManifest">
+        <attr name="name" format="string" />
+    </declare-styleable>
+
 </resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 4164e5d..b28c6f2 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -89,7 +89,7 @@
     <color name="perms_dangerous_perm_color">#33b5e5</color>
     <color name="shadow">#cc222222</color>
     <color name="perms_costs_money">#fff4511e</color>
-    
+
     <!-- For search-related UIs -->
     <color name="search_url_text_normal">#7fa87f</color>
     <color name="search_url_text_selected">@android:color/black</color>
@@ -132,6 +132,10 @@
     <drawable name="notification_template_icon_low_bg">#0cffffff</drawable>
     <drawable name="notification_template_divider">#29000000</drawable>
     <drawable name="notification_template_divider_media">#29ffffff</drawable>
+    <color name="notification_primary_text_color_light">@color/primary_text_default_material_light</color>
+    <color name="notification_primary_text_color_dark">@color/primary_text_default_material_dark</color>
+    <color name="notification_secondary_text_color_light">@color/secondary_text_material_light</color>
+    <color name="notification_secondary_text_color_dark">@color/secondary_text_material_dark</color>
 
     <color name="notification_material_background_color">#ffffffff</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7de48d3..6a8b556 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2090,6 +2090,7 @@
         <item>com.android.server.notification.ImportanceExtractor</item>
         <item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
         <item>com.android.server.notification.VisibilityExtractor</item>
+        <item>com.android.server.notification.BadgeExtractor</item>
     </string-array>
 
     <!-- Flag indicating that this device does not rotate and will always remain in its default
@@ -2624,8 +2625,14 @@
     <!-- Component that is the default launcher when demo mode is enabled. -->
     <string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string>
 
-    <!-- Hashed password (SHA-256) used to restrict demo mode operation -->
-    <string name="config_demoModePassword" translatable="false"></string>
+    <!-- Hashed password (SHA-256) used to restrict carrier demo mode operation. -->
+    <string name="config_carrierDemoModePassword" translatable="false"></string>
+
+    <!-- Secure setting used to activate carrier demo mode. -->
+    <string name="config_carrierDemoModeSetting" translatable="false"></string>
+
+    <!-- List of packages to enable in carrier demo mode (comma separated). -->
+    <string name="config_carrierDemoModePackages" translatable="false"></string>
 
     <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
     <bool name="config_useRoundIcon">false</bool>
@@ -2711,6 +2718,9 @@
     <!-- Component name of the default cell broadcast receiver -->
     <string name="config_defaultCellBroadcastReceiverComponent" translatable="false">com.android.cellbroadcastreceiver/.PrivilegedCellBroadcastReceiver</string>
 
+    <!-- Specifies the path that is used by MaskableIconDrawable class to crop launcher icons. -->
+    <string name="config_icon_mask" translatable="false">"M50,0L100,0 100,100 0,100 0,0z"</string>
+
     <!-- The component name, flattened to a string, for the default accessibility service to be
          enabled by the accessibility shortcut. This service must be trusted, as it can be activated
          without explicit consent of the user. If no accessibility service with the specified name
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index f351b70..613616f 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -97,6 +97,7 @@
   <item type="id" name="redo" />
   <item type="id" name="replaceText" />
   <item type="id" name="shareText" />
+  <item type="id" name="textAssist" />
   <item type="id" name="selection_start_handle" />
   <item type="id" name="selection_end_handle" />
   <item type="id" name="insertion_handle" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 40d0e45..1146871 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2787,12 +2787,16 @@
         <public name="supportsDismissingWindow" />
         <public name="restartOnConfigChanges" />
         <public name="certDigest" />
+        <public name="splitName" />
+        <public name="colorMode" />
+        <public name="isolatedSplits" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
     </public-group>
 
     <public-group type="id" first-id="0x01020041">
+        <public name="textAssist" />
     </public-group>
 
     <public type="attr" name="primaryContentAlpha" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d09b190..ac8c896 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -578,6 +578,9 @@
     <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
     <string name="notification_header_divider_symbol" translatable="false">•</string>
 
+    <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
+    <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
+
     <!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen -->
     <string name="notification_hidden_text">Contents hidden</string>
 
@@ -2589,6 +2592,15 @@
     <!-- Title for EditText context menu [CHAR LIMIT=20] -->
     <string name="editTextMenuTitle">Text actions</string>
 
+    <!-- Label for item in the text selection menu to trigger an Email app [CHAR LIMIT=20] -->
+    <string name="email">Email</string>
+
+    <!-- Label for item in the text selection menu to trigger a Dialer app [CHAR LIMIT=20] -->
+    <string name="dial">Dial</string>
+
+    <!-- Label for item in the text selection menu to trigger a Map app [CHAR LIMIT=20] -->
+    <string name="map">Map</string>
+
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Storage space running out</string>
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 0b326e9..1e15348 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -451,14 +451,14 @@
     </style>
 
     <style name="TextAppearance.Material.Notification">
-        <item name="textColor">@color/secondary_text_material_light</item>
+        <item name="textColor">@color/notification_secondary_text_color_light</item>
         <item name="textSize">@dimen/notification_text_size</item>
     </style>
 
     <style name="TextAppearance.Material.Notification.Reply" />
 
     <style name="TextAppearance.Material.Notification.Title">
-        <item name="textColor">@color/primary_text_default_material_light</item>
+        <item name="textColor">@color/notification_primary_text_color_light</item>
         <item name="textSize">@dimen/notification_title_text_size</item>
     </style>
 
@@ -467,7 +467,7 @@
     </style>
 
     <style name="TextAppearance.Material.Notification.Info">
-        <item name="textColor">@color/secondary_text_default_material_light</item>
+        <item name="textColor">@color/notification_secondary_text_color_light</item>
         <item name="textSize">@dimen/notification_subtext_size</item>
     </style>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6eb3bee..a732998 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -476,6 +476,9 @@
   <java-symbol type="string" name="replace" />
   <java-symbol type="string" name="undo" />
   <java-symbol type="string" name="redo" />
+  <java-symbol type="string" name="email" />
+  <java-symbol type="string" name="dial" />
+  <java-symbol type="string" name="map" />
   <java-symbol type="string" name="textSelectionCABTitle" />
   <java-symbol type="string" name="BaMmi" />
   <java-symbol type="string" name="CLIRDefaultOffNextCallOff" />
@@ -1133,7 +1136,9 @@
   <java-symbol type="string" name="config_ethernet_tcp_buffers" />
   <java-symbol type="string" name="config_wifi_tcp_buffers" />
   <java-symbol type="string" name="config_demoModeLauncherComponent" />
-  <java-symbol type="string" name="config_demoModePassword" />
+  <java-symbol type="string" name="config_carrierDemoModePassword" />
+  <java-symbol type="string" name="config_carrierDemoModeSetting" />
+  <java-symbol type="string" name="config_carrierDemoModePackages" />
   <java-symbol type="string" name="demo_starting_message" />
   <java-symbol type="string" name="demo_restarting_message" />
   <java-symbol type="string" name="conference_call" />
@@ -1251,6 +1256,11 @@
   <java-symbol type="drawable" name="platlogo" />
   <java-symbol type="drawable" name="stat_notify_sync_error" />
   <java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+  <java-symbol type="drawable" name="ic_wifi_signal_0" />
+  <java-symbol type="drawable" name="ic_wifi_signal_1" />
+  <java-symbol type="drawable" name="ic_wifi_signal_2" />
+  <java-symbol type="drawable" name="ic_wifi_signal_3" />
+  <java-symbol type="drawable" name="ic_wifi_signal_4" />
   <java-symbol type="drawable" name="ic_signal_wifi_badged_0_bars" />
   <java-symbol type="drawable" name="ic_signal_wifi_badged_1_bar" />
   <java-symbol type="drawable" name="ic_signal_wifi_badged_2_bars" />
@@ -2783,8 +2793,14 @@
   <java-symbol type="drawable" name="lockscreen_notselected" />
   <java-symbol type="drawable" name="lockscreen_selected" />
 
+  <java-symbol type="string" name="notification_header_divider_symbol_with_spaces" />
   <java-symbol type="string" name="config_defaultCellBroadcastReceiverComponent" />
 
+  <java-symbol type="color" name="notification_primary_text_color_light" />
+  <java-symbol type="color" name="notification_primary_text_color_dark" />
+  <java-symbol type="color" name="notification_secondary_text_color_light" />
+  <java-symbol type="color" name="notification_secondary_text_color_dark" />
+
   <java-symbol type="string" name="app_category_game" />
   <java-symbol type="string" name="app_category_audio" />
   <java-symbol type="string" name="app_category_video" />
@@ -2796,6 +2812,8 @@
 
   <java-symbol type="raw" name="fallback_categories" />
 
+  <java-symbol type="string" name="config_icon_mask" />
+
   <java-symbol type="attr" name="primaryContentAlpha" />
 
   <!-- Accessibility Shortcut -->
diff --git a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
similarity index 80%
rename from core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
rename to core/tests/coretests/src/android/metrics/LogMakerTest.java
index a340559..35d8d93 100644
--- a/core/tests/coretests/src/com/android/internal/logging/LogBuilderTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.internal.logging;
+package android.metrics;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import junit.framework.TestCase;
 
-public class LogBuilderTest extends TestCase {
+public class LogMakerTest extends TestCase {
 
     public void testSerialize() {
-        LogBuilder builder = new LogBuilder(0);
+        LogMaker builder = new LogMaker(0);
         builder.addTaggedData(1, "one");
         builder.addTaggedData(2, "two");
         Object[] out = builder.serialize();
@@ -41,7 +41,7 @@
         int bucket = 13;
         int value = 14;
 
-        LogBuilder builder = new LogBuilder(category);
+        LogMaker builder = new LogMaker(category);
         builder.setType(type);
         builder.setSubtype(subtype);
         builder.setTimestamp(timestamp);
@@ -53,7 +53,7 @@
         builder.addTaggedData(2, "two");
 
         Object[] out = builder.serialize();
-        LogBuilder parsed = new LogBuilder(out);
+        LogMaker parsed = new LogMaker(out);
 
         assertEquals(category, parsed.getCategory());
         assertEquals(type, parsed.getType());
@@ -68,7 +68,7 @@
     }
 
     public void testIntBucket() {
-        LogBuilder builder = new LogBuilder(0);
+        LogMaker builder = new LogMaker(0);
         builder.setCounterBucket(100);
         assertEquals(100, builder.getCounterBucket());
         assertEquals(false, builder.isLongCounterBucket());
@@ -76,14 +76,14 @@
 
     public void testLongBucket() {
         long longBucket = Long.MAX_VALUE;
-        LogBuilder builder = new LogBuilder(0);
+        LogMaker builder = new LogMaker(0);
         builder.setCounterBucket(longBucket);
         assertEquals(longBucket, builder.getCounterBucket());
         assertEquals(true, builder.isLongCounterBucket());
     }
 
     public void testInvalidInputThrows() {
-        LogBuilder builder = new LogBuilder(0);
+        LogMaker builder = new LogMaker(0);
         boolean threw = false;
         try {
             builder.addTaggedData(0, new Object());
@@ -95,11 +95,12 @@
     }
 
     public void testValidInputTypes() {
-        LogBuilder builder = new LogBuilder(0);
+        LogMaker builder = new LogMaker(0);
         builder.addTaggedData(1, "onetwothree");
         builder.addTaggedData(2, 123);
         builder.addTaggedData(3, 123L);
         builder.addTaggedData(4, 123.0F);
+        builder.addTaggedData(5, null);
         Object[] out = builder.serialize();
         assertEquals("onetwothree", out[1]);
         assertEquals(123, out[3]);
@@ -107,11 +108,21 @@
         assertEquals(123.0F, out[7]);
     }
 
-  public void testCategoryDefault() {
-        LogBuilder builder = new LogBuilder(10);
+    public void testCategoryDefault() {
+        LogMaker builder = new LogMaker(10);
         Object[] out = builder.serialize();
         assertEquals(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY, out[0]);
         assertEquals(10, out[1]);
     }
 
+    public void testGiantLogOmitted() {
+        LogMaker badBuilder = new LogMaker(0);
+        StringBuilder b = new StringBuilder();
+        for (int i = 0; i < 4000; i++) {
+            b.append("test, " + i);
+        }
+        badBuilder.addTaggedData(100, b.toString());
+        assertTrue(badBuilder.serialize().length < LogMaker.MAX_SERIALIZED_SIZE);
+    }
+
 }
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
new file mode 100644
index 0000000..1afe9da
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -0,0 +1,75 @@
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkKeyTest {
+    private static final String VALID_SSID = "\"ssid1\"";
+    private static final String VALID_BSSID = "00:00:00:00:00:00";
+    @Mock private WifiInfo mWifiInfo;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void createFromWifi_nullInput() throws Exception {
+        assertNull(NetworkKey.createFromWifiInfo(null));
+    }
+
+    @Test
+    public void createFromWifi_nullSsid() throws Exception {
+        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+    }
+
+    @Test
+    public void createFromWifi_emptySsid() throws Exception {
+        when(mWifiInfo.getSSID()).thenReturn("");
+        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+    }
+
+    @Test
+    public void createFromWifi_noneSsid() throws Exception {
+        when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE);
+        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+    }
+
+    @Test
+    public void createFromWifi_nullBssid() throws Exception {
+        when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+    }
+
+    @Test
+    public void createFromWifi_emptyBssid() throws Exception {
+        when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+        when(mWifiInfo.getBSSID()).thenReturn("");
+        assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+    }
+
+    @Test
+    public void createFromWifi_validWifiInfo() throws Exception {
+        when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+        when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+
+        NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID));
+        final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo);
+        assertEquals(expected, actual);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index 34c34d7..dc75417 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -33,7 +33,8 @@
 import java.util.List;
 
 public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase {
-    private static final String DUMMY_PACKAGE_NAME = "dymmy package name";
+    private static final String DUMMY_PACKAGE_NAME = "dummy package name";
+    private static final String DUMMY_IME_LABEL = "dummy ime label";
     private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
     private static final boolean DUMMY_IS_AUX_IME = false;
     private static final boolean DUMMY_FORCE_DEFAULT = false;
@@ -88,6 +89,35 @@
         }
     }
 
+    private static ImeSubtypeListItem createDummyItem(String imeName,
+            String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale) {
+        final ResolveInfo ri = new ResolveInfo();
+        final ServiceInfo si = new ServiceInfo();
+        final ApplicationInfo ai = new ApplicationInfo();
+        ai.packageName = DUMMY_PACKAGE_NAME;
+        ai.enabled = true;
+        si.applicationInfo = ai;
+        si.enabled = true;
+        si.packageName = DUMMY_PACKAGE_NAME;
+        si.name = imeName;
+        si.exported = true;
+        si.nonLocalizedLabel = DUMMY_IME_LABEL;
+        ri.serviceInfo = si;
+        ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+        subtypes.add(new InputMethodSubtypeBuilder()
+                .setSubtypeNameResId(0)
+                .setSubtypeIconResId(0)
+                .setSubtypeLocale(subtypeLocale)
+                .setIsAsciiCapable(true)
+                .build());
+        final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
+                DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
+                DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
+                false /* supportsDismissingWindow */);
+        return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
+                systemLocale);
+    }
+
     private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
         final List<ImeSubtypeListItem> items = new ArrayList<>();
         addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
@@ -329,4 +359,56 @@
         assertFalse(item_e.mIsSystemLocale);
         assertFalse(item_EN_US.mIsSystemLocale);
     }
+
+    @SmallTest
+    public void testImeSubtypeListComparator() throws Exception {
+        {
+            final List<ImeSubtypeListItem> items = Arrays.asList(
+                    createDummyItem("X", "A", "en_US", 0, "en_US"),
+                    createDummyItem("X", "A", "en", 1, "en_US"),
+                    createDummyItem("X", "A", "ja", 2, "en_US"),
+                    createDummyItem("X", "Z", "en_US", 3, "en_US"),
+                    createDummyItem("X", "Z", "en", 4, "en_US"),
+                    createDummyItem("X", "Z", "ja", 5, "en_US"),
+                    createDummyItem("X", "", "en_US", 6, "en_US"),
+                    createDummyItem("X", "", "en", 7, "en_US"),
+                    createDummyItem("X", "", "ja", 8, "en_US"),
+                    createDummyItem("Y", "A", "en_US", 9, "en_US"),
+                    createDummyItem("Y", "A", "en", 10, "en_US"),
+                    createDummyItem("Y", "A", "ja", 11, "en_US"),
+                    createDummyItem("Y", "Z", "en_US", 12, "en_US"),
+                    createDummyItem("Y", "Z", "en", 13, "en_US"),
+                    createDummyItem("Y", "Z", "ja", 14, "en_US"),
+                    createDummyItem("Y", "", "en_US", 15, "en_US"),
+                    createDummyItem("Y", "", "en", 16, "en_US"),
+                    createDummyItem("Y", "", "ja", 17, "en_US"),
+                    createDummyItem("", "A", "en_US", 18, "en_US"),
+                    createDummyItem("", "A", "en", 19, "en_US"),
+                    createDummyItem("", "A", "ja", 20, "en_US"),
+                    createDummyItem("", "Z", "en_US", 21, "en_US"),
+                    createDummyItem("", "Z", "en", 22, "en_US"),
+                    createDummyItem("", "Z", "ja", 23, "en_US"),
+                    createDummyItem("", "", "en_US", 24, "en_US"),
+                    createDummyItem("", "", "en", 25, "en_US"),
+                    createDummyItem("", "", "ja", 26, "en_US"));
+
+            for (int i = 0; i < items.size(); ++i) {
+                assertEquals(0, items.get(i).compareTo(items.get(i)));
+                for (int j = i + 1; j < items.size(); ++j) {
+                    assertTrue(items.get(i).compareTo(items.get(j)) < 0);
+                    assertTrue(items.get(j).compareTo(items.get(i)) > 0);
+                }
+            }
+        }
+
+        {
+            // Following two items have the same priority.
+            final ImeSubtypeListItem nonSystemLocale1 =
+                    createDummyItem("X", "A", "ja_JP", 0, "en_us");
+            final ImeSubtypeListItem nonSystemLocale2 =
+                    createDummyItem("X", "A", "hi_IN", 1, "en_us");
+            assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2));
+            assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1));
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
index 0bff850..c023b57 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/LockscreenGestureParserTest.java
@@ -19,7 +19,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class LockscreenGestureParserTest extends ParserTest {
@@ -79,7 +79,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(view, proto.getCategory());
         assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
@@ -95,6 +95,6 @@
 
         mParser.parseEvent(mLogger, t, objects);
 
-        verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+        verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
index 2119c25..f05205d 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationActionClickedParserTest.java
@@ -20,7 +20,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class NotificationActionClickedParserTest extends ParserTest {
@@ -49,12 +49,12 @@
         validateGoodData(t, mTag, index, objects);
     }
 
-    private LogBuilder validateGoodData(int t, String tag, int index, Object[] objects) {
+    private LogMaker validateGoodData(int t, String tag, int index, Object[] objects) {
         mParser.parseEvent(mLogger, t, objects);
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_ITEM_ACTION, proto.getCategory());
         assertEquals(mKeyPackage, proto.getPackageName());
@@ -69,7 +69,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testWrongType() throws Throwable {
@@ -79,7 +79,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testBadKey() throws Throwable {
@@ -89,7 +89,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testMncTimestamps() throws Throwable {
@@ -102,7 +102,7 @@
         objects[3] = mSinceUpdateMillis;
         objects[4] = mSinceVisibleMillis;
 
-        LogBuilder proto = validateGoodData(t, "", index, objects);
+        LogMaker proto = validateGoodData(t, "", index, objects);
         validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
                 mSinceVisibleMillis);
     }
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
index 1e117ee..7771e84 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationAlertParserTest.java
@@ -21,12 +21,9 @@
 import static org.mockito.Mockito.when;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
-import java.util.Collections;
-import java.util.List;
-
 import org.mockito.ArgumentCaptor;
 
 public class NotificationAlertParserTest extends ParserTest {
@@ -126,7 +123,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(mTime, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_ALERT, proto.getCategory());
         assertEquals(mKeyPackage, proto.getPackageName());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
index de16919..77b2ed6 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationCanceledParserTest.java
@@ -21,11 +21,9 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
-import java.util.List;
-
 public class NotificationCanceledParserTest extends ParserTest {
 
     public NotificationCanceledParserTest() {
@@ -57,7 +55,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
         assertEquals(mKeyPackage, proto.getPackageName());
@@ -108,7 +106,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         validateNotificationTimes(proto, life, freshness, exposure);
     }
 
@@ -121,7 +119,7 @@
         mParser.parseEvent(mLogger, 0, objects);
 
         if (intentional) {
-            verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+            verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
         }
     }
 
@@ -164,7 +162,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testWrongType() throws Throwable {
@@ -174,7 +172,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testBadKey() throws Throwable {
@@ -184,7 +182,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testIgnoreUnexpectedData() throws Throwable {
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
index 2f61dd7..cc65132 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationClickedParserTest.java
@@ -21,7 +21,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class NotificationClickedParserTest extends ParserTest {
@@ -46,12 +46,12 @@
         validateGoodData(t, mTag, objects);
     }
 
-    private LogBuilder validateGoodData(int t, String tag, Object[] objects) {
+    private LogMaker validateGoodData(int t, String tag, Object[] objects) {
         mParser.parseEvent(mLogger, t, objects);
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
         assertEquals(mKeyPackage, proto.getPackageName());
@@ -66,7 +66,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testWrongType() throws Throwable {
@@ -75,7 +75,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testBadKey() throws Throwable {
@@ -84,7 +84,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testMncTimestamps() throws Throwable {
@@ -95,7 +95,7 @@
         objects[2] = mSinceUpdateMillis;
         objects[3] = mSinceVisibleMillis;
 
-        LogBuilder proto = validateGoodData(t, "", objects);
+        LogMaker proto = validateGoodData(t, "", objects);
         validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
                 mSinceVisibleMillis);
     }
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
index 86b4a45..f337f91 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationExpansionParserTest.java
@@ -21,7 +21,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class NotificationExpansionParserTest extends ParserTest {
@@ -54,12 +54,12 @@
         validateGoodData(t, mTag, objects);
     }
 
-    private LogBuilder validateGoodData(int t, String tag, Object[] objects) {
+    private LogMaker validateGoodData(int t, String tag, Object[] objects) {
         mParser.parseEvent(mLogger, t, objects);
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
         assertEquals(mKeyPackage, proto.getPackageName());
@@ -79,7 +79,7 @@
 
         mParser.parseEvent(mLogger, t, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testCollapsedByUser() throws Throwable {
@@ -93,7 +93,7 @@
 
         mParser.parseEvent(mLogger, t, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testAutoCollapsed() throws Throwable {
@@ -107,7 +107,7 @@
 
         mParser.parseEvent(mLogger, t, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testMissingData() throws Throwable {
@@ -115,7 +115,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testWrongType() throws Throwable {
@@ -126,7 +126,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testBadKey() throws Throwable {
@@ -137,7 +137,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
     }
 
     public void testMncTimestamps() throws Throwable {
@@ -152,7 +152,7 @@
         objects[4] = mSinceUpdateMillis;
         objects[5] = mSinceVisibleMillis;
 
-        LogBuilder proto = validateGoodData(t, "", objects);
+        LogMaker proto = validateGoodData(t, "", objects);
         validateNotificationTimes(proto, mSinceCreationMillis, mSinceUpdateMillis,
                 mSinceVisibleMillis);
     }
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
index 3efc48f..ce6f1f4 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelHiddenParserTest.java
@@ -19,7 +19,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class NotificationPanelHiddenParserTest extends ParserTest {
@@ -48,7 +48,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
         assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
index 34dda98..9e15812 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationPanelRevealedParserTest.java
@@ -20,7 +20,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class NotificationPanelRevealedParserTest extends ParserTest {
@@ -37,7 +37,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
         assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -57,7 +57,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_PANEL, proto.getCategory());
         assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -69,7 +69,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, times(1)).addEvent((LogBuilder) anyObject());
+        verify(mLogger, times(1)).addEvent((LogMaker) anyObject());
     }
 
     public void testIgnoreUnexpectedData() throws Throwable {
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
index cc5421c..7fef929 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/NotificationVisibilityParserTest.java
@@ -16,17 +16,13 @@
 package com.android.internal.logging.legacy;
 
 import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
-import org.mockito.ArgumentCaptor;
-
 public class NotificationVisibilityParserTest extends ParserTest {
     private final int mCreationTime = 23124;
     private final int mUpdateTime = 3412;
@@ -84,7 +80,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(mTime, proto.getTimestamp());
         assertEquals(MetricsEvent.NOTIFICATION_ITEM, proto.getCategory());
         assertEquals(mKeyPackage, proto.getPackageName());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
index 2e5c6d2..4adf629 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/ParserTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.internal.logging.legacy;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import static org.mockito.Matchers.any;
@@ -39,8 +39,8 @@
 
     protected TagParser mParser;
 
-    protected LogBuilder[] mProto;
-    protected ArgumentCaptor<LogBuilder> mProtoCaptor;
+    protected LogMaker[] mProto;
+    protected ArgumentCaptor<LogMaker> mProtoCaptor;
     protected ArgumentCaptor<String> mNameCaptor;
     protected ArgumentCaptor<Integer> mCountCaptor;
     protected String mKey = "0|com.android.example.notificationshowcase|31338|null|10090";
@@ -54,9 +54,9 @@
 
 
     public ParserTest() {
-        mProto = new LogBuilder[5];
+        mProto = new LogMaker[5];
         for (int i = 0; i < mProto.length; i++) {
-            mProto[i] = new LogBuilder(MetricsEvent.VIEW_UNKNOWN);
+            mProto[i] = new LogMaker(MetricsEvent.VIEW_UNKNOWN);
         }
     }
 
@@ -66,19 +66,19 @@
 
         MockitoAnnotations.initMocks(this);
 
-        mProtoCaptor = ArgumentCaptor.forClass(LogBuilder.class);
+        mProtoCaptor = ArgumentCaptor.forClass(LogMaker.class);
         mNameCaptor = ArgumentCaptor.forClass(String.class);
         mCountCaptor = ArgumentCaptor.forClass(Integer.class);
 
-        OngoingStubbing<LogBuilder> stub = when(mLogger.obtain()).thenReturn(mProto[0]);
+        OngoingStubbing<LogMaker> stub = when(mLogger.obtain()).thenReturn(mProto[0]);
         for (int i = 1; i < mProto.length; i++) {
             stub.thenReturn(mProto[i]);
         }
-        doNothing().when(mLogger).addEvent(any(LogBuilder.class));
+        doNothing().when(mLogger).addEvent(any(LogMaker.class));
         doNothing().when(mLogger).incrementBy(anyString(), anyInt());
     }
 
-    protected void validateNotificationTimes(LogBuilder proto, int life, int freshness,
+    protected void validateNotificationTimes(LogMaker proto, int life, int freshness,
             int exposure) {
         validateNotificationTimes(proto, life, freshness);
         if (exposure != 0) {
@@ -89,7 +89,7 @@
         }
     }
 
-    protected void validateNotificationTimes(LogBuilder proto, int life, int freshness) {
+    protected void validateNotificationTimes(LogMaker proto, int life, int freshness) {
         if (life != 0) {
             assertEquals(life,
                 proto.getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS));
@@ -104,7 +104,7 @@
         }
     }
 
-    protected void validateNotificationIdAndTag(LogBuilder proto, int id, String tag) {
+    protected void validateNotificationIdAndTag(LogMaker proto, int id, String tag) {
         assertEquals(tag, proto.getTaggedData(MetricsEvent.NOTIFICATION_TAG));
         assertEquals(id, proto.getTaggedData(MetricsEvent.NOTIFICATION_ID));
     }
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
index be918cd..b480e61 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/PowerScreenStateParserTest.java
@@ -19,7 +19,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class PowerScreenStateParserTest extends ParserTest {
@@ -60,7 +60,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(type, proto.getType());
         assertEquals(MetricsEvent.SCREEN, proto.getCategory());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
index 906ec04..def9628 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/StatusBarStateParserTest.java
@@ -19,7 +19,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class StatusBarStateParserTest extends ParserTest {
@@ -64,7 +64,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(view, proto.getCategory());
         assertEquals(type, proto.getType());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
index 111909f..2ad76c1 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiActionParserTest.java
@@ -23,7 +23,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class SysuiActionParserTest extends ParserTest {
@@ -47,7 +47,7 @@
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(view, proto.getCategory());
         assertEquals(MetricsEvent.TYPE_ACTION, proto.getType());
@@ -66,7 +66,7 @@
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(view, proto.getCategory());
         assertEquals(packageName, proto.getPackageName());
@@ -117,7 +117,7 @@
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(view, proto.getCategory());
         assertEquals(expectedValue, proto.getSubtype());
@@ -130,7 +130,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
     }
 
@@ -141,7 +141,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
     }
 
@@ -151,7 +151,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
index 7853f77..e7a05d8 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiMultiActionParserTest.java
@@ -15,16 +15,11 @@
  */
 package com.android.internal.logging.legacy;
 
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.metrics.LogMaker;
 
 public class SysuiMultiActionParserTest extends ParserTest {
 
@@ -41,7 +36,7 @@
         String counterName = "sheep";
         int bucket = 13;
         int value = 14;
-        LogBuilder builder = new LogBuilder(category);
+        LogMaker builder = new LogMaker(category);
         builder.setType(type);
         builder.setSubtype(subtype);
         builder.setPackageName(packageName);
@@ -57,7 +52,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(category, proto.getCategory());
         assertEquals(type, proto.getType());
         assertEquals(subtype, proto.getSubtype());
diff --git a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
index 1291508..64d69a4 100644
--- a/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/logging/legacy/SysuiViewVisibilityParserTest.java
@@ -23,7 +23,7 @@
 import static org.mockito.Mockito.verify;
 
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 public class SysuiViewVisibilityParserTest extends ParserTest {
@@ -47,7 +47,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(t, proto.getTimestamp());
         assertEquals(view, proto.getCategory());
         assertEquals(MetricsEvent.TYPE_OPEN, proto.getType());
@@ -64,7 +64,7 @@
 
         verify(mLogger, times(1)).addEvent(mProtoCaptor.capture());
 
-        LogBuilder proto = mProtoCaptor.getValue();
+        LogMaker proto = mProtoCaptor.getValue();
         assertEquals(MetricsEvent.TYPE_CLOSE, proto.getType());
     }
 
@@ -73,7 +73,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
     }
 
@@ -84,7 +84,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
     }
 
@@ -95,7 +95,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
     }
 
@@ -105,7 +105,7 @@
 
         mParser.parseEvent(mLogger, 0, objects);
 
-        verify(mLogger, never()).addEvent((LogBuilder) anyObject());
+        verify(mLogger, never()).addEvent((LogMaker) anyObject());
         verify(mLogger, never()).incrementBy(anyString(), anyInt());
     }
 
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
index 2f18de0..62ef25b 100644
--- a/data/etc/framework-sysconfig.xml
+++ b/data/etc/framework-sysconfig.xml
@@ -21,5 +21,6 @@
          delivery restrictions. -->
     <allow-implicit-broadcast action="android.intent.action.SIM_STATE_CHANGED" />
     <allow-implicit-broadcast action="android.intent.action.PACKAGE_CHANGED" />
+    <allow-implicit-broadcast action="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
 
 </config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f1be4a9..c5961ab 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -303,6 +303,7 @@
         <permission name="android.permission.BIND_APPWIDGET"/>
         <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+        <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
         <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
         <permission name="android.permission.CONTROL_VPN"/>
         <permission name="android.permission.DUMP"/>
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 8673e0b..2c93c32 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -19,6 +19,7 @@
 import android.content.res.AssetManager;
 import android.text.FontConfig;
 import android.util.Log;
+import dalvik.annotation.optimization.CriticalNative;
 
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -40,11 +41,11 @@
      */
     public long mNativePtr;
 
+    // Points native font family builder. Must be zero after freezing this family.
+    private long mBuilderPtr;
+
     public FontFamily() {
-        mNativePtr = nCreateFamily(null, 0);
-        if (mNativePtr == 0) {
-            throw new IllegalStateException("error creating native FontFamily");
-        }
+        mBuilderPtr = nInitBuilder(null, 0);
     }
 
     public FontFamily(String lang, String variant) {
@@ -54,27 +55,48 @@
         } else if ("elegant".equals(variant)) {
             varEnum = 2;
         }
-        mNativePtr = nCreateFamily(lang, varEnum);
-        if (mNativePtr == 0) {
-            throw new IllegalStateException("error creating native FontFamily");
+        mBuilderPtr = nInitBuilder(lang, varEnum);
+    }
+
+    public void freeze() {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("This FontFamily is already frozen");
         }
+        mNativePtr = nCreateFamily(mBuilderPtr);
+        mBuilderPtr = 0;
+    }
+
+    public void abortCreation() {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("This FontFamily is already frozen or abandoned");
+        }
+        nAbort(mBuilderPtr);
+        mBuilderPtr = 0;
     }
 
     @Override
     protected void finalize() throws Throwable {
         try {
-            nUnrefFamily(mNativePtr);
+            if (mNativePtr != 0) {
+                nUnrefFamily(mNativePtr);
+            }
+            if (mBuilderPtr != 0) {
+                nAbort(mBuilderPtr);
+            }
         } finally {
             super.finalize();
         }
     }
 
     public boolean addFont(String path, int ttcIndex) {
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to call addFont after freezing.");
+        }
         try (FileInputStream file = new FileInputStream(path)) {
             FileChannel fileChannel = file.getChannel();
             long fontSize = fileChannel.size();
             ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
-            return nAddFont(mNativePtr, fontBuffer, ttcIndex);
+            return nAddFont(mBuilderPtr, fontBuffer, ttcIndex);
         } catch (IOException e) {
             Log.e(TAG, "Error mapping font file " + path);
             return false;
@@ -83,20 +105,34 @@
 
     public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontConfig.Axis> axes,
             int weight, boolean style) {
-        return nAddFontWeightStyle(mNativePtr, font, ttcIndex, axes, weight, style);
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
+        }
+        return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, axes, weight, style);
     }
 
     public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
             boolean isAsset) {
-        return nAddFontFromAssetManager(mNativePtr, mgr, path, cookie, isAsset);
+        if (mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
+        }
+        return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset);
     }
 
-    private static native long nCreateFamily(String lang, int variant);
+    private static native long nInitBuilder(String lang, int variant);
+
+    @CriticalNative
+    private static native long nCreateFamily(long mBuilderPtr);
+
+    @CriticalNative
+    private static native void nAbort(long mBuilderPtr);
+
+    @CriticalNative
     private static native void nUnrefFamily(long nativePtr);
-    private static native boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex);
-    private static native boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
+    private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex);
+    private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
             int ttcIndex, List<FontConfig.Axis> listOfAxis,
             int weight, boolean isItalic);
-    private static native boolean nAddFontFromAssetManager(long nativeFamily, AssetManager mgr,
+    private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
             String path, int cookie, boolean isAsset);
 }
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 0a349e9..38e8061 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -16,20 +16,34 @@
 
 package android.graphics;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.res.AssetManager;
+import android.graphics.fonts.FontRequest;
+import android.graphics.fonts.FontResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
+import android.provider.FontsContract;
 import android.text.FontConfig;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.LruCache;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
+
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
@@ -64,7 +78,11 @@
 
     static Typeface[] sDefaults;
     private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
-            new LongSparseArray<SparseArray<Typeface>>(3);
+            new LongSparseArray<>(3);
+    @GuardedBy("sLock")
+    private static FontsContract sFontsContract;
+    @GuardedBy("sLock")
+    private static Handler mHandler;
 
     /**
      * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
@@ -74,6 +92,7 @@
     static Typeface sDefaultTypeface;
     static Map<String, Typeface> sSystemFontMap;
     static FontFamily[] sFallbackFonts;
+    private static final Object sLock = new Object();
 
     static final String FONTS_CONFIG = "fonts.xml";
 
@@ -124,7 +143,7 @@
 
                 FontFamily fontFamily = new FontFamily();
                 if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
-                    FontFamily[] families = { fontFamily };
+                    FontFamily[] families = {fontFamily};
                     typeface = createFromFamiliesWithDefault(families);
                     sDynamicTypefaceCache.put(key, typeface);
                     return typeface;
@@ -135,6 +154,139 @@
     }
 
     /**
+     * Create a typeface object given a font request. The font will be asynchronously fetched,
+     * therefore the result is delivered to the given callback. See {@link FontRequest}.
+     * Only one of the methods in callback will be invoked, depending on whether the request
+     * succeeds or fails. These calls will happen on the main thread.
+     * @param request A {@link FontRequest} object that identifies the provider and query for the
+     *                request. May not be null.
+     * @param callback A callback that will be triggered when results are obtained. May not be null.
+     */
+    public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
+        synchronized (sLock) {
+            if (sFontsContract == null) {
+                sFontsContract = new FontsContract();
+                mHandler = new Handler();
+            }
+            final ResultReceiver receiver = new ResultReceiver(null) {
+                @Override
+                public void onReceiveResult(int resultCode, Bundle resultData) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            receiveResult(request, callback, resultCode, resultData);
+                        }
+                    });
+                }
+            };
+            sFontsContract.getFont(request, receiver);
+        }
+    }
+
+    private static void receiveResult(FontRequest request, FontRequestCallback callback,
+            int resultCode, Bundle resultData) {
+        if (resultCode == FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND) {
+            callback.onTypefaceRequestFailed(
+                    FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
+            return;
+        }
+        if (resultCode == FontsContract.RESULT_CODE_FONT_NOT_FOUND
+                || resultData == null) {
+            callback.onTypefaceRequestFailed(
+                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
+            return;
+        }
+        List<FontResult> resultList =
+                resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
+        if (resultList == null || resultList.isEmpty()) {
+            callback.onTypefaceRequestFailed(
+                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
+            return;
+        }
+        FontFamily fontFamily = new FontFamily();
+        for (int i = 0; i < resultList.size(); ++i) {
+            FontResult result = resultList.get(i);
+            ParcelFileDescriptor fd = result.getFileDescriptor();
+            if (fd == null) {
+                callback.onTypefaceRequestFailed(
+                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+                return;
+            }
+            try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
+                FileChannel fileChannel = is.getChannel();
+                long fontSize = fileChannel.size();
+                ByteBuffer fontBuffer = fileChannel.map(
+                        FileChannel.MapMode.READ_ONLY, 0, fontSize);
+                int style = result.getStyle();
+                int weight = (style & BOLD) != 0 ? 700 : 400;
+                // TODO: this method should be
+                // create(fd, ttcIndex, fontVariationSettings, style).
+                if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
+                                null, weight, (style & ITALIC) != 0)) {
+                    Log.e(TAG, "Error creating font " + request.getQuery());
+                    callback.onTypefaceRequestFailed(
+                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+                    return;
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error reading font " + request.getQuery(), e);
+                callback.onTypefaceRequestFailed(
+                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
+                return;
+            } finally {
+                IoUtils.closeQuietly(fd);
+            }
+        }
+        fontFamily.freeze();
+        callback.onTypefaceRetrieved(Typeface.createFromFamiliesWithDefault(
+                new FontFamily[] {fontFamily}));
+    }
+
+    /**
+     * Interface used to receive asynchronously fetched typefaces.
+     */
+    public interface FontRequestCallback {
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
+         * provider was not found on the device.
+         */
+        int FAIL_REASON_PROVIDER_NOT_FOUND = 0;
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
+         * returned by the provider was not loaded properly.
+         */
+        int FAIL_REASON_FONT_LOAD_ERROR = 1;
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
+         * provider did not return any results for the given query.
+         */
+        int FAIL_REASON_FONT_NOT_FOUND = 2;
+
+        /** @hide */
+        @IntDef({FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
+                FAIL_REASON_FONT_NOT_FOUND})
+        @Retention(RetentionPolicy.SOURCE)
+        @interface FontRequestFailReason {}
+
+        /**
+         * Called then a Typeface request done via {@link Typeface#create(FontRequest,
+         * FontRequestCallback)} is complete. Note that this method will not be called if
+         * {@link #onTypefaceRequestFailed(int)} is called instead.
+         * @param typeface  The Typeface object retrieved.
+         */
+        void onTypefaceRetrieved(Typeface typeface);
+
+        /**
+         * Called when a Typeface request done via {@link Typeface#create(FontRequest,
+         * FontRequestCallback)} fails.
+         * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
+         *               {@link #FAIL_REASON_FONT_NOT_FOUND} or
+         *               {@link #FAIL_REASON_FONT_LOAD_ERROR}.
+         */
+        void onTypefaceRequestFailed(@FontRequestFailReason int reason);
+    }
+
+    /**
      * Create a typeface object given a family name, and option style information.
      * If null is passed for the name, then the "default" font will be chosen.
      * The resulting typeface object can be queried (getStyle()) to discover what
@@ -222,10 +374,13 @@
 
                 FontFamily fontFamily = new FontFamily();
                 if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */)) {
+                    fontFamily.freeze();
                     FontFamily[] families = { fontFamily };
                     typeface = createFromFamiliesWithDefault(families);
                     sDynamicTypefaceCache.put(key, typeface);
                     return typeface;
+                } else {
+                    fontFamily.abortCreation();
                 }
             }
         }
@@ -271,8 +426,11 @@
         if (sFallbackFonts != null) {
             FontFamily fontFamily = new FontFamily();
             if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
+                fontFamily.freeze();
                 FontFamily[] families = { fontFamily };
                 return createFromFamiliesWithDefault(families);
+            } else {
+                fontFamily.abortCreation();
             }
         }
         throw new RuntimeException("Font not found " + path);
@@ -341,6 +499,7 @@
                 Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
             }
         }
+        fontFamily.freeze();
         return fontFamily;
     }
 
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index 348af70d..6d0bbdf 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -147,6 +147,8 @@
                 return new TransitionDrawable();
             case "ripple":
                 return new RippleDrawable();
+            case "maskable-icon":
+                return new MaskableIconDrawable();
             case "color":
                 return new ColorDrawable();
             case "shape":
diff --git a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
new file mode 100644
index 0000000..3467b1a
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
@@ -0,0 +1,1001 @@
+/*
+ * 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 android.graphics.drawable;
+
+import static android.graphics.drawable.Drawable.obtainAttributes;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.PathParser;
+
+import com.android.internal.R;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * This drawable supports two layers: foreground and background.
+ *
+ * <p>The layers are clipped when rendering using the mask path defined in the device configuration.
+ *
+ * <p>This class can also be created via XML inflation using <code>&lt;maskable-icon></code> tag
+ * in addition to dynamic creation.
+ */
+public class MaskableIconDrawable extends Drawable implements Drawable.Callback {
+
+    /**
+     * Mask path is defined inside device configuration in following dimension: [100 x 100]
+     */
+    public static final float MASK_SIZE = 100f;
+
+    /**
+     * The view port of the layers is smaller than their intrinsic width and height by this factor.
+     *
+     * It is part of the API contract that all four sides of the layers are padded so as to provide
+     * extra content to reveal within the clip path when performing affine transformations on the
+     * layers.
+     */
+    public static final float DEFAULT_VIEW_PORT_SCALE = 2f / 3f;
+
+    /**
+     * Clip path defined in {@link com.android.internal.R.string.config_icon_mask}.
+     */
+    private static Path sMask;
+
+    /**
+     * Scaled mask based on the view bounds.
+     */
+    private final Path mMask;
+    private final Matrix mMaskMatrix;
+    private final Region mTransparentRegion;
+
+    private Bitmap mMaskBitmap;
+
+    /**
+     * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
+     * background layer.
+     */
+    private static final int BACKGROUND_ID = 0;
+    private static final int FOREGROUND_ID = 1;
+
+    /**
+     * State variable that maintains the {@link ChildDrawable} array.
+     */
+    LayerState mLayerState;
+
+    private Shader mLayersShader;
+    private Bitmap mLayersBitmap;
+
+    private final Rect mTmpOutRect = new Rect();
+    private Rect mHotspotBounds;
+    private boolean mMutated;
+
+    private boolean mSuspendChildInvalidation;
+    private boolean mChildRequestedInvalidation;
+    private final Canvas mCanvas;
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+        Paint.FILTER_BITMAP_FLAG);
+
+    /**
+     * Constructor used for xml inflation.
+     */
+    MaskableIconDrawable() {
+        this((LayerState) null, null);
+    }
+
+    /**
+     * The one constructor to rule them all. This is called by all public
+     * constructors to set the state and initialize local properties.
+     */
+    MaskableIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
+        mLayerState = createConstantState(state, res);
+
+        if (sMask == null) {
+            sMask = PathParser.createPathFromPathData(
+                Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask));
+        }
+        mMask = new Path();
+        mMaskMatrix = new Matrix();
+        mCanvas = new Canvas();
+        mTransparentRegion = new Region();
+    }
+
+    private ChildDrawable createChildDrawable(Drawable drawable) {
+        final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
+        layer.mDrawable = drawable;
+        layer.mDrawable.setCallback(this);
+        mLayerState.mChildrenChangingConfigurations |=
+            layer.mDrawable.getChangingConfigurations();
+        return layer;
+    }
+
+    LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
+        return new LayerState(state, this, res);
+    }
+
+    /**
+     * Constructor used to dynamically create this drawable.
+     *
+     * @param backgroundDrawable drawable that should be rendered in the background
+     * @param foregroundDrawable drawable that should be rendered in the foreground
+     */
+    public MaskableIconDrawable(Drawable backgroundDrawable,
+            Drawable foregroundDrawable) {
+        this((LayerState)null, null);
+        addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
+        addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
+    }
+
+    /**
+     * Sets the layer to the {@param index} and invalidates cache.
+     *
+     * @param index The index of the layer.
+     * @param layer The layer to add.
+     */
+    private void addLayer(int index, @NonNull ChildDrawable layer) {
+        mLayerState.mChildren[index] = layer;
+        mLayerState.invalidateCache();
+    }
+
+    @Override
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        super.inflate(r, parser, attrs, theme);
+
+        final LayerState state = mLayerState;
+        if (state == null) {
+            return;
+        }
+
+        // The density may have changed since the last update. This will
+        // apply scaling to any existing constant state properties.
+        final int density = Drawable.resolveDensity(r, 0);
+        state.setDensity(density);
+
+        final ChildDrawable[] array = state.mChildren;
+        for (int i = 0; i < state.mChildren.length; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(density);
+        }
+        inflateLayers(r, parser, attrs, theme);
+    }
+
+    /**
+     * @return the mask path object used to clip the drawable
+     */
+    public Path getIconMask() {
+        return mMask;
+    }
+
+    /**
+     * @return the foreground drawable managed by this drawable
+     */
+    public Drawable getForeground() {
+        return mLayerState.mChildren[FOREGROUND_ID].mDrawable;
+    }
+
+    /**
+     * @return the background drawable managed by this drawable
+     */
+    public Drawable getBackground() {
+        return mLayerState.mChildren[BACKGROUND_ID].mDrawable;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        if (bounds.isEmpty()) {
+            return;
+        }
+        updateLayerBounds(bounds);
+    }
+
+    private void updateLayerBounds(Rect bounds) {
+        try {
+            suspendChildInvalidation();
+            updateLayerBoundsInternal(bounds);
+            updateMaskBoundsInternal(bounds);
+        } finally {
+            resumeChildInvalidation();
+        }
+    }
+
+    private void updateLayerBoundsInternal(Rect bounds) {
+        int cX = bounds.centerX();
+        int cY = bounds.centerY();
+
+        for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
+            int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
+            int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
+            final Rect outRect = mTmpOutRect;
+            outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
+
+            final ChildDrawable r = mLayerState.mChildren[i];
+            final Drawable d = r.mDrawable;
+            d.setBounds(outRect);
+        }
+    }
+
+    private void updateMaskBoundsInternal(Rect b) {
+        mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
+        sMask.transform(mMaskMatrix, mMask);
+
+        mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8);
+        mCanvas.setBitmap(mMaskBitmap);
+        mPaint.setShader(null);
+        mCanvas.drawPath(mMask, mPaint);
+
+        // reset everything that depends on the view bounds
+        mTransparentRegion.setEmpty();
+        mLayersShader = null;
+        mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mLayersShader == null) {
+            mCanvas.setBitmap(mLayersBitmap);
+            for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+                final Drawable dr = mLayerState.mChildren[i].mDrawable;
+                if (dr != null) {
+                    dr.draw(mCanvas);
+                }
+            }
+            mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
+            mPaint.setShader(mLayersShader);
+        }
+        canvas.drawBitmap(mMaskBitmap, 0.0f, 0.0f, mPaint);
+    }
+
+    @Override
+    public void invalidateSelf() {
+        mLayersShader = null;
+        super.invalidateSelf();
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        outline.setConvexPath(mMask);
+    }
+
+    @Override
+    public @Nullable Region getTransparentRegion() {
+        if (mTransparentRegion.isEmpty()) {
+            mMask.toggleInverseFillType();
+            mTransparentRegion.set(getBounds());
+            mTransparentRegion.setPath(mMask, mTransparentRegion);
+            mMask.toggleInverseFillType();
+        }
+        return mTransparentRegion;
+    }
+
+    @Override
+    public void applyTheme(@NonNull Theme t) {
+        super.applyTheme(t);
+
+        final LayerState state = mLayerState;
+        if (state == null) {
+            return;
+        }
+
+        final int density = Drawable.resolveDensity(t.getResources(), 0);
+        state.setDensity(density);
+
+        final ChildDrawable[] array = state.mChildren;
+        for (int i = 0; i < state.N_CHILDREN; i++) {
+            final ChildDrawable layer = array[i];
+            layer.setDensity(density);
+
+            if (layer.mThemeAttrs != null) {
+                final TypedArray a = t.resolveAttributes(
+                    layer.mThemeAttrs, R.styleable.MaskableIconDrawableLayer);
+                updateLayerFromTypedArray(layer, a);
+                a.recycle();
+            }
+
+            final Drawable d = layer.mDrawable;
+            if (d != null && d.canApplyTheme()) {
+                d.applyTheme(t);
+
+                // Update cached mask of child changing configurations.
+                state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
+            }
+        }
+    }
+
+    /**
+     * Inflates child layers using the specified parser.
+     */
+    void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
+            throws XmlPullParserException, IOException {
+        final LayerState state = mLayerState;
+
+        final int innerDepth = parser.getDepth() + 1;
+        int type;
+        int depth;
+        int childIndex = 0;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            if (depth > innerDepth) {
+                continue;
+            }
+            String tagName = parser.getName();
+            if (tagName.equals("background")) {
+                childIndex = BACKGROUND_ID;
+            } else if (tagName.equals("foreground")) {
+                childIndex = FOREGROUND_ID;
+            } else {
+                continue;
+            }
+
+            final ChildDrawable layer = new ChildDrawable(state.mDensity);
+            final TypedArray a = obtainAttributes(r, theme, attrs,
+                R.styleable.MaskableIconDrawableLayer);
+            updateLayerFromTypedArray(layer, a);
+            a.recycle();
+
+            // If the layer doesn't have a drawable or unresolved theme
+            // attribute for a drawable, attempt to parse one from the child
+            // element. If multiple child elements exist, we'll only use the
+            // first one.
+            if (layer.mDrawable == null && (layer.mThemeAttrs == null)) {
+                while ((type = parser.next()) == XmlPullParser.TEXT) {
+                }
+                if (type != XmlPullParser.START_TAG) {
+                    throw new XmlPullParserException(parser.getPositionDescription()
+                            + ": <foreground> or <background> tag requires a 'color' or 'drawable'"
+                            + "attribute or child tag defining a drawable");
+                }
+
+                // We found a child drawable. Take ownership.
+                layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
+                layer.mDrawable.setCallback(this);
+                state.mChildrenChangingConfigurations |=
+                        layer.mDrawable.getChangingConfigurations();
+            }
+            addLayer(childIndex, layer);
+        }
+    }
+
+    private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
+        final LayerState state = mLayerState;
+
+        // Account for any configuration changes.
+        state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
+
+        // Extract the theme attributes, if any.
+        layer.mThemeAttrs = a.extractThemeAttrs();
+
+        Drawable dr = a.getDrawable(R.styleable.MaskableIconDrawableLayer_drawable);
+        if (dr == null) {
+             int color = a.getColor(R.styleable.MaskableIconDrawableLayer_color, Color.TRANSPARENT);
+             if (color != Color.TRANSPARENT) {
+                 dr = new ColorDrawable(color);
+             }
+        }
+        if (dr != null) {
+            if (layer.mDrawable != null) {
+                // It's possible that a drawable was already set, in which case
+                // we should clear the callback. We may have also integrated the
+                // drawable's changing configurations, but we don't have enough
+                // information to revert that change.
+                layer.mDrawable.setCallback(null);
+            }
+
+            // Take ownership of the new drawable.
+            layer.mDrawable = dr;
+            layer.mDrawable.setCallback(this);
+            state.mChildrenChangingConfigurations |=
+                layer.mDrawable.getChangingConfigurations();
+        }
+    }
+
+    @Override
+    public boolean canApplyTheme() {
+        return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean isProjected() {
+        if (super.isProjected()) {
+            return true;
+        }
+
+        final ChildDrawable[] layers = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            if (layers[i].mDrawable.isProjected()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Temporarily suspends child invalidation.
+     *
+     * @see #resumeChildInvalidation()
+     */
+    private void suspendChildInvalidation() {
+        mSuspendChildInvalidation = true;
+    }
+
+    /**
+     * Resumes child invalidation after suspension, immediately performing an
+     * invalidation if one was requested by a child during suspension.
+     *
+     * @see #suspendChildInvalidation()
+     */
+    private void resumeChildInvalidation() {
+        mSuspendChildInvalidation = false;
+
+        if (mChildRequestedInvalidation) {
+            mChildRequestedInvalidation = false;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void invalidateDrawable(@NonNull Drawable who) {
+        if (mSuspendChildInvalidation) {
+            mChildRequestedInvalidation = true;
+        } else {
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+        scheduleSelf(what, when);
+    }
+
+    @Override
+    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+        unscheduleSelf(what);
+    }
+
+    @Override
+    public @Config int getChangingConfigurations() {
+        return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
+    }
+
+    @Override
+    public void setHotspot(float x, float y) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setHotspot(x, y);
+            }
+        }
+    }
+
+    @Override
+    public void setHotspotBounds(int left, int top, int right, int bottom) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setHotspotBounds(left, top, right, bottom);
+            }
+        }
+
+        if (mHotspotBounds == null) {
+            mHotspotBounds = new Rect(left, top, right, bottom);
+        } else {
+            mHotspotBounds.set(left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public void getHotspotBounds(Rect outRect) {
+        if (mHotspotBounds != null) {
+            outRect.set(mHotspotBounds);
+        } else {
+            super.getHotspotBounds(outRect);
+        }
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        final boolean changed = super.setVisible(visible, restart);
+        final ChildDrawable[] array = mLayerState.mChildren;
+
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setVisible(visible, restart);
+            }
+        }
+
+        return changed;
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setDither(dither);
+            }
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setAlpha(alpha);
+            }
+        }
+    }
+
+    @Override
+    public int getAlpha() {
+        final Drawable dr = getFirstNonNullDrawable();
+        if (dr != null) {
+            return dr.getAlpha();
+        } else {
+            return super.getAlpha();
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setColorFilter(colorFilter);
+            }
+        }
+    }
+
+    @Override
+    public void setTintList(ColorStateList tint) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.N_CHILDREN;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setTintList(tint);
+            }
+        }
+    }
+
+    @Override
+    public void setTintMode(Mode tintMode) {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int N = mLayerState.N_CHILDREN;
+        for (int i = 0; i < N; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setTintMode(tintMode);
+            }
+        }
+    }
+
+    private Drawable getFirstNonNullDrawable() {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                return dr;
+            }
+        }
+        return null;
+    }
+
+    public void setOpacity(int opacity) {
+        mLayerState.mOpacityOverride = opacity;
+    }
+
+    @Override
+    public int getOpacity() {
+        if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
+            return mLayerState.mOpacityOverride;
+        }
+        return mLayerState.getOpacity();
+    }
+
+    @Override
+    public void setAutoMirrored(boolean mirrored) {
+        mLayerState.mAutoMirrored = mirrored;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.setAutoMirrored(mirrored);
+            }
+        }
+    }
+
+    @Override
+    public boolean isAutoMirrored() {
+        return mLayerState.mAutoMirrored;
+    }
+
+    @Override
+    public void jumpToCurrentState() {
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.jumpToCurrentState();
+            }
+        }
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mLayerState.isStateful();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        boolean changed = false;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null && dr.isStateful() && dr.setState(state)) {
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
+        return changed;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        boolean changed = false;
+
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null && dr.setLevel(level)) {
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            updateLayerBounds(getBounds());
+        }
+
+        return changed;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE);
+    }
+
+    private int getMaxIntrinsicWidth() {
+        int width = -1;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final ChildDrawable r = mLayerState.mChildren[i];
+            final int w = r.mDrawable.getIntrinsicWidth();
+            if (w > width) {
+                width = w;
+            }
+        }
+        return width;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE);
+    }
+
+    private int getMaxIntrinsicHeight() {
+        int height = -1;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final ChildDrawable r = mLayerState.mChildren[i];
+            final int h = r.mDrawable.getIntrinsicHeight();
+            if (h > height) {
+                height = h;
+            }
+        }
+        return height;
+    }
+
+    @Override
+    public ConstantState getConstantState() {
+        if (mLayerState.canConstantState()) {
+            mLayerState.mChangingConfigurations = getChangingConfigurations();
+            return mLayerState;
+        }
+        return null;
+    }
+
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mLayerState = createConstantState(mLayerState, null);
+            for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+                final Drawable dr = mLayerState.mChildren[i].mDrawable;
+                if (dr != null) {
+                    dr.mutate();
+                }
+            }
+            mMutated = true;
+        }
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void clearMutated() {
+        super.clearMutated();
+        final ChildDrawable[] array = mLayerState.mChildren;
+        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+            final Drawable dr = array[i].mDrawable;
+            if (dr != null) {
+                dr.clearMutated();
+            }
+        }
+        mMutated = false;
+    }
+
+    static class ChildDrawable {
+        public Drawable mDrawable;
+        public int[] mThemeAttrs;
+        public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+        ChildDrawable(int density) {
+            mDensity = density;
+        }
+
+        ChildDrawable(@NonNull ChildDrawable orig, @NonNull MaskableIconDrawable owner,
+                @Nullable Resources res) {
+
+            final Drawable dr = orig.mDrawable;
+            final Drawable clone;
+            if (dr != null) {
+                final ConstantState cs = dr.getConstantState();
+                if (cs == null) {
+                    clone = dr;
+                } else if (res != null) {
+                    clone = cs.newDrawable(res);
+                } else {
+                    clone = cs.newDrawable();
+                }
+                clone.setCallback(owner);
+                clone.setBounds(dr.getBounds());
+                clone.setLevel(dr.getLevel());
+            } else {
+                clone = null;
+            }
+
+            mDrawable = clone;
+            mThemeAttrs = orig.mThemeAttrs;
+
+            mDensity = Drawable.resolveDensity(res, orig.mDensity);
+        }
+
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null
+                    || (mDrawable != null && mDrawable.canApplyTheme());
+        }
+
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                mDensity = targetDensity;
+            }
+        }
+    }
+
+    static class LayerState extends ConstantState {
+        private int[] mThemeAttrs;
+
+        final static int N_CHILDREN = 2;
+        ChildDrawable[] mChildren;
+
+        int mDensity;
+        int mOpacityOverride = PixelFormat.UNKNOWN;
+
+        @Config int mChangingConfigurations;
+        @Config int mChildrenChangingConfigurations;
+
+        private boolean mCheckedOpacity;
+        private int mOpacity;
+
+        private boolean mCheckedStateful;
+        private boolean mIsStateful;
+        private boolean mAutoMirrored = false;
+
+        LayerState(@Nullable LayerState orig, @NonNull MaskableIconDrawable owner,
+                @Nullable Resources res) {
+            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
+            mChildren = new ChildDrawable[N_CHILDREN];
+            if (orig != null) {
+                final ChildDrawable[] origChildDrawable = orig.mChildren;
+
+                mChangingConfigurations = orig.mChangingConfigurations;
+                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
+
+                for (int i = 0; i < N_CHILDREN; i++) {
+                    final ChildDrawable or = origChildDrawable[i];
+                    mChildren[i] = new ChildDrawable(or, owner, res);
+                }
+
+                mCheckedOpacity = orig.mCheckedOpacity;
+                mOpacity = orig.mOpacity;
+                mCheckedStateful = orig.mCheckedStateful;
+                mIsStateful = orig.mIsStateful;
+                mAutoMirrored = orig.mAutoMirrored;
+                mThemeAttrs = orig.mThemeAttrs;
+                mOpacityOverride = orig.mOpacityOverride;
+            } else {
+                for (int i = 0; i < N_CHILDREN; i++) {
+                    mChildren[i] = new ChildDrawable(mDensity);
+                }
+            }
+        }
+
+        public final void setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                mDensity = targetDensity;
+            }
+        }
+
+        @Override
+        public boolean canApplyTheme() {
+            if (mThemeAttrs != null || super.canApplyTheme()) {
+                return true;
+            }
+
+            final ChildDrawable[] array = mChildren;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                final ChildDrawable layer = array[i];
+                if (layer.canApplyTheme()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new MaskableIconDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(@Nullable Resources res) {
+            return new MaskableIconDrawable(this, res);
+        }
+
+        @Override
+        public @Config int getChangingConfigurations() {
+            return mChangingConfigurations
+                    | mChildrenChangingConfigurations;
+        }
+
+        public final int getOpacity() {
+            if (mCheckedOpacity) {
+                return mOpacity;
+            }
+
+            final ChildDrawable[] array = mChildren;
+
+            // Seek to the first non-null drawable.
+            int firstIndex = -1;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                if (array[i].mDrawable != null) {
+                    firstIndex = i;
+                    break;
+                }
+            }
+
+            int op;
+            if (firstIndex >= 0) {
+                op = array[firstIndex].mDrawable.getOpacity();
+            } else {
+                op = PixelFormat.TRANSPARENT;
+            }
+
+            // Merge all remaining non-null drawables.
+            for (int i = firstIndex + 1; i < N_CHILDREN; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null) {
+                    op = Drawable.resolveOpacity(op, dr.getOpacity());
+                }
+            }
+
+            mOpacity = op;
+            mCheckedOpacity = true;
+            return op;
+        }
+
+        public final boolean isStateful() {
+            if (mCheckedStateful) {
+                return mIsStateful;
+            }
+
+            final ChildDrawable[] array = mChildren;
+            boolean isStateful = false;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.isStateful()) {
+                    isStateful = true;
+                    break;
+                }
+            }
+
+            mIsStateful = isStateful;
+            mCheckedStateful = true;
+            return isStateful;
+        }
+
+        public final boolean canConstantState() {
+            final ChildDrawable[] array = mChildren;
+            for (int i = 0; i < N_CHILDREN; i++) {
+                final Drawable dr = array[i].mDrawable;
+                if (dr != null && dr.getConstantState() == null) {
+                    return false;
+                }
+            }
+
+            // Don't cache the result, this method is not called very often.
+            return true;
+        }
+
+        public void invalidateCache() {
+            mCheckedOpacity = false;
+            mCheckedStateful = false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/fonts/FontRequest.java b/graphics/java/android/graphics/fonts/FontRequest.java
new file mode 100644
index 0000000..e50df6f
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontRequest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information about a font request that may be sent to a Font Provider.
+ */
+public final class FontRequest implements Parcelable {
+    private final String mProviderAuthority;
+    private final String mQuery;
+
+    /**
+     * @param providerAuthority The authority of the Font Provider to be used for the request.
+     * @param query The query to be sent over to the provider. Refer to your font provider's
+     *              documentation on the format of this string.
+     */
+    public FontRequest(@NonNull String providerAuthority, @NonNull String query) {
+        mProviderAuthority = Preconditions.checkNotNull(providerAuthority);
+        mQuery = Preconditions.checkNotNull(query);
+    }
+
+    /**
+     * Returns the selected font provider's authority. This tells the system what font provider
+     * it should request the font from.
+     */
+    public String getProviderAuthority() {
+        return mProviderAuthority;
+    }
+
+    /**
+     * Returns the query string. Refer to your font provider's documentation on the format of this
+     * string.
+     */
+    public String getQuery() {
+        return mQuery;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mProviderAuthority);
+        dest.writeString(mQuery);
+    }
+
+    private FontRequest(Parcel in) {
+        mProviderAuthority = in.readString();
+        mQuery = in.readString();
+    }
+
+    public static final Parcelable.Creator<FontRequest> CREATOR =
+            new Parcelable.Creator<FontRequest>() {
+                @Override
+                public FontRequest createFromParcel(Parcel in) {
+                    return new FontRequest(in);
+                }
+
+                @Override
+                public FontRequest[] newArray(int size) {
+                    return new FontRequest[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "FontRequest {"
+                + "mProviderAuthority: " + mProviderAuthority
+                + ", mQuery: " + mQuery
+                + "}";
+    }
+}
diff --git a/graphics/java/android/graphics/fonts/FontResult.java b/graphics/java/android/graphics/fonts/FontResult.java
new file mode 100644
index 0000000..3ef99fd
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontResult.java
@@ -0,0 +1,108 @@
+/*
+ * 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 android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Results returned from a Font Provider to the system.
+ * @hide
+ */
+public final class FontResult implements Parcelable {
+    private final ParcelFileDescriptor mFileDescriptor;
+    private final int mTtcIndex;
+    private final String mFontVariationSettings;
+    private final int mStyle;
+
+    /**
+     * Creates a FontResult with all the information needed about a provided font.
+     * @param fileDescriptor A ParcelFileDescriptor pointing to the font file. This shoult point to
+     *                       a real file or shared memory, as the client will mmap the given file
+     *                       descriptor. Pipes, sockets and other non-mmap-able file descriptors
+     *                       will fail to load in the client application.
+     * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
+     * @param fontVariationSettings If providing a variation font, the settings for it. May be null.
+     * @param style One of {@link android.graphics.Typeface#NORMAL},
+     *              {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC}
+     *              or {@link android.graphics.Typeface#BOLD_ITALIC}
+     */
+    public FontResult(@NonNull ParcelFileDescriptor fileDescriptor, int ttcIndex,
+            @Nullable String fontVariationSettings, int style) {
+        mFileDescriptor = Preconditions.checkNotNull(fileDescriptor);
+        mTtcIndex = ttcIndex;
+        mFontVariationSettings = fontVariationSettings;
+        mStyle = style;
+    }
+
+    public ParcelFileDescriptor getFileDescriptor() {
+        return mFileDescriptor;
+    }
+
+    public int getTtcIndex() {
+        return mTtcIndex;
+    }
+
+    public String getFontVariationSettings() {
+        return mFontVariationSettings;
+    }
+
+    public int getStyle() {
+        return mStyle;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mFileDescriptor, flags);
+        dest.writeInt(mTtcIndex);
+        dest.writeString(mFontVariationSettings);
+        dest.writeInt(mStyle);
+    }
+
+    private FontResult(Parcel in) {
+        mFileDescriptor = in.readParcelable(null);
+        mTtcIndex = in.readInt();
+        mFontVariationSettings = in.readString();
+        mStyle = in.readInt();
+    }
+
+    public static final Parcelable.Creator<FontResult> CREATOR =
+            new Parcelable.Creator<FontResult>() {
+                @Override
+                public FontResult createFromParcel(Parcel in) {
+                    return new FontResult(in);
+                }
+
+                @Override
+                public FontResult[] newArray(int size) {
+                    return new FontResult[size];
+                }
+            };
+}
diff --git a/graphics/java/android/graphics/fonts/FontSpec.aidl b/graphics/java/android/graphics/fonts/FontSpec.aidl
new file mode 100644
index 0000000..dddea25
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/FontSpec.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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 android.graphics.fonts;
+
+parcelable FontSpec;
\ No newline at end of file
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
new file mode 100644
index 0000000..e36632a
--- /dev/null
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -0,0 +1,228 @@
+/*
+ * 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 android.security.keystore;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Process;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
+import android.security.keymaster.KeymasterDefs;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Utilities for attesting the device's hardware identifiers.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public abstract class AttestationUtils {
+    private static AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+    private AttestationUtils() {
+    }
+
+    /**
+     * Specifies that the device should attest its serial number. For use with
+     * {@link #attestDeviceIds}.
+     *
+     * @see #attestDeviceIds
+     */
+    public static final int ID_TYPE_SERIAL = 1;
+
+    /**
+     * Specifies that the device should attest its IMEIs. For use with {@link #attestDeviceIds}.
+     *
+     * @see #attestDeviceIds
+     */
+    public static final int ID_TYPE_IMEI = 2;
+
+    /**
+     * Specifies that the device should attest its MEIDs. For use with {@link #attestDeviceIds}.
+     *
+     * @see #attestDeviceIds
+     */
+    public static final int ID_TYPE_MEID = 3;
+
+    /**
+     * Performs attestation of the device's identifiers. This method returns a certificate chain
+     * whose first element contains the requested device identifiers in an extension. The device's
+     * brand, device and product are always also included in the attestation. If the device supports
+     * attestation in secure hardware, the chain will be rooted at a trustworthy CA key. Otherwise,
+     * the chain will be rooted at an untrusted certificate. See
+     * <a href="https://developer.android.com/training/articles/security-key-attestation.html">
+     * Key Attestation</a> for the format of the certificate extension.
+     * <p>
+     * Attestation will only be successful when all of the following are true:
+     * 1) The device has been set up to support device identifier attestation at the factory.
+     * 2) The user has not permanently disabled device identifier attestation.
+     * 3) You have permission to access the device identifiers you are requesting attestation for.
+     * <p>
+     * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is
+     * unsuccessful, the device may not support it in general or the user may have permanently
+     * disabled it.
+     * <p>
+     * The caller must hold {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+     * permission.
+     *
+     * @param context the context to use for retrieving device identifiers.
+     * @param idTypes the types of device identifiers to attest.
+     * @param attestationChallenge a blob to include in the certificate alongside the device
+     * identifiers.
+     *
+     * @return a certificate chain containing the requested device identifiers in the first element
+     *
+     * @exception SecurityException if you are not permitted to obtain an attestation of the
+     * device's identifiers.
+     * @exception DeviceIdAttestationException if the attestation operation fails.
+     */
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @NonNull public static X509Certificate[] attestDeviceIds(Context context,
+            @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+            DeviceIdAttestationException {
+        // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
+        if (idTypes == null) {
+            throw new NullPointerException("Missing id types");
+        }
+        if (attestationChallenge == null) {
+            throw new NullPointerException("Missing attestation challenge");
+        }
+        final KeymasterArguments attestArgs = new KeymasterArguments();
+        attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge);
+        final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+        for (int idType : idTypes) {
+            idTypesSet.add(idType);
+        }
+        TelephonyManager telephonyService = null;
+        if (idTypesSet.contains(ID_TYPE_IMEI) || idTypesSet.contains(ID_TYPE_MEID)) {
+            telephonyService = (TelephonyManager) context.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            if (telephonyService == null) {
+                throw new DeviceIdAttestationException("Unable to access telephony service");
+            }
+        }
+        for (final Integer idType : idTypesSet) {
+            switch (idType) {
+                case ID_TYPE_SERIAL:
+                    attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+                            Build.getSerial().getBytes(StandardCharsets.UTF_8));
+                    break;
+                case ID_TYPE_IMEI: {
+                    final String imei = telephonyService.getImei(0);
+                    if (imei == null) {
+                        throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+                    }
+                    attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+                            imei.getBytes(StandardCharsets.UTF_8));
+                    break;
+                }
+                case ID_TYPE_MEID: {
+                    final String meid = telephonyService.getDeviceId();
+                    if (meid == null) {
+                        throw new DeviceIdAttestationException("Unable to retrieve MEID");
+                    }
+                    attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+                            meid.getBytes(StandardCharsets.UTF_8));
+                    break;
+                }
+                default:
+                    throw new IllegalArgumentException("Unknown device ID type " + idType);
+            }
+        }
+        attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
+                Build.BRAND.getBytes(StandardCharsets.UTF_8));
+        attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
+                Build.DEVICE.getBytes(StandardCharsets.UTF_8));
+        attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
+                Build.PRODUCT.getBytes(StandardCharsets.UTF_8));
+
+        final KeyStore keyStore = KeyStore.getInstance();
+        final String keyAlias = "android_internal_device_id_attestation-"
+                + Process.myPid() + "-" + sSequenceNumber.incrementAndGet();
+        // Clear any leftover temporary key.
+        if (!keyStore.delete(keyAlias)) {
+            throw new DeviceIdAttestationException("Unable to remove temporary key");
+        }
+        try {
+            // Generate a temporary key.
+            final KeymasterArguments generateArgs = new KeymasterArguments();
+            generateArgs.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_VERIFY);
+            generateArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+            generateArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+            generateArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
+            generateArgs.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
+            generateArgs.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
+            generateArgs.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
+                    RSAKeyGenParameterSpec.F4);
+            int errorCode = keyStore.generateKey(keyAlias, generateArgs, null, 0,
+                    new KeyCharacteristics());
+            if (errorCode != KeyStore.NO_ERROR) {
+                throw new DeviceIdAttestationException("Unable to create temporary key",
+                        KeyStore.getKeyStoreException(errorCode));
+            }
+
+            // Perform attestation.
+            final KeymasterCertificateChain outChain = new KeymasterCertificateChain();
+            errorCode = keyStore.attestKey(keyAlias, attestArgs, outChain);
+            if (errorCode != KeyStore.NO_ERROR) {
+                throw new DeviceIdAttestationException("Unable to perform attestation",
+                        KeyStore.getKeyStoreException(errorCode));
+            }
+
+            // Extract certificate chain.
+            final Collection<byte[]> rawChain = outChain.getCertificates();
+            if (rawChain.size() < 2) {
+                throw new DeviceIdAttestationException("Attestation certificate chain contained "
+                        + rawChain.size() + " entries. At least two are required.");
+            }
+            final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream();
+            try {
+                for (final byte[] cert : rawChain) {
+                    concatenatedRawChain.write(cert);
+                }
+                return CertificateFactory.getInstance("X.509").generateCertificates(
+                        new ByteArrayInputStream(concatenatedRawChain.toByteArray()))
+                                .toArray(new X509Certificate[0]);
+            } catch (Exception e) {
+                throw new DeviceIdAttestationException("Unable to construct certificate chain", e);
+            }
+        } finally {
+            // Remove temporary key.
+            keyStore.delete(keyAlias);
+        }
+    }
+}
diff --git a/keystore/java/android/security/keystore/DeviceIdAttestationException.java b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
new file mode 100644
index 0000000..e18d193
--- /dev/null
+++ b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
@@ -0,0 +1,45 @@
+/*
+ * 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 android.security.keystore;
+
+/**
+ * Thrown when {@link AttestationUtils} is unable to attest the given device ids.
+ *
+ * @hide
+ */
+public class DeviceIdAttestationException extends Exception {
+    /**
+     * Constructs a new {@code DeviceIdAttestationException} with the current stack trace and the
+     * specified detail message.
+     *
+     * @param detailMessage the detail message for this exception.
+     */
+    public DeviceIdAttestationException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    /**
+     * Constructs a new {@code DeviceIdAttestationException} with the current stack trace, the
+     * specified detail message and the specified cause.
+     *
+     * @param message the detail message for this exception.
+     * @param cause the cause of this exception, may be {@code null}.
+     */
+    public DeviceIdAttestationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index e068900..acacd76 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -288,22 +288,34 @@
 {
     AutoMutex _l(mLock);
     const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) };
-    ResTable tables[2];
+    Asset* assets[2] = {NULL, NULL};
+    bool ret = false;
+    {
+        ResTable tables[2];
 
-    for (int i = 0; i < 2; ++i) {
-        asset_path ap;
-        ap.type = kFileTypeRegular;
-        ap.path = paths[i];
-        Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
-        if (ass == NULL) {
-            ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
-            return false;
+        for (int i = 0; i < 2; ++i) {
+            asset_path ap;
+            ap.type = kFileTypeRegular;
+            ap.path = paths[i];
+            assets[i] = openNonAssetInPathLocked("resources.arsc",
+                    Asset::ACCESS_BUFFER, ap);
+            if (assets[i] == NULL) {
+                ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
+                goto exit;
+            }
+            if (tables[i].add(assets[i]) != NO_ERROR) {
+                ALOGW("failed to add %s to resource table", paths[i].string());
+                goto exit;
+            }
         }
-        tables[i].add(ass);
+        ret = tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
+                targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
     }
 
-    return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
-            targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
+exit:
+    delete assets[0];
+    delete assets[1];
+    return ret;
 }
 
 bool AssetManager::addDefaultAssets()
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index a4bcc62..763a178 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1907,7 +1907,7 @@
     if (diff != 0) return diff;
     diff = (int32_t)(screenLayout2 - o.screenLayout2);
     if (diff != 0) return diff;
-    diff = (int32_t)(colorimetry - o.colorimetry);
+    diff = (int32_t)(colorMode - o.colorMode);
     if (diff != 0) return diff;
     diff = (int32_t)(uiMode - o.uiMode);
     if (diff != 0) return diff;
@@ -1969,8 +1969,8 @@
     if (screenLayout2 != o.screenLayout2) {
         return screenLayout2 < o.screenLayout2 ? -1 : 1;
     }
-    if (colorimetry != o.colorimetry) {
-        return colorimetry < o.colorimetry ? -1 : 1;
+    if (colorMode != o.colorMode) {
+        return colorMode < o.colorMode ? -1 : 1;
     }
     if (uiMode != o.uiMode) {
         return uiMode < o.uiMode ? -1 : 1;
@@ -1997,8 +1997,8 @@
     if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) diffs |= CONFIG_LAYOUTDIR;
     if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR)) diffs |= CONFIG_SCREEN_LAYOUT;
     if ((screenLayout2 & MASK_SCREENROUND) != (o.screenLayout2 & MASK_SCREENROUND)) diffs |= CONFIG_SCREEN_ROUND;
-    if ((colorimetry & MASK_WIDE_COLOR_GAMUT) != (o.colorimetry & MASK_WIDE_COLOR_GAMUT)) diffs |= CONFIG_COLORIMETRY;
-    if ((colorimetry & MASK_HDR) != (o.colorimetry & MASK_HDR)) diffs |= CONFIG_COLORIMETRY;
+    if ((colorMode & MASK_WIDE_COLOR_GAMUT) != (o.colorMode & MASK_WIDE_COLOR_GAMUT)) diffs |= CONFIG_COLOR_MODE;
+    if ((colorMode & MASK_HDR) != (o.colorMode & MASK_HDR)) diffs |= CONFIG_COLOR_MODE;
     if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
     if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
     if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
@@ -2110,14 +2110,14 @@
         }
     }
 
-    if (colorimetry || o.colorimetry) {
-        if (((colorimetry^o.colorimetry) & MASK_HDR) != 0) {
-            if (!(colorimetry & MASK_HDR)) return false;
-            if (!(o.colorimetry & MASK_HDR)) return true;
+    if (colorMode || o.colorMode) {
+        if (((colorMode^o.colorMode) & MASK_HDR) != 0) {
+            if (!(colorMode & MASK_HDR)) return false;
+            if (!(o.colorMode & MASK_HDR)) return true;
         }
-        if (((colorimetry^o.colorimetry) & MASK_WIDE_COLOR_GAMUT) != 0) {
-            if (!(colorimetry & MASK_WIDE_COLOR_GAMUT)) return false;
-            if (!(o.colorimetry & MASK_WIDE_COLOR_GAMUT)) return true;
+        if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0) {
+            if (!(colorMode & MASK_WIDE_COLOR_GAMUT)) return false;
+            if (!(o.colorMode & MASK_WIDE_COLOR_GAMUT)) return true;
         }
     }
 
@@ -2408,14 +2408,14 @@
             }
         }
 
-        if (colorimetry || o.colorimetry) {
-            if (((colorimetry^o.colorimetry) & MASK_WIDE_COLOR_GAMUT) != 0 &&
-                    (requested->colorimetry & MASK_WIDE_COLOR_GAMUT)) {
-                return colorimetry & MASK_WIDE_COLOR_GAMUT;
+        if (colorMode || o.colorMode) {
+            if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0 &&
+                    (requested->colorMode & MASK_WIDE_COLOR_GAMUT)) {
+                return colorMode & MASK_WIDE_COLOR_GAMUT;
             }
-            if (((colorimetry^o.colorimetry) & MASK_HDR) != 0 &&
-                    (requested->colorimetry & MASK_HDR)) {
-                return colorimetry & MASK_HDR;
+            if (((colorMode^o.colorMode) & MASK_HDR) != 0 &&
+                    (requested->colorMode & MASK_HDR)) {
+                return colorMode & MASK_HDR;
             }
         }
 
@@ -2669,14 +2669,14 @@
             return false;
         }
 
-        const int hdr = colorimetry & MASK_HDR;
-        const int setHdr = settings.colorimetry & MASK_HDR;
+        const int hdr = colorMode & MASK_HDR;
+        const int setHdr = settings.colorMode & MASK_HDR;
         if (hdr != 0 && hdr != setHdr) {
             return false;
         }
 
-        const int wideColorGamut = colorimetry & MASK_WIDE_COLOR_GAMUT;
-        const int setWideColorGamut = settings.colorimetry & MASK_WIDE_COLOR_GAMUT;
+        const int wideColorGamut = colorMode & MASK_WIDE_COLOR_GAMUT;
+        const int setWideColorGamut = settings.colorMode & MASK_WIDE_COLOR_GAMUT;
         if (wideColorGamut != 0 && wideColorGamut != setWideColorGamut) {
             return false;
         }
@@ -3000,9 +3000,9 @@
                 break;
         }
     }
-    if ((colorimetry&MASK_HDR) != 0) {
+    if ((colorMode&MASK_HDR) != 0) {
         if (res.size() > 0) res.append("-");
-        switch (colorimetry&MASK_HDR) {
+        switch (colorMode&MASK_HDR) {
             case ResTable_config::HDR_NO:
                 res.append("lowdr");
                 break;
@@ -3010,13 +3010,13 @@
                 res.append("highdr");
                 break;
             default:
-                res.appendFormat("hdr=%d", dtohs(colorimetry&MASK_HDR));
+                res.appendFormat("hdr=%d", dtohs(colorMode&MASK_HDR));
                 break;
         }
     }
-    if ((colorimetry&MASK_WIDE_COLOR_GAMUT) != 0) {
+    if ((colorMode&MASK_WIDE_COLOR_GAMUT) != 0) {
         if (res.size() > 0) res.append("-");
-        switch (colorimetry&MASK_WIDE_COLOR_GAMUT) {
+        switch (colorMode&MASK_WIDE_COLOR_GAMUT) {
             case ResTable_config::WIDE_COLOR_GAMUT_NO:
                 res.append("nowidecg");
                 break;
@@ -3024,7 +3024,7 @@
                 res.append("widecg");
                 break;
             default:
-                res.appendFormat("wideColorGamut=%d", dtohs(colorimetry&MASK_WIDE_COLOR_GAMUT));
+                res.appendFormat("wideColorGamut=%d", dtohs(colorMode&MASK_WIDE_COLOR_GAMUT));
                 break;
         }
     }
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 1e4aee9..86ab123 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1147,25 +1147,25 @@
     };
 
     enum {
-        // colorimetry bits for wide-color gamut/narrow-color gamut.
+        // colorMode bits for wide-color gamut/narrow-color gamut.
         MASK_WIDE_COLOR_GAMUT = 0x03,
         WIDE_COLOR_GAMUT_ANY = ACONFIGURATION_WIDE_COLOR_GAMUT_ANY,
         WIDE_COLOR_GAMUT_NO = ACONFIGURATION_WIDE_COLOR_GAMUT_NO,
         WIDE_COLOR_GAMUT_YES = ACONFIGURATION_WIDE_COLOR_GAMUT_YES,
 
-        // colorimetry bits for HDR/LDR.
+        // colorMode bits for HDR/LDR.
         MASK_HDR = 0x0c,
-        SHIFT_COLORIMETRY_HDR = 2,
-        HDR_ANY = ACONFIGURATION_HDR_ANY << SHIFT_COLORIMETRY_HDR,
-        HDR_NO = ACONFIGURATION_HDR_NO << SHIFT_COLORIMETRY_HDR,
-        HDR_YES = ACONFIGURATION_HDR_YES << SHIFT_COLORIMETRY_HDR,
+        SHIFT_COLOR_MODE_HDR = 2,
+        HDR_ANY = ACONFIGURATION_HDR_ANY << SHIFT_COLOR_MODE_HDR,
+        HDR_NO = ACONFIGURATION_HDR_NO << SHIFT_COLOR_MODE_HDR,
+        HDR_YES = ACONFIGURATION_HDR_YES << SHIFT_COLOR_MODE_HDR,
     };
 
     // An extension of screenConfig.
     union {
         struct {
             uint8_t screenLayout2;      // Contains round/notround qualifier.
-            uint8_t colorimetry;        // Wide-gamut, HDR, etc.
+            uint8_t colorMode;          // Wide-gamut, HDR, etc.
             uint16_t screenConfigPad2;  // Reserved padding.
         };
         uint32_t screenConfig2;
@@ -1208,7 +1208,7 @@
         CONFIG_UI_MODE = ACONFIGURATION_UI_MODE,
         CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
         CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND,
-        CONFIG_COLORIMETRY = ACONFIGURATION_COLORIMETRY,
+        CONFIG_COLOR_MODE = ACONFIGURATION_COLOR_MODE,
     };
     
     // Compare two configuration, returning CONFIG_* flags set for each value
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index 3e5aca7..b54915f 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -187,9 +187,9 @@
   memset(&defaultConfig, 0, sizeof(defaultConfig));
 
   ResTable_config wideGamutConfig = defaultConfig;
-  wideGamutConfig.colorimetry = ResTable_config::WIDE_COLOR_GAMUT_YES;
+  wideGamutConfig.colorMode = ResTable_config::WIDE_COLOR_GAMUT_YES;
 
-  EXPECT_EQ(defaultConfig.diff(wideGamutConfig), ResTable_config::CONFIG_COLORIMETRY);
+  EXPECT_EQ(defaultConfig.diff(wideGamutConfig), ResTable_config::CONFIG_COLOR_MODE);
 }
 
 TEST(ConfigTest, ScreenIsHdr) {
@@ -197,9 +197,9 @@
   memset(&defaultConfig, 0, sizeof(defaultConfig));
 
   ResTable_config hdrConfig = defaultConfig;
-  hdrConfig.colorimetry = ResTable_config::HDR_YES;
+  hdrConfig.colorMode = ResTable_config::HDR_YES;
 
-  EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLORIMETRY);
+  EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLOR_MODE);
 }
 
 }  // namespace android.
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 5e4a7f7..3853356 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -104,15 +104,15 @@
     }
 }
 
-bool DisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
-        std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
+bool DisplayList::prepareListAndChildren(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+        std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
     info.prepareTextures = info.canvasContext.pinImages(bitmapResources);
 
     for (auto&& op : children) {
         RenderNode* childNode = op->renderNode;
         info.damageAccumulator->pushTransform(&op->localMatrix);
         bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
-        childFn(childNode, info, childFunctorsNeedLayer);
+        childFn(childNode, observer, info, childFunctorsNeedLayer);
         info.damageAccumulator->popTransform();
     }
 
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index cab092f..ef0fd31 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -125,8 +125,8 @@
 
     virtual void syncContents();
     virtual void updateChildren(std::function<void(RenderNode*)> updateFn);
-    virtual bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
-            std::function<void(RenderNode*, TreeInfo&, bool)> childFn);
+    virtual bool prepareListAndChildren(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+            std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn);
 
 protected:
     // allocator into which all ops and LsaVector arrays allocated
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index a5443d9..7d8f046 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -22,6 +22,7 @@
 #include "OpDumper.h"
 #include "RecordedOp.h"
 #include "TreeInfo.h"
+#include "utils/FatVector.h"
 #include "utils/MathUtils.h"
 #include "utils/StringUtils.h"
 #include "utils/TraceUtils.h"
@@ -39,6 +40,20 @@
 namespace android {
 namespace uirenderer {
 
+// Used for tree mutations that are purely destructive.
+// Generic tree mutations should use MarkAndSweepObserver instead
+class ImmediateRemoved : public TreeObserver {
+public:
+    explicit ImmediateRemoved(TreeInfo* info) : mTreeInfo(info) {}
+
+    void onMaybeRemovedFromTree(RenderNode* node) override {
+        node->onRemovedFromTree(mTreeInfo);
+    }
+
+private:
+    TreeInfo* mTreeInfo;
+};
+
 RenderNode::RenderNode()
         : mDirtyPropertyFields(0)
         , mNeedsDisplayListSync(false)
@@ -49,20 +64,17 @@
 }
 
 RenderNode::~RenderNode() {
-    deleteDisplayList(nullptr);
+    ImmediateRemoved observer(nullptr);
+    deleteDisplayList(observer);
     delete mStagingDisplayList;
     LOG_ALWAYS_FATAL_IF(hasLayer(), "layer missed detachment!");
 }
 
-void RenderNode::setStagingDisplayList(DisplayList* displayList, TreeObserver* observer) {
+void RenderNode::setStagingDisplayList(DisplayList* displayList) {
+    mValid = (displayList != nullptr);
     mNeedsDisplayListSync = true;
     delete mStagingDisplayList;
     mStagingDisplayList = displayList;
-    // If mParentCount == 0 we are the sole reference to this RenderNode,
-    // so immediately free the old display list
-    if (!mParentCount && !mStagingDisplayList) {
-        deleteDisplayList(observer);
-    }
 }
 
 /**
@@ -187,12 +199,13 @@
 void RenderNode::prepareTree(TreeInfo& info) {
     ATRACE_CALL();
     LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing");
+    MarkAndSweepRemoved observer(&info);
 
     // The OpenGL renderer reserves the stencil buffer for overdraw debugging.  Functors
     // will need to be drawn in a layer.
     bool functorsNeedLayer = Properties::debugOverdraw && !Properties::isSkiaEnabled();
 
-    prepareTreeImpl(info, functorsNeedLayer);
+    prepareTreeImpl(observer, info, functorsNeedLayer);
 }
 
 void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
@@ -283,7 +296,7 @@
  * While traversing down the tree, functorsNeedLayer flag is set to true if anything that uses the
  * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
  */
-void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) {
+void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
     info.damageAccumulator->pushTransform(this);
 
     if (info.mode == TreeInfo::MODE_FULL) {
@@ -309,14 +322,14 @@
 
     prepareLayer(info, animatorDirtyMask);
     if (info.mode == TreeInfo::MODE_FULL) {
-        pushStagingDisplayListChanges(info);
+        pushStagingDisplayListChanges(observer, info);
     }
 
     if (mDisplayList) {
         info.out.hasFunctors |= mDisplayList->hasFunctor();
-        bool isDirty = mDisplayList->prepareListAndChildren(info, childFunctorsNeedLayer,
-                [](RenderNode* child, TreeInfo& info, bool functorsNeedLayer) {
-            child->prepareTreeImpl(info, functorsNeedLayer);
+        bool isDirty = mDisplayList->prepareListAndChildren(observer, info, childFunctorsNeedLayer,
+                [](RenderNode* child, TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
+            child->prepareTreeImpl(observer, info, functorsNeedLayer);
         });
         if (isDirty) {
             damageSelf(info);
@@ -353,7 +366,7 @@
     }
 }
 
-void RenderNode::syncDisplayList(TreeInfo* info) {
+void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
     // Make sure we inc first so that we don't fluctuate between 0 and 1,
     // which would thrash the layer cache
     if (mStagingDisplayList) {
@@ -361,7 +374,7 @@
             child->incParentRefCount();
         });
     }
-    deleteDisplayList(info ? info->observer : nullptr, info);
+    deleteDisplayList(observer, info);
     mDisplayList = mStagingDisplayList;
     mStagingDisplayList = nullptr;
     if (mDisplayList) {
@@ -369,20 +382,20 @@
     }
 }
 
-void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
+void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
     if (mNeedsDisplayListSync) {
         mNeedsDisplayListSync = false;
         // Damage with the old display list first then the new one to catch any
         // changes in isRenderable or, in the future, bounds
         damageSelf(info);
-        syncDisplayList(&info);
+        syncDisplayList(observer, &info);
         damageSelf(info);
     }
 }
 
-void RenderNode::deleteDisplayList(TreeObserver* observer, TreeInfo* info) {
+void RenderNode::deleteDisplayList(TreeObserver& observer, TreeInfo* info) {
     if (mDisplayList) {
-        mDisplayList->updateChildren([observer, info](RenderNode* child) {
+        mDisplayList->updateChildren([&observer, info](RenderNode* child) {
             child->decParentRefCount(observer, info);
         });
         if (!mDisplayList->reuseDisplayList(this, info ? &info->canvasContext : nullptr)) {
@@ -392,38 +405,53 @@
     mDisplayList = nullptr;
 }
 
-void RenderNode::destroyHardwareResources(TreeObserver* observer, TreeInfo* info) {
+void RenderNode::destroyHardwareResources(TreeInfo* info) {
+    ImmediateRemoved observer(info);
+    destroyHardwareResourcesImpl(observer, info);
+}
+
+void RenderNode::destroyHardwareResourcesImpl(TreeObserver& observer, TreeInfo* info) {
     if (hasLayer()) {
         renderthread::CanvasContext::destroyLayer(this);
     }
     if (mDisplayList) {
-        mDisplayList->updateChildren([observer, info](RenderNode* child) {
-            child->destroyHardwareResources(observer, info);
+        mDisplayList->updateChildren([&observer, info](RenderNode* child) {
+            child->destroyHardwareResourcesImpl(observer, info);
         });
-        if (mNeedsDisplayListSync) {
-            // Next prepare tree we are going to push a new display list, so we can
-            // drop our current one now
-            deleteDisplayList(observer, info);
+        setStagingDisplayList(nullptr);
+        deleteDisplayList(observer, info);
+    }
+}
+
+void RenderNode::destroyLayers() {
+    if (hasLayer()) {
+        renderthread::CanvasContext::destroyLayer(this);
+    }
+    if (mDisplayList) {
+        mDisplayList->updateChildren([](RenderNode* child) {
+            child->destroyLayers();
+        });
+    }
+}
+
+void RenderNode::decParentRefCount(TreeObserver& observer, TreeInfo* info) {
+    LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!");
+    mParentCount--;
+    if (!mParentCount) {
+        observer.onMaybeRemovedFromTree(this);
+        if (CC_UNLIKELY(mPositionListener.get())) {
+            mPositionListener->onPositionLost(*this, info);
         }
     }
 }
 
-void RenderNode::decParentRefCount(TreeObserver* observer, TreeInfo* info) {
-    LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!");
-    mParentCount--;
-    if (!mParentCount) {
-        if (observer) {
-            observer->onMaybeRemovedFromTree(this);
-        }
-        if (CC_UNLIKELY(mPositionListener.get())) {
-            mPositionListener->onPositionLost(*this, info);
-        }
-        // If a child of ours is being attached to our parent then this will incorrectly
-        // destroy its hardware resources. However, this situation is highly unlikely
-        // and the failure is "just" that the layer is re-created, so this should
-        // be safe enough
-        destroyHardwareResources(observer, info);
-    }
+void RenderNode::onRemovedFromTree(TreeInfo* info) {
+    destroyHardwareResources(info);
+}
+
+void RenderNode::clearRoot() {
+    ImmediateRemoved observer(nullptr);
+    decParentRefCount(observer);
 }
 
 /**
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index b8964f0..14a664c 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -34,6 +34,7 @@
 #include "RenderProperties.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "pipeline/skia/SkiaLayer.h"
+#include "utils/FatVector.h"
 
 #include <vector>
 
@@ -101,7 +102,7 @@
         kReplayFlag_ClipChildren = 0x1
     };
 
-    ANDROID_API void setStagingDisplayList(DisplayList* newData, TreeObserver* observer);
+    ANDROID_API void setStagingDisplayList(DisplayList* newData);
 
     void computeOrdering();
 
@@ -164,6 +165,10 @@
         return mStagingProperties;
     }
 
+    bool isValid() {
+        return mValid;
+    }
+
     int getWidth() const {
         return properties().getWidth();
     }
@@ -173,7 +178,8 @@
     }
 
     ANDROID_API virtual void prepareTree(TreeInfo& info);
-    void destroyHardwareResources(TreeObserver* observer, TreeInfo* info = nullptr);
+    void destroyHardwareResources(TreeInfo* info = nullptr);
+    void destroyLayers();
 
     // UI thread only!
     ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
@@ -232,24 +238,35 @@
         return mParentCount;
     }
 
+    void onRemovedFromTree(TreeInfo* info);
+
+    // Called by CanvasContext to promote a RenderNode to be a root node
+    void makeRoot() {
+        incParentRefCount();
+    }
+
+    // Called by CanvasContext when it drops a RenderNode from being a root node
+    void clearRoot();
+
 private:
     void computeOrderingImpl(RenderNodeOp* opState,
             std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface,
             const mat4* transformFromProjectionSurface);
 
     void syncProperties();
-    void syncDisplayList(TreeInfo* info);
+    void syncDisplayList(TreeObserver& observer, TreeInfo* info);
 
-    void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer);
+    void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer);
     void pushStagingPropertiesChanges(TreeInfo& info);
-    void pushStagingDisplayListChanges(TreeInfo& info);
+    void pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info);
     void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
     void pushLayerUpdate(TreeInfo& info);
-    void deleteDisplayList(TreeObserver* observer, TreeInfo* info = nullptr);
+    void deleteDisplayList(TreeObserver& observer, TreeInfo* info = nullptr);
+    void destroyHardwareResourcesImpl(TreeObserver& observer, TreeInfo* info = nullptr);
     void damageSelf(TreeInfo& info);
 
     void incParentRefCount() { mParentCount++; }
-    void decParentRefCount(TreeObserver* observer, TreeInfo* info = nullptr);
+    void decParentRefCount(TreeObserver& observer, TreeInfo* info = nullptr);
     void output(std::ostream& output, uint32_t level);
 
     String8 mName;
@@ -259,6 +276,10 @@
     RenderProperties mProperties;
     RenderProperties mStagingProperties;
 
+    // Owned by UI. Set when DL is set, cleared when DL cleared or when node detached
+    // (likely by parent re-record/removal)
+    bool mValid = false;
+
     bool mNeedsDisplayListSync;
     // WARNING: Do not delete this directly, you must go through deleteDisplayList()!
     DisplayList* mDisplayList;
@@ -361,5 +382,28 @@
     std::unique_ptr<skiapipeline::SkiaLayer> mSkiaLayer;
 }; // class RenderNode
 
+class MarkAndSweepRemoved : public TreeObserver {
+PREVENT_COPY_AND_ASSIGN(MarkAndSweepRemoved);
+
+public:
+    explicit MarkAndSweepRemoved(TreeInfo* info) : mTreeInfo(info) {}
+
+    void onMaybeRemovedFromTree(RenderNode* node) override {
+        mMarked.emplace_back(node);
+    }
+
+    ~MarkAndSweepRemoved() {
+        for (auto& node : mMarked) {
+            if (!node->hasParents()) {
+                node->onRemovedFromTree(mTreeInfo);
+            }
+        }
+    }
+
+private:
+    FatVector<sp<RenderNode>, 10> mMarked;
+    TreeInfo* mTreeInfo;
+};
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 89e2a01..e54bc36 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -367,7 +367,7 @@
 // (see https://code.google.com/p/skia/issues/detail?id=1303)
 bool SkiaCanvas::getClipBounds(SkRect* outRect) const {
     SkIRect ibounds;
-    if (!mCanvas->getClipDeviceBounds(&ibounds)) {
+    if (!mCanvas->getDeviceClipBounds(&ibounds)) {
         return false;
     }
 
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 749efdd..c6fbe2b 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -47,9 +47,9 @@
     // Due to the unordered nature of tree pushes, once prepareTree
     // is finished it is possible that the node was "resurrected" and has
     // a non-zero parent count.
-    virtual void onMaybeRemovedFromTree(RenderNode* node) {}
+    virtual void onMaybeRemovedFromTree(RenderNode* node) = 0;
 protected:
-    ~TreeObserver() {}
+    virtual ~TreeObserver() {}
 };
 
 // This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN
@@ -91,10 +91,6 @@
     LayerUpdateQueue* layerUpdateQueue = nullptr;
     ErrorHandler* errorHandler = nullptr;
 
-    // Optional, may be nullptr. Used to allow things to observe interesting
-    // tree state changes
-    TreeObserver* observer = nullptr;
-
     int32_t windowInsetLeft = 0;
     int32_t windowInsetTop = 0;
     bool updateWindowPositions = false;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index ca43156..9041b44 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -130,9 +130,9 @@
     sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(fontData.release());
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
 
-    minikin::FontFamily* family = new minikin::FontFamily();
     minikin::MinikinFont* font = new MinikinFontSkia(std::move(typeface), data, st.st_size, 0);
-    family->addFont(font);
+    minikin::FontFamily* family = new minikin::FontFamily(
+                 std::vector<minikin::Font>({ minikin::Font(font, minikin::FontStyle()) }));
     font->Unref();
 
     std::vector<minikin::FontFamily*> typefaces = { family };
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 6ca8d8b..ea302a1 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -59,8 +59,7 @@
     SkImageInfo canvasInfo = canvas->imageInfo();
     SkMatrix44 mat4(canvas->getTotalMatrix());
 
-    SkIRect ibounds;
-    canvas->getClipDeviceBounds(&ibounds);
+    SkIRect ibounds = canvas->getDeviceClipBounds();
 
     DrawGlInfo info;
     info.clipLeft = ibounds.fLeft;
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
index bf39dad..012c948 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
@@ -37,9 +37,9 @@
 public:
     GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
             : mFunctor(functor)
-            , mListener(listener) {
-        canvas->getClipBounds(&mBounds);
-    }
+            , mListener(listener)
+            , mBounds(canvas->getLocalClipBounds())
+    {}
     virtual ~GLFunctorDrawable();
 
     void syncFunctor() const;
@@ -51,7 +51,7 @@
  private:
      Functor* mFunctor;
      sp<GlFunctorLifecycleListener> mListener;
-     SkRect mBounds;
+     const SkRect mBounds;
 };
 
 }; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 9db8cd3..36d02ecb 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -51,8 +51,9 @@
     }
 }
 
-bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
-        std::function<void(RenderNode*, TreeInfo&, bool)> childFn) {
+bool SkiaDisplayList::prepareListAndChildren(TreeObserver& observer, TreeInfo& info,
+        bool functorsNeedLayer,
+        std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) {
     // If the prepare tree is triggered by the UI thread and no previous call to
     // pinImages has failed then we must pin all mutable images in the GPU cache
     // until the next UI thread draw.
@@ -74,7 +75,7 @@
         info.damageAccumulator->pushTransform(&mat4);
         // TODO: a layer is needed if the canvas is rotated or has a non-rect clip
         info.hasBackwardProjectedNodes = false;
-        childFn(childNode, info, functorsNeedLayer);
+        childFn(childNode, observer, info, functorsNeedLayer);
         hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes;
         info.damageAccumulator->popTransform();
     }
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index ff86fd1..2a01330 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -113,8 +113,8 @@
      *       to subclass from DisplayList
      */
 
-    bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer,
-            std::function<void(RenderNode*, TreeInfo&, bool)> childFn) override;
+    bool prepareListAndChildren(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer,
+            std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) override;
 
     /**
      *  Calls the provided function once for each child of this DisplayList
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1b3bf96..a53e5e0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -146,21 +146,38 @@
         , mProfiler(mFrames)
         , mContentDrawBounds(0, 0, 0, 0)
         , mRenderPipeline(std::move(renderPipeline)) {
+    rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
     mRenderThread.renderState().registerCanvasContext(this);
     mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
 }
 
 CanvasContext::~CanvasContext() {
-    destroy(nullptr);
+    destroy();
     mRenderThread.renderState().unregisterCanvasContext(this);
+    for (auto& node : mRenderNodes) {
+        node->clearRoot();
+    }
+    mRenderNodes.clear();
 }
 
-void CanvasContext::destroy(TreeObserver* observer) {
+void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
+    int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
+    node->makeRoot();
+    mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
+}
+
+void CanvasContext::removeRenderNode(RenderNode* node) {
+    node->clearRoot();
+    mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
+            mRenderNodes.end());
+}
+
+void CanvasContext::destroy() {
     stopDrawing();
     setSurface(nullptr);
-    freePrefetchedLayers(observer);
-    destroyHardwareResources(observer);
+    freePrefetchedLayers();
+    destroyHardwareResources();
     mAnimationContext->destroy();
 }
 
@@ -320,7 +337,7 @@
     mAnimationContext->runRemainingAnimations(info);
     GL_CHECKPOINT(MODERATE);
 
-    freePrefetchedLayers(info.observer);
+    freePrefetchedLayers();
     GL_CHECKPOINT(MODERATE);
 
     mIsDirty = true;
@@ -504,19 +521,19 @@
     }
 }
 
-void CanvasContext::freePrefetchedLayers(TreeObserver* observer) {
+void CanvasContext::freePrefetchedLayers() {
     if (mPrefetchedLayers.size()) {
         for (auto& node : mPrefetchedLayers) {
             ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...",
                     node->getName());
-            node->destroyHardwareResources(observer);
-            node->decStrong(observer);
+            node->destroyLayers();
+            node->decStrong(nullptr);
         }
         mPrefetchedLayers.clear();
     }
 }
 
-void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) {
+void CanvasContext::buildLayer(RenderNode* node) {
     ATRACE_CALL();
     if (!mRenderPipeline->isContextReady()) return;
 
@@ -525,7 +542,6 @@
 
     TreeInfo info(TreeInfo::MODE_FULL, *this);
     info.damageAccumulator = &mDamageAccumulator;
-    info.observer = observer;
     info.layerUpdateQueue = &mLayerUpdateQueue;
     info.runAnimations = false;
     node->prepareTree(info);
@@ -545,12 +561,12 @@
     return mRenderPipeline->copyLayerInto(layer, bitmap);
 }
 
-void CanvasContext::destroyHardwareResources(TreeObserver* observer) {
+void CanvasContext::destroyHardwareResources() {
     stopDrawing();
     if (mRenderPipeline->isContextReady()) {
-        freePrefetchedLayers(observer);
+        freePrefetchedLayers();
         for (const sp<RenderNode>& node : mRenderNodes) {
-            node->destroyHardwareResources(observer);
+            node->destroyHardwareResources();
         }
         mRenderPipeline->onDestroyHardwareResources();
     }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 0174b86..aa01caa 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -132,17 +132,17 @@
     void prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
             int64_t syncQueued, RenderNode* target);
     void draw();
-    void destroy(TreeObserver* observer);
+    void destroy();
 
     // IFrameCallback, Choreographer-driven frame callback entry point
     virtual void doFrame() override;
     void prepareAndDraw(RenderNode* node);
 
-    void buildLayer(RenderNode* node, TreeObserver* observer);
+    void buildLayer(RenderNode* node);
     bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
     void markLayerInUse(RenderNode* node);
 
-    void destroyHardwareResources(TreeObserver* observer);
+    void destroyHardwareResources();
     static void trimMemory(RenderThread& thread, int level);
 
     DeferredLayerUpdater* createTextureLayer();
@@ -160,15 +160,8 @@
 
     void serializeDisplayListTree();
 
-    void addRenderNode(RenderNode* node, bool placeFront) {
-        int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size());
-        mRenderNodes.emplace(mRenderNodes.begin() + pos, node);
-    }
-
-    void removeRenderNode(RenderNode* node) {
-        mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node),
-                mRenderNodes.end());
-    }
+    void addRenderNode(RenderNode* node, bool placeFront);
+    void removeRenderNode(RenderNode* node);
 
     void setContentDrawBounds(int left, int top, int right, int bottom) {
         mContentDrawBounds.set(left, top, right, bottom);
@@ -213,7 +206,7 @@
 
     void setSurface(Surface* window);
 
-    void freePrefetchedLayers(TreeObserver* observer);
+    void freePrefetchedLayers();
 
     bool isSwapChainStuffed();
 
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 4ff54a5..7d641d3 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -65,12 +65,11 @@
     }
 }
 
-int DrawFrameTask::drawFrame(TreeObserver* observer) {
+int DrawFrameTask::drawFrame() {
     LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
 
     mSyncResult = SyncResult::OK;
     mSyncQueued = systemTime(CLOCK_MONOTONIC);
-    mObserver = observer;
     postAndWait();
 
     return mSyncResult;
@@ -89,7 +88,6 @@
     bool canDrawThisFrame;
     {
         TreeInfo info(TreeInfo::MODE_FULL, *mContext);
-        info.observer = mObserver;
         canUnblockUiThread = syncFrameState(info);
         canDrawThisFrame = info.out.canDrawThisFrame;
     }
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index c02d376..fb48062 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -65,7 +65,7 @@
     void pushLayerUpdate(DeferredLayerUpdater* layer);
     void removeLayerUpdate(DeferredLayerUpdater* layer);
 
-    int drawFrame(TreeObserver* observer);
+    int drawFrame();
 
     int64_t* frameInfo() { return mFrameInfo; }
 
@@ -90,7 +90,6 @@
 
     int mSyncResult;
     int64_t mSyncQueued;
-    TreeObserver* mObserver;
 
     int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
 };
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 022e871..fb79272 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -230,19 +230,18 @@
     return mDrawFrameTask.frameInfo();
 }
 
-int RenderProxy::syncAndDrawFrame(TreeObserver* observer) {
-    return mDrawFrameTask.drawFrame(observer);
+int RenderProxy::syncAndDrawFrame() {
+    return mDrawFrameTask.drawFrame();
 }
 
-CREATE_BRIDGE2(destroy, CanvasContext* context, TreeObserver* observer) {
-    args->context->destroy(args->observer);
+CREATE_BRIDGE1(destroy, CanvasContext* context) {
+    args->context->destroy();
     return nullptr;
 }
 
-void RenderProxy::destroy(TreeObserver* observer) {
+void RenderProxy::destroy() {
     SETUP_TASK(destroy);
     args->context = mContext;
-    args->observer = observer;
     // destroyCanvasAndSurface() needs a fence as when it returns the
     // underlying BufferQueue is going to be released from under
     // the render thread.
@@ -282,16 +281,15 @@
     return layer;
 }
 
-CREATE_BRIDGE3(buildLayer, CanvasContext* context, RenderNode* node, TreeObserver* observer) {
-    args->context->buildLayer(args->node, args->observer);
+CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) {
+    args->context->buildLayer(args->node);
     return nullptr;
 }
 
-void RenderProxy::buildLayer(RenderNode* node, TreeObserver* observer) {
+void RenderProxy::buildLayer(RenderNode* node) {
     SETUP_TASK(buildLayer);
     args->context = mContext;
     args->node = node;
-    args->observer = observer;
     postAndWait(task);
 }
 
@@ -328,15 +326,14 @@
     postAndWait(task);
 }
 
-CREATE_BRIDGE2(destroyHardwareResources, CanvasContext* context, TreeObserver* observer) {
-    args->context->destroyHardwareResources(args->observer);
+CREATE_BRIDGE1(destroyHardwareResources, CanvasContext* context) {
+    args->context->destroyHardwareResources();
     return nullptr;
 }
 
-void RenderProxy::destroyHardwareResources(TreeObserver* observer) {
+void RenderProxy::destroyHardwareResources() {
     SETUP_TASK(destroyHardwareResources);
     args->context = mContext;
-    args->observer = observer;
     postAndWait(task);
 }
 
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 44a5a14..1629090 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -44,7 +44,6 @@
 class DisplayList;
 class Layer;
 class Rect;
-class TreeObserver;
 
 namespace renderthread {
 
@@ -87,19 +86,19 @@
     ANDROID_API void setLightCenter(const Vector3& lightCenter);
     ANDROID_API void setOpaque(bool opaque);
     ANDROID_API int64_t* frameInfo();
-    ANDROID_API int syncAndDrawFrame(TreeObserver* observer);
-    ANDROID_API void destroy(TreeObserver* observer);
+    ANDROID_API int syncAndDrawFrame();
+    ANDROID_API void destroy();
 
     ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
 
     ANDROID_API DeferredLayerUpdater* createTextureLayer();
-    ANDROID_API void buildLayer(RenderNode* node, TreeObserver* observer);
+    ANDROID_API void buildLayer(RenderNode* node);
     ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap);
     ANDROID_API void pushLayerUpdate(DeferredLayerUpdater* layer);
     ANDROID_API void cancelLayerUpdate(DeferredLayerUpdater* layer);
     ANDROID_API void detachSurfaceTexture(DeferredLayerUpdater* layer);
 
-    ANDROID_API void destroyHardwareResources(TreeObserver* observer);
+    ANDROID_API void destroyHardwareResources();
     ANDROID_API static void trimMemory(int level);
     ANDROID_API static void overrideProperty(const char* name, const char* value);
 
diff --git a/libs/hwui/tests/common/LeakChecker.cpp b/libs/hwui/tests/common/LeakChecker.cpp
index 8a0b64b..d935382 100644
--- a/libs/hwui/tests/common/LeakChecker.cpp
+++ b/libs/hwui/tests/common/LeakChecker.cpp
@@ -67,12 +67,6 @@
 }
 
 void LeakChecker::checkForLeaks() {
-    // TODO: Re-enable, disabled to workaround b/34586922
-    if ((true)) {
-        cout << "checkForLeaks disabled, see b/34586922" << endl;
-        return;
-    }
-
     // TODO: Until we can shutdown the RT thread we need to do this in
     // two passes as GetUnreachableMemory has limited insight into
     // thread-local caches so some leaks will not be properly tagged as leaks
diff --git a/libs/hwui/tests/common/TestListViewSceneBase.cpp b/libs/hwui/tests/common/TestListViewSceneBase.cpp
index 6d2e8599..38c4848 100644
--- a/libs/hwui/tests/common/TestListViewSceneBase.cpp
+++ b/libs/hwui/tests/common/TestListViewSceneBase.cpp
@@ -69,7 +69,7 @@
         // draw it to parent DisplayList
         canvas->drawRenderNode(mListItems[ci].get());
     }
-    mListView->setStagingDisplayList(canvas->finishRecording(), nullptr);
+    mListView->setStagingDisplayList(canvas->finishRecording());
 }
 
 } // namespace test
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 79daa3f..0916d72 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -193,9 +193,7 @@
 }
 
 SkRect TestUtils::getClipBounds(const SkCanvas* canvas) {
-    SkIRect bounds;
-    (void)canvas->getClipDeviceBounds(&bounds);
-    return SkRect::Make(bounds);
+    return SkRect::Make(canvas->getDeviceClipBounds());
 }
 
 SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 8b287de..16bc6f7 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -156,6 +156,11 @@
         int* mSignal;
     };
 
+    class MockTreeObserver : public TreeObserver {
+    public:
+        virtual void onMaybeRemovedFromTree(RenderNode* node) {}
+    };
+
     static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {
         for (int i = 0; i < 16; i++) {
             if (!MathUtils::areEqual(a[i], b[i])) {
@@ -215,7 +220,7 @@
             std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(props.getWidth(),
                     props.getHeight()));
             setup(props, *canvas.get());
-            node->setStagingDisplayList(canvas->finishRecording(), nullptr);
+            node->setStagingDisplayList(canvas->finishRecording());
         }
         node->setPropertyFieldsDirty(0xFFFFFFFF);
         return node;
@@ -236,7 +241,7 @@
         if (setup) {
             RecordingCanvasType canvas(props.getWidth(), props.getHeight());
             setup(props, canvas);
-            node->setStagingDisplayList(canvas.finishRecording(), nullptr);
+            node->setStagingDisplayList(canvas.finishRecording());
         }
         node->setPropertyFieldsDirty(0xFFFFFFFF);
         return node;
@@ -247,7 +252,7 @@
        std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(
             node.stagingProperties().getWidth(), node.stagingProperties().getHeight()));
        contentCallback(*canvas.get());
-       node.setStagingDisplayList(canvas->finishRecording(), nullptr);
+       node.setStagingDisplayList(canvas->finishRecording());
     }
 
     static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom,
@@ -265,14 +270,14 @@
         RenderProperties& props = node->mutateStagingProperties();
         props.setLeftTopRightBottom(left, top, right, bottom);
         if (displayList) {
-            node->setStagingDisplayList(displayList, nullptr);
+            node->setStagingDisplayList(displayList);
         }
         if (setup) {
             std::unique_ptr<skiapipeline::SkiaRecordingCanvas> canvas(
                 new skiapipeline::SkiaRecordingCanvas(nullptr,
                 props.getWidth(), props.getHeight()));
             setup(props, *canvas.get());
-            node->setStagingDisplayList(canvas->finishRecording(), nullptr);
+            node->setStagingDisplayList(canvas->finishRecording());
         }
         node->setPropertyFieldsDirty(0xFFFFFFFF);
         TestUtils::syncHierarchyPropertiesAndDisplayList(node);
@@ -350,8 +355,9 @@
 
 private:
     static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
+        MarkAndSweepRemoved observer(nullptr);
         node->syncProperties();
-        node->syncDisplayList(nullptr);
+        node->syncDisplayList(observer, nullptr);
         auto displayList = node->getDisplayList();
         if (displayList) {
             for (auto&& childOp : displayList->getChildren()) {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index c0d9450..5b685bb 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -60,6 +60,6 @@
                     0, 100 * (i + 2), minikin::kBidi_Force_LTR, paint, nullptr);
         }
 
-        container->setStagingDisplayList(canvas->finishRecording(), nullptr);
+        container->setStagingDisplayList(canvas->finishRecording());
     }
 };
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 396e896..f8d6397 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -151,7 +151,7 @@
         testContext.waitForVsync();
         nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
         UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
-        proxy->syncAndDrawFrame(nullptr);
+        proxy->syncAndDrawFrame();
     }
 
     proxy->resetProfileInfo();
@@ -167,7 +167,7 @@
             ATRACE_NAME("UI-Draw Frame");
             UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
             scene->doFrame(i);
-            proxy->syncAndDrawFrame(nullptr);
+            proxy->syncAndDrawFrame();
         }
         if (opts.reportFrametimeWeight) {
             proxy->fence();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 42ba3db..ef5ce0d 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -40,7 +40,7 @@
 
     ASSERT_FALSE(canvasContext->hasSurface());
 
-    canvasContext->destroy(nullptr);
+    canvasContext->destroy();
 }
 
 RENDERTHREAD_TEST(CanvasContext, invokeFunctor) {
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 5a2791c..a391d1e 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -322,10 +322,14 @@
     }
 
     for (auto& node : nodes) {
+        EXPECT_TRUE(node->isValid());
         EXPECT_FALSE(node->nothingToDraw());
-        node->setStagingDisplayList(nullptr, nullptr);
-        node->destroyHardwareResources(nullptr);
+        node->setStagingDisplayList(nullptr);
+        EXPECT_FALSE(node->isValid());
+        EXPECT_FALSE(node->nothingToDraw());
+        node->destroyHardwareResources();
         EXPECT_TRUE(node->nothingToDraw());
+        EXPECT_FALSE(node->isValid());
     }
 
     {
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 2f1eae3..f21b3f7 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -321,7 +321,6 @@
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
-    info.observer = nullptr;
     parent->prepareTree(info);
 
     //parent(A)             -> (receiverBackground, child)
@@ -437,7 +436,6 @@
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
-    info.observer = nullptr;
     parent->prepareTree(info);
 
     int drawCounter = 0;
@@ -527,7 +525,6 @@
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
-    info.observer = nullptr;
     parent->prepareTree(info);
 
     std::unique_ptr<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
@@ -545,7 +542,6 @@
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
-    info.observer = nullptr;
     renderNode->prepareTree(info);
 
     //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index ab8e4e1..8af4bbe 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -66,6 +66,70 @@
     EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents";
 }
 
+TEST(RenderNode, validity) {
+    auto child = TestUtils::createNode(0, 0, 200, 400,
+            [](RenderProperties& props, Canvas& canvas) {
+        canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
+    });
+    auto parent = TestUtils::createNode(0, 0, 200, 400,
+            [&child](RenderProperties& props, Canvas& canvas) {
+        canvas.drawRenderNode(child.get());
+    });
+
+    EXPECT_TRUE(child->isValid());
+    EXPECT_TRUE(parent->isValid());
+    EXPECT_TRUE(child->nothingToDraw());
+    EXPECT_TRUE(parent->nothingToDraw());
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+    EXPECT_TRUE(child->isValid());
+    EXPECT_TRUE(parent->isValid());
+    EXPECT_FALSE(child->nothingToDraw());
+    EXPECT_FALSE(parent->nothingToDraw());
+
+    TestUtils::recordNode(*parent, [](Canvas& canvas) {
+        canvas.drawColor(Color::Amber_500, SkBlendMode::kSrcOver);
+    });
+
+    EXPECT_TRUE(child->isValid());
+    EXPECT_TRUE(parent->isValid());
+    EXPECT_FALSE(child->nothingToDraw());
+    EXPECT_FALSE(parent->nothingToDraw());
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+    EXPECT_FALSE(child->isValid());
+    EXPECT_TRUE(parent->isValid());
+    EXPECT_TRUE(child->nothingToDraw());
+    EXPECT_FALSE(parent->nothingToDraw());
+
+    TestUtils::recordNode(*child, [](Canvas& canvas) {
+        canvas.drawColor(Color::Amber_500, SkBlendMode::kSrcOver);
+    });
+
+    EXPECT_TRUE(child->isValid());
+    EXPECT_TRUE(child->nothingToDraw());
+
+    TestUtils::recordNode(*parent, [&child](Canvas& canvas) {
+        canvas.drawRenderNode(child.get());
+    });
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+    EXPECT_TRUE(child->isValid());
+    EXPECT_TRUE(parent->isValid());
+    EXPECT_FALSE(child->nothingToDraw());
+    EXPECT_FALSE(parent->nothingToDraw());
+
+    parent->destroyHardwareResources();
+
+    EXPECT_FALSE(child->isValid());
+    EXPECT_FALSE(parent->isValid());
+    EXPECT_TRUE(child->nothingToDraw());
+    EXPECT_TRUE(parent->nothingToDraw());
+}
+
 TEST(RenderNode, releasedCallback) {
     class DecRefOnReleased : public GlFunctorLifecycleListener {
     public:
@@ -112,7 +176,6 @@
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
-    info.observer = nullptr;
 
     {
         auto nonNullDLNode = TestUtils::createNode(0, 0, 200, 400,
@@ -131,7 +194,7 @@
         nullDLNode->prepareTree(info);
     }
 
-    canvasContext->destroy(nullptr);
+    canvasContext->destroy();
 }
 
 RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) {
@@ -151,7 +214,6 @@
     LayerUpdateQueue layerUpdateQueue;
     info.damageAccumulator = &damageAccumulator;
     info.layerUpdateQueue = &layerUpdateQueue;
-    info.observer = nullptr;
 
     // Put node on HW layer
     rootNode->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
@@ -165,5 +227,5 @@
     EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
     EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode);
     EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
-    canvasContext->destroy(nullptr);
+    canvasContext->destroy();
 }
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 8f6fc8b..be460bf 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -126,7 +126,6 @@
     TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
     DamageAccumulator damageAccumulator;
     info.damageAccumulator = &damageAccumulator;
-    info.observer = nullptr;
 
     SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200));
 
@@ -137,7 +136,9 @@
 
     ASSERT_FALSE(cleanVD.isDirty());
     ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed());
-    ASSERT_FALSE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {}));
+    TestUtils::MockTreeObserver observer;
+    ASSERT_FALSE(skiaDL.prepareListAndChildren(observer, info, false,
+            [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
     ASSERT_TRUE(cleanVD.getPropertyChangeWillBeConsumed());
 
     // prepare again this time adding a dirty VD
@@ -146,7 +147,8 @@
 
     ASSERT_TRUE(dirtyVD.isDirty());
     ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed());
-    ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {}));
+    ASSERT_TRUE(skiaDL.prepareListAndChildren(observer, info, false,
+            [](RenderNode*, TreeObserver&, TreeInfo&, bool) {}));
     ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed());
 
     // prepare again this time adding a RenderNode and a callback
@@ -155,8 +157,8 @@
     SkCanvas dummyCanvas;
     skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas);
     bool hasRun = false;
-    ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false,
-            [&hasRun, renderNode, infoPtr](RenderNode* n, TreeInfo& i, bool r) {
+    ASSERT_TRUE(skiaDL.prepareListAndChildren(observer, info, false,
+            [&hasRun, renderNode, infoPtr](RenderNode* n, TreeObserver& observer, TreeInfo& i, bool r) {
         hasRun = true;
         ASSERT_EQ(renderNode.get(), n);
         ASSERT_EQ(infoPtr, &i);
@@ -164,7 +166,7 @@
     }));
     ASSERT_TRUE(hasRun);
 
-    canvasContext->destroy(nullptr);
+    canvasContext->destroy();
 }
 
 TEST(SkiaDisplayList, updateChildren) {
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 0b8c2a9..5bb0b6d 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -170,9 +170,7 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
 
     // 1 Overdraw, should be blue blended onto white.
-    renderNodes.push_back(whiteNode); //this is the "content" node
-    renderNodes.push_back(whiteNode); //the "content" node above does not cause an overdraw, because
-    //it clips the first "background" node
+    renderNodes.push_back(whiteNode);
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned) 0xffd0d0ff);
 
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
index 8b80d69..79fc864 100644
--- a/libs/hwui/utils/TestWindowContext.cpp
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -98,8 +98,8 @@
     }
 
     void finishDrawing() {
-        mRootNode->setStagingDisplayList(mCanvas->finishRecording(), nullptr);
-        mProxy->syncAndDrawFrame(nullptr);
+        mRootNode->setStagingDisplayList(mCanvas->finishRecording());
+        mProxy->syncAndDrawFrame();
         // Surprisingly, calling mProxy->fence() here appears to make no difference to
         // the timings we record.
     }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index dc8264a..391a905 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -306,12 +306,26 @@
      * until there are no glitches.
      * This tuning step should be done while playing silence.
      * This technique provides a compromise between latency and glitch rate.
+     *
+     * @deprecated Use {@link AudioTrack.Builder#setPerformanceMode(int)} with
+     * {@link AudioTrack#PERFORMANCE_MODE_LOW_LATENCY} to control performance.
      */
     public final static int FLAG_LOW_LATENCY = 0x1 << 8;
 
+    /**
+     * @hide
+     * Flag requesting a deep buffer path when creating an {@code AudioTrack}.
+     *
+     * A deep buffer path, if available, may consume less power and is
+     * suitable for media playback where latency is not a concern.
+     * Use {@link AudioTrack.Builder#setPerformanceMode(int)} with
+     * {@link AudioTrack#PERFORMANCE_MODE_POWER_SAVING} to enable.
+     */
+    public final static int FLAG_DEEP_BUFFER = 0x1 << 9;
+
     private final static int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO |
             FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY |
-            FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY;
+            FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER;
     private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
             FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
 
@@ -541,6 +555,8 @@
 
         /**
          * Sets the combination of flags.
+         *
+         * This is a bitwise OR with the existing flags.
          * @param flags a combination of {@link AudioAttributes#FLAG_AUDIBILITY_ENFORCED},
          *    {@link AudioAttributes#FLAG_HW_AV_SYNC}.
          * @return the same Builder instance.
@@ -553,6 +569,17 @@
 
         /**
          * @hide
+         * Replaces flags.
+         * @param flags any combination of {@link AudioAttributes#FLAG_ALL}.
+         * @return the same Builder instance.
+         */
+        public Builder replaceFlags(int flags) {
+            mFlags = flags & AudioAttributes.FLAG_ALL;
+            return this;
+        }
+
+        /**
+         * @hide
          * Adds a Bundle of data
          * @param bundle a non-null Bundle
          * @return the same builder instance
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7c60385..bcae71c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -730,61 +730,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void handleKeyDown(KeyEvent event, int stream) {
-        int keyCode = event.getKeyCode();
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_VOLUME_UP:
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
-                /*
-                 * Adjust the volume in on key down since it is more
-                 * responsive to the user.
-                 */
-                adjustSuggestedStreamVolume(
-                        keyCode == KeyEvent.KEYCODE_VOLUME_UP
-                                ? ADJUST_RAISE
-                                : ADJUST_LOWER,
-                        stream,
-                        FLAG_SHOW_UI | FLAG_VIBRATE);
-                break;
-            case KeyEvent.KEYCODE_VOLUME_MUTE:
-                if (event.getRepeatCount() == 0) {
-                    MediaSessionLegacyHelper.getHelper(getContext())
-                            .sendVolumeKeyEvent(event, false);
-                }
-                break;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public void handleKeyUp(KeyEvent event, int stream) {
-        int keyCode = event.getKeyCode();
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_VOLUME_UP:
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
-                /*
-                 * Play a sound. This is done on key up since we don't want the
-                 * sound to play when a user holds down volume down to mute.
-                 */
-                if (mUseVolumeKeySounds) {
-                    adjustSuggestedStreamVolume(
-                            ADJUST_SAME,
-                            stream,
-                            FLAG_PLAY_SOUND);
-                }
-                mVolumeKeyUpTime = SystemClock.uptimeMillis();
-                break;
-            case KeyEvent.KEYCODE_VOLUME_MUTE:
-                MediaSessionLegacyHelper.getHelper(getContext())
-                        .sendVolumeKeyEvent(event, false);
-                break;
-        }
-    }
-
-    /**
      * Indicates if the device implements a fixed volume policy.
      * <p>Some devices may not have volume control and may operate at a fixed volume,
      * and may not enable muting or changing the volume of audio streams.
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 031ac06..b23f5fd 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -214,6 +214,66 @@
      */
     public final static int WRITE_NON_BLOCKING = 1;
 
+    /** @hide */
+    @IntDef({
+        PERFORMANCE_MODE_NONE,
+        PERFORMANCE_MODE_LOW_LATENCY,
+        PERFORMANCE_MODE_POWER_SAVING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PerformanceMode {}
+
+    /**
+     * Default performance mode for an {@link AudioTrack}.
+     */
+    public static final int PERFORMANCE_MODE_NONE = 0;
+
+    /**
+     * Low latency performance mode for an {@link AudioTrack}.
+     * If the device supports it, this mode
+     * enables a lower latency path through to the audio output sink.
+     * Effects may no longer work with such an {@code AudioTrack} and
+     * the sample rate must match that of the output sink.
+     * <p>
+     * Applications should be aware that low latency requires careful
+     * buffer management, with smaller chunks of audio data written by each
+     * {@code write()} call.
+     * <p>
+     * If this flag is used without specifying a {@code bufferSizeInBytes} then the
+     * {@code AudioTrack}'s actual buffer size may be too small.
+     * It is recommended that a fairly
+     * large buffer should be specified when the {@code AudioTrack} is created.
+     * Then the actual size can be reduced by calling
+     * {@link #setBufferSizeInFrames(int)}. The buffer size can be optimized
+     * by lowering it after each {@code write()} call until the audio glitches,
+     * which is detected by calling
+     * {@link #getUnderrunCount()}. Then the buffer size can be increased
+     * until there are no glitches.
+     * This tuning step should be done while playing silence.
+     * This technique provides a compromise between latency and glitch rate.
+     */
+    public static final int PERFORMANCE_MODE_LOW_LATENCY = 1;
+
+    /**
+     * Power saving performance mode for an {@link AudioTrack}.
+     * If the device supports it, this
+     * mode will enable a lower power path to the audio output sink.
+     * In addition, this lower power path typically will have
+     * deeper internal buffers and better underrun resistance,
+     * with a tradeoff of higher latency.
+     * <p>
+     * In this mode, applications should attempt to use a larger buffer size
+     * and deliver larger chunks of audio data per {@code write()} call.
+     * Use {@link #getBufferSizeInFrames()} to determine
+     * the actual buffer size of the {@code AudioTrack} as it may have increased
+     * to accommodate a deeper buffer.
+     */
+    public static final int PERFORMANCE_MODE_POWER_SAVING = 2;
+
+    // keep in sync with system/media/audio/include/system/audio-base.h
+    private static final int AUDIO_OUTPUT_FLAG_FAST = 0x4;
+    private static final int AUDIO_OUTPUT_FLAG_DEEP_BUFFER = 0x8;
+
     //--------------------------------------------------------------------------
     // Member variables
     //--------------------
@@ -648,6 +708,7 @@
         private int mBufferSizeInBytes;
         private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
         private int mMode = MODE_STREAM;
+        private int mPerformanceMode = PERFORMANCE_MODE_NONE;
 
         /**
          * Constructs a new Builder with the default values as described above.
@@ -752,6 +813,32 @@
         }
 
         /**
+         * Sets the {@link AudioTrack} performance mode.  This is an advisory request which
+         * may not be supported by the particular device, and the framework is free
+         * to ignore such request if it is incompatible with other requests or hardware.
+         *
+         * @param performanceMode one of
+         * {@link AudioTrack#PERFORMANCE_MODE_NONE},
+         * {@link AudioTrack#PERFORMANCE_MODE_LOW_LATENCY},
+         * or {@link AudioTrack#PERFORMANCE_MODE_POWER_SAVING}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException if {@code performanceMode} is not valid.
+         */
+        public @NonNull Builder setPerformanceMode(@PerformanceMode int performanceMode) {
+            switch (performanceMode) {
+                case PERFORMANCE_MODE_NONE:
+                case PERFORMANCE_MODE_LOW_LATENCY:
+                case PERFORMANCE_MODE_POWER_SAVING:
+                    mPerformanceMode = performanceMode;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid performance mode " + performanceMode);
+            }
+            return this;
+        }
+
+        /**
          * Builds an {@link AudioTrack} instance initialized with all the parameters set
          * on this <code>Builder</code>.
          * @return a new successfully initialized {@link AudioTrack} instance.
@@ -765,6 +852,25 @@
                         .setUsage(AudioAttributes.USAGE_MEDIA)
                         .build();
             }
+            switch (mPerformanceMode) {
+            case PERFORMANCE_MODE_LOW_LATENCY:
+                mAttributes = new AudioAttributes.Builder(mAttributes)
+                    .replaceFlags((mAttributes.getAllFlags()
+                            | AudioAttributes.FLAG_LOW_LATENCY)
+                            & ~AudioAttributes.FLAG_DEEP_BUFFER)
+                    .build();
+                break;
+            case PERFORMANCE_MODE_NONE:
+                break;
+            case PERFORMANCE_MODE_POWER_SAVING:
+                mAttributes = new AudioAttributes.Builder(mAttributes)
+                .replaceFlags((mAttributes.getAllFlags()
+                        | AudioAttributes.FLAG_DEEP_BUFFER)
+                        & ~AudioAttributes.FLAG_LOW_LATENCY)
+                .build();
+                break;
+            }
+
             if (mFormat == null) {
                 mFormat = new AudioFormat.Builder()
                         .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
@@ -1278,6 +1384,27 @@
     }
 
     /**
+     * Returns the current performance mode of the {@link AudioTrack}.
+     *
+     * @return one of {@link AudioTrack#PERFORMANCE_MODE_NONE},
+     * {@link AudioTrack#PERFORMANCE_MODE_LOW_LATENCY},
+     * or {@link AudioTrack#PERFORMANCE_MODE_POWER_SAVING}.
+     * Use {@link AudioTrack.Builder#setPerformanceMode}
+     * in the {@link AudioTrack.Builder} to enable a performance mode.
+     * @throws IllegalStateException if track is not initialized.
+     */
+    public @PerformanceMode int getPerformanceMode() {
+        final int flags = native_get_flags();
+        if ((flags & AUDIO_OUTPUT_FLAG_FAST) != 0) {
+            return PERFORMANCE_MODE_LOW_LATENCY;
+        } else if ((flags & AUDIO_OUTPUT_FLAG_DEEP_BUFFER) != 0) {
+            return PERFORMANCE_MODE_POWER_SAVING;
+        } else {
+            return PERFORMANCE_MODE_NONE;
+        }
+    }
+
+    /**
      *  Returns the output sample rate in Hz for the specified stream type.
      */
     static public int getNativeOutputSampleRate(int streamType) {
@@ -2855,6 +2982,8 @@
 
     private native final int native_get_underrun_count();
 
+    private native final int native_get_flags();
+
     // longArray must be a non-null array of length >= 2
     // [0] is assigned the frame position
     // [1] is assigned the time in CLOCK_MONOTONIC nanoseconds
diff --git a/media/java/android/media/BufferingParams.aidl b/media/java/android/media/BufferingParams.aidl
new file mode 100644
index 0000000..d156d44
--- /dev/null
+++ b/media/java/android/media/BufferingParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.media;
+
+parcelable BufferingParams;
diff --git a/media/java/android/media/BufferingParams.java b/media/java/android/media/BufferingParams.java
new file mode 100644
index 0000000..fdcd6ba
--- /dev/null
+++ b/media/java/android/media/BufferingParams.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 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 android.media;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Structure for source buffering management params.
+ *
+ * Used by {@link MediaPlayer#getDefaultBufferingParams()},
+ * {@link MediaPlayer#getBufferingParams()} and
+ * {@link MediaPlayer#setBufferingParams(BufferingParams)}
+ * to control source buffering behavior.
+ *
+ * <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
+ * (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
+ * is playing back source). {@link BufferingParams} includes mode and corresponding
+ * watermarks for each stage of source buffering. The watermarks could be either size
+ * based (in milliseconds), or time based (in kilobytes) or both, depending on the mode.
+ *
+ * <p>There are 4 buffering modes: {@link #BUFFERING_MODE_NONE},
+ * {@link #BUFFERING_MODE_TIME_ONLY}, {@link #BUFFERING_MODE_SIZE_ONLY} and
+ * {@link #BUFFERING_MODE_TIME_THEN_SIZE}.
+ * {@link MediaPlayer} source component has default buffering modes which can be queried
+ * by calling {@link MediaPlayer#getDefaultBufferingParams()}.
+ * Users should always use those default modes or their downsized version when trying to
+ * change buffering params. For example, {@link #BUFFERING_MODE_TIME_THEN_SIZE} can be
+ * downsized to {@link #BUFFERING_MODE_NONE}, {@link #BUFFERING_MODE_TIME_ONLY} or
+ * {@link #BUFFERING_MODE_SIZE_ONLY}. But {@link #BUFFERING_MODE_TIME_ONLY} can not be
+ * downsized to {@link #BUFFERING_MODE_SIZE_ONLY}.
+ * <ul>
+ * <li><strong>initial buffering stage:</strong> has one watermark which is used when
+ * {@link MediaPlayer} is being prepared. When cached data amount exceeds this watermark,
+ * {@link MediaPlayer} is prepared.</li>
+ * <li><strong>rebuffering stage:</strong> has two watermarks, low and high, which are
+ * used when {@link MediaPlayer} is playing back content.
+ * <ul>
+ * <li> When cached data amount exceeds high watermark, {@link MediaPlayer} will pause
+ * buffering. Buffering will resume when cache runs below some limit which could be low
+ * watermark or some intermediate value decided by the source component.</li>
+ * <li> When cached data amount runs below low watermark, {@link MediaPlayer} will paused
+ * playback. Playback will resume when cached data amount exceeds high watermark
+ * or reaches end of stream.</li>
+ * </ul>
+ * </ul>
+ * <p>Users should use {@link Builder} to change {@link BufferingParams}.
+ */
+public final class BufferingParams implements Parcelable {
+    /**
+     * This mode indicates that source buffering is not supported.
+     */
+    public static final int BUFFERING_MODE_NONE = 0;
+    /**
+     * This mode indicates that only time based source buffering is supported. This means
+     * the watermark(s) are time based.
+     */
+    public static final int BUFFERING_MODE_TIME_ONLY = 1;
+    /**
+     * This mode indicates that only size based source buffering is supported. This means
+     * the watermark(s) are size based.
+     */
+    public static final int BUFFERING_MODE_SIZE_ONLY = 2;
+    /**
+     * This mode indicates that both time and size based source buffering are supported,
+     * and time based calculation precedes size based. Size based calculation will be used
+     * only when time information is not available from the source.
+     */
+    public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3;
+
+    /** @hide */
+    @IntDef(
+        value = {
+                BUFFERING_MODE_NONE,
+                BUFFERING_MODE_TIME_ONLY,
+                BUFFERING_MODE_SIZE_ONLY,
+                BUFFERING_MODE_TIME_THEN_SIZE,
+        }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BufferingMode {}
+
+    private static final int BUFFERING_NO_WATERMARK = -1;
+
+    // params
+    private int mInitialBufferingMode = BUFFERING_MODE_NONE;
+    private int mRebufferingMode = BUFFERING_MODE_NONE;
+
+    private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
+    private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+
+    private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
+    private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
+    private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
+    private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+
+    private BufferingParams() {
+    }
+
+    /**
+     * Return the initial buffering mode used when {@link MediaPlayer} is being prepared.
+     * @return one of the values that can be set in {@link Builder#setInitialBufferingMode(int)}
+     */
+    public int getInitialBufferingMode() {
+        return mInitialBufferingMode;
+    }
+
+    /**
+     * Return the rebuffering mode used when {@link MediaPlayer} is playing back source.
+     * @return one of the values that can be set in {@link Builder#setRebufferingMode(int)}
+     */
+    public int getRebufferingMode() {
+        return mRebufferingMode;
+    }
+
+    /**
+     * Return the time based initial buffering watermark in milliseconds.
+     * It is meaningful only when initial buffering mode obatined from
+     * {@link #getInitialBufferingMode()} is time based.
+     * @return time based initial buffering watermark in milliseconds
+     */
+    public int getInitialBufferingWatermarkMs() {
+        return mInitialWatermarkMs;
+    }
+
+    /**
+     * Return the size based initial buffering watermark in kilobytes.
+     * It is meaningful only when initial buffering mode obatined from
+     * {@link #getInitialBufferingMode()} is size based.
+     * @return size based initial buffering watermark in kilobytes
+     */
+    public int getInitialBufferingWatermarkKB() {
+        return mInitialWatermarkKB;
+    }
+
+    /**
+     * Return the time based low watermark in milliseconds for rebuffering.
+     * It is meaningful only when rebuffering mode obatined from
+     * {@link #getRebufferingMode()} is time based.
+     * @return time based low watermark for rebuffering in milliseconds
+     */
+    public int getRebufferingWatermarkLowMs() {
+        return mRebufferingWatermarkLowMs;
+    }
+
+    /**
+     * Return the time based high watermark in milliseconds for rebuffering.
+     * It is meaningful only when rebuffering mode obatined from
+     * {@link #getRebufferingMode()} is time based.
+     * @return time based high watermark for rebuffering in milliseconds
+     */
+    public int getRebufferingWatermarkHighMs() {
+        return mRebufferingWatermarkHighMs;
+    }
+
+    /**
+     * Return the size based low watermark in kilobytes for rebuffering.
+     * It is meaningful only when rebuffering mode obatined from
+     * {@link #getRebufferingMode()} is size based.
+     * @return size based low watermark for rebuffering in kilobytes
+     */
+    public int getRebufferingWatermarkLowKB() {
+        return mRebufferingWatermarkLowKB;
+    }
+
+    /**
+     * Return the size based high watermark in kilobytes for rebuffering.
+     * It is meaningful only when rebuffering mode obatined from
+     * {@link #getRebufferingMode()} is size based.
+     * @return size based high watermark for rebuffering in kilobytes
+     */
+    public int getRebufferingWatermarkHighKB() {
+        return mRebufferingWatermarkHighKB;
+    }
+
+    /**
+     * Builder class for {@link BufferingParams} objects.
+     * <p> Here is an example where <code>Builder</code> is used to define the
+     * {@link BufferingParams} to be used by a {@link MediaPlayer} instance:
+     *
+     * <pre class="prettyprint">
+     * BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
+     * myParams = new BufferingParams.Builder(myParams)
+     *             .setInitialBufferingWatermarkMs(10000)
+     *             .build();
+     * mediaplayer.setBufferingParams(myParams);
+     * </pre>
+     */
+    public static class Builder {
+        private int mInitialBufferingMode = BUFFERING_MODE_NONE;
+        private int mRebufferingMode = BUFFERING_MODE_NONE;
+
+        private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
+        private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+
+        private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
+        private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
+        private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
+        private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+
+        /**
+         * Constructs a new Builder with the defaults.
+         * By default, both initial buffering mode and rebuffering mode are
+         * {@link BufferingParams#BUFFERING_MODE_NONE}, and all watermarks are -1.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructs a new Builder from a given {@link BufferingParams} instance
+         * @param bp the {@link BufferingParams} object whose data will be reused
+         * in the new Builder.
+         */
+        public Builder(BufferingParams bp) {
+            mInitialBufferingMode = bp.mInitialBufferingMode;
+            mRebufferingMode = bp.mRebufferingMode;
+
+            mInitialWatermarkMs = bp.mInitialWatermarkMs;
+            mInitialWatermarkKB = bp.mInitialWatermarkKB;
+
+            mRebufferingWatermarkLowMs = bp.mRebufferingWatermarkLowMs;
+            mRebufferingWatermarkHighMs = bp.mRebufferingWatermarkHighMs;
+            mRebufferingWatermarkLowKB = bp.mRebufferingWatermarkLowKB;
+            mRebufferingWatermarkHighKB = bp.mRebufferingWatermarkHighKB;
+        }
+
+        /**
+         * Combines all of the fields that have been set and return a new
+         * {@link BufferingParams} object. <code>IllegalStateException</code> will be
+         * thrown if there is conflict between fields.
+         * @return a new {@link BufferingParams} object
+         */
+        public BufferingParams build() {
+            if (isTimeBasedMode(mRebufferingMode)
+                    && mRebufferingWatermarkLowMs > mRebufferingWatermarkHighMs) {
+                throw new IllegalStateException("Illegal watermark:"
+                        + mRebufferingWatermarkLowMs + " : " + mRebufferingWatermarkHighMs);
+            }
+            if (isSizeBasedMode(mRebufferingMode)
+                    && mRebufferingWatermarkLowKB > mRebufferingWatermarkHighKB) {
+                throw new IllegalStateException("Illegal watermark:"
+                        + mRebufferingWatermarkLowKB + " : " + mRebufferingWatermarkHighKB);
+            }
+
+            BufferingParams bp = new BufferingParams();
+            bp.mInitialBufferingMode = mInitialBufferingMode;
+            bp.mRebufferingMode = mRebufferingMode;
+
+            bp.mInitialWatermarkMs = mInitialWatermarkMs;
+            bp.mInitialWatermarkKB = mInitialWatermarkKB;
+
+            bp.mRebufferingWatermarkLowMs = mRebufferingWatermarkLowMs;
+            bp.mRebufferingWatermarkHighMs = mRebufferingWatermarkHighMs;
+            bp.mRebufferingWatermarkLowKB = mRebufferingWatermarkLowKB;
+            bp.mRebufferingWatermarkHighKB = mRebufferingWatermarkHighKB;
+            return bp;
+        }
+
+        private boolean isTimeBasedMode(int mode) {
+            return (mode == BUFFERING_MODE_TIME_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
+        }
+
+        private boolean isSizeBasedMode(int mode) {
+            return (mode == BUFFERING_MODE_SIZE_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
+        }
+
+        /**
+         * Sets the initial buffering mode.
+         * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
+         *     {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
+         *     {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
+         *     {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+         * @return the same Builder instance.
+         */
+        public Builder setInitialBufferingMode(@BufferingMode int mode) {
+            switch (mode) {
+                case BUFFERING_MODE_NONE:
+                case BUFFERING_MODE_TIME_ONLY:
+                case BUFFERING_MODE_SIZE_ONLY:
+                case BUFFERING_MODE_TIME_THEN_SIZE:
+                     mInitialBufferingMode = mode;
+                     break;
+                default:
+                     throw new IllegalArgumentException("Illegal buffering mode " + mode);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the rebuffering mode.
+         * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
+         *     {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
+         *     {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
+         *     {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+         * @return the same Builder instance.
+         */
+        public Builder setRebufferingMode(@BufferingMode int mode) {
+            switch (mode) {
+                case BUFFERING_MODE_NONE:
+                case BUFFERING_MODE_TIME_ONLY:
+                case BUFFERING_MODE_SIZE_ONLY:
+                case BUFFERING_MODE_TIME_THEN_SIZE:
+                     mRebufferingMode = mode;
+                     break;
+                default:
+                     throw new IllegalArgumentException("Illegal buffering mode " + mode);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the time based watermark in milliseconds for initial buffering.
+         * @param watermarkMs time based watermark in milliseconds
+         * @return the same Builder instance.
+         */
+        public Builder setInitialBufferingWatermarkMs(int watermarkMs) {
+            mInitialWatermarkMs = watermarkMs;
+            return this;
+        }
+
+        /**
+         * Sets the size based watermark in kilobytes for initial buffering.
+         * @param watermarkKB size based watermark in kilobytes
+         * @return the same Builder instance.
+         */
+        public Builder setInitialBufferingWatermarkKB(int watermarkKB) {
+            mInitialWatermarkKB = watermarkKB;
+            return this;
+        }
+
+        /**
+         * Sets the time based low watermark in milliseconds for rebuffering.
+         * @param watermarkMs time based low watermark in milliseconds
+         * @return the same Builder instance.
+         */
+        public Builder setRebufferingWatermarkLowMs(int watermarkMs) {
+            mRebufferingWatermarkLowMs = watermarkMs;
+            return this;
+        }
+
+        /**
+         * Sets the time based high watermark in milliseconds for rebuffering.
+         * @param watermarkMs time based high watermark in milliseconds
+         * @return the same Builder instance.
+         */
+        public Builder setRebufferingWatermarkHighMs(int watermarkMs) {
+            mRebufferingWatermarkHighMs = watermarkMs;
+            return this;
+        }
+
+        /**
+         * Sets the size based low watermark in milliseconds for rebuffering.
+         * @param watermarkKB size based low watermark in milliseconds
+         * @return the same Builder instance.
+         */
+        public Builder setRebufferingWatermarkLowKB(int watermarkKB) {
+            mRebufferingWatermarkLowKB = watermarkKB;
+            return this;
+        }
+
+        /**
+         * Sets the size based high watermark in milliseconds for rebuffering.
+         * @param watermarkKB size based high watermark in milliseconds
+         * @return the same Builder instance.
+         */
+        public Builder setRebufferingWatermarkHighKB(int watermarkKB) {
+            mRebufferingWatermarkHighKB = watermarkKB;
+            return this;
+        }
+
+        /**
+         * Sets the time based low and high watermarks in milliseconds for rebuffering.
+         * @param lowWatermarkMs time based low watermark in milliseconds
+         * @param highWatermarkMs time based high watermark in milliseconds
+         * @return the same Builder instance.
+         */
+        public Builder setRebufferingWatermarksMs(int lowWatermarkMs, int highWatermarkMs) {
+            mRebufferingWatermarkLowMs = lowWatermarkMs;
+            mRebufferingWatermarkHighMs = highWatermarkMs;
+            return this;
+        }
+
+        /**
+         * Sets the size based low and high watermarks in kilobytes for rebuffering.
+         * @param lowWatermarkKB size based low watermark in kilobytes
+         * @param highWatermarkKB size based high watermark in kilobytes
+         * @return the same Builder instance.
+         */
+        public Builder setRebufferingWatermarksKB(int lowWatermarkKB, int highWatermarkKB) {
+            mRebufferingWatermarkLowKB = lowWatermarkKB;
+            mRebufferingWatermarkHighKB = highWatermarkKB;
+            return this;
+        }
+    }
+
+    private BufferingParams(Parcel in) {
+        mInitialBufferingMode = in.readInt();
+        mRebufferingMode = in.readInt();
+
+        mInitialWatermarkMs = in.readInt();
+        mInitialWatermarkKB = in.readInt();
+
+        mRebufferingWatermarkLowMs = in.readInt();
+        mRebufferingWatermarkHighMs = in.readInt();
+        mRebufferingWatermarkLowKB = in.readInt();
+        mRebufferingWatermarkHighKB = in.readInt();
+    }
+
+    public static final Parcelable.Creator<BufferingParams> CREATOR =
+            new Parcelable.Creator<BufferingParams>() {
+                @Override
+                public BufferingParams createFromParcel(Parcel in) {
+                    return new BufferingParams(in);
+                }
+
+                @Override
+                public BufferingParams[] newArray(int size) {
+                    return new BufferingParams[size];
+                }
+            };
+
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mInitialBufferingMode);
+        dest.writeInt(mRebufferingMode);
+
+        dest.writeInt(mInitialWatermarkMs);
+        dest.writeInt(mInitialWatermarkKB);
+
+        dest.writeInt(mRebufferingWatermarkLowMs);
+        dest.writeInt(mRebufferingWatermarkHighMs);
+        dest.writeInt(mRebufferingWatermarkLowKB);
+        dest.writeInt(mRebufferingWatermarkHighKB);
+    }
+}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 432e77c..a76a328 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -192,5 +192,7 @@
 
     oneway void releasePlayer(in int piid);
 
+    void disableRingtoneSync();
+
     // WARNING: read warning at top of file, it is recommended to add new methods at the end
 }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index e3a0f25..4023400 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -45,6 +45,7 @@
 import android.widget.VideoView;
 import android.graphics.SurfaceTexture;
 import android.media.AudioManager;
+import android.media.BufferingParams;
 import android.media.MediaFormat;
 import android.media.MediaTimeProvider;
 import android.media.PlaybackParams;
@@ -479,6 +480,11 @@
  *     <td>{} </p></td>
  *     <td>This method can be called in any state and calling it does not change
  *         the object state. </p></td></tr>
+ * <tr><td>setBufferingParams</p></td>
+ *     <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}</p></td>
+ *     <td>{Idle} </p></td>
+ *     <td>This method does not change the object state.
+ *         </p></td></tr>
  * <tr><td>setPlaybackParams</p></td>
  *     <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td>
  *     <td>{Idle, Stopped} </p></td>
@@ -1390,6 +1396,45 @@
     public native boolean isPlaying();
 
     /**
+     * Gets the default buffering management params.
+     * Calling it only after {@code setDataSource} has been called.
+     * Each type of data source might have different set of default params.
+     *
+     * @return the default buffering management params supported by the source component.
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized, or {@code setDataSource} has not been called.
+     */
+    @NonNull
+    public native BufferingParams getDefaultBufferingParams();
+
+    /**
+     * Gets the current buffering management params used by the source component.
+     * Calling it only after {@code setDataSource} has been called.
+     *
+     * @return the current buffering management params used by the source component.
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized, or {@code setDataSource} has not been called.
+     */
+    @NonNull
+    public native BufferingParams getBufferingParams();
+
+    /**
+     * Sets buffering management params.
+     * The object sets its internal BufferingParams to the input, except that the input is
+     * invalid or not supported.
+     * Call it only after {@code setDataSource} has been called.
+     * Users should only use supported mode returned by {@link #getDefaultBufferingParams()}
+     * or its downsized version as described in {@link BufferingParams}.
+     *
+     * @param params the buffering management params.
+     *
+     * @throws IllegalStateException if the internal player engine has not been
+     * initialized or has been released, or {@code setDataSource} has not been called.
+     * @throws IllegalArgumentException if params is invalid or not supported.
+     */
+    public native void setBufferingParams(@NonNull BufferingParams params);
+
+    /**
      * Change playback speed of audio by resampling the audio.
      * <p>
      * Specifies resampling as audio mode for variable rate playback, i.e.,
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index e62dfaa..3e88450 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -803,15 +803,17 @@
     /**
      * Sets the next output file descriptor to be used when the maximum filesize is reached
      * on the prior output {@link #setOutputFile} or {@link #setNextOutputFile}). File descriptor
-     * must be seekable and in read-write mode. After setting the next output file, application
-     * should not use the file referenced by this file descriptor until {@link #stop}. Application
-     * must call this after receiving on the {@link android.media.MediaRecorder.OnInfoListener} a
-     * "what" code of {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING} and before receiving
-     * a "what" code of {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED}. The file is not used
-     * until switching to that output. Application will receive
-     * {@link #MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED} when the next output file is used.
-     * Application will not be able to set a new output file if the previous one has not been used.
-     * Application is responsible for cleaning up unused files after {@link #stop} is called.
+     * must be seekable and writable. After setting the next output file, application should not
+     * use the file referenced by this file descriptor until {@link #stop}. It is the application's
+     * responsibility to close the file descriptor. It is safe to do so as soon as this call returns.
+     * Application must call this after receiving on the
+     * {@link android.media.MediaRecorder.OnInfoListener} a "what" code of
+     * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING} and before receiving a "what" code of
+     * {@link #MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED}. The file is not used until switching to
+     * that output. Application will receive{@link #MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED}
+     * when the next output file is used. Application will not be able to set a new output file if
+     * the previous one has not been used. Application is responsible for cleaning up unused files
+     * after {@link #stop} is called.
      *
      * @param fd an open file descriptor to be written into.
      * @throws IllegalStateException if it is called before prepare().
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 7614999..8a1027b 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -33,8 +33,11 @@
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.Uri;
 import android.os.Environment;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.MediaStore;
@@ -850,6 +853,18 @@
     public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
         final ContentResolver resolver = context.getContentResolver();
 
+        if (Settings.Secure.getString(resolver, Settings.Secure.SYNC_PARENT_SOUNDS).equals("1")) {
+            // Sync is enabled, so we need to disable it
+            IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+            IAudioService audioService = IAudioService.Stub.asInterface(b);
+            try {
+                audioService.disableRingtoneSync();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to disable ringtone sync.");
+                return;
+            }
+        }
+
         String setting = getSettingForType(type);
         if (setting == null) return;
         if(!isInternalRingtoneUri(ringtoneUri)) {
diff --git a/media/java/android/media/session/IOnMediaKeyListener.aidl b/media/java/android/media/session/IOnMediaKeyListener.aidl
new file mode 100644
index 0000000..7752357
--- /dev/null
+++ b/media/java/android/media/session/IOnMediaKeyListener.aidl
@@ -0,0 +1,28 @@
+/* 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 android.media.session;
+
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+/**
+ * Listener to handle media key.
+ * @hide
+ */
+interface IOnMediaKeyListener {
+    void onMediaKey(in KeyEvent event, in ResultReceiver result);
+}
+
diff --git a/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl
new file mode 100644
index 0000000..07b8347
--- /dev/null
+++ b/media/java/android/media/session/IOnVolumeKeyLongPressListener.aidl
@@ -0,0 +1,27 @@
+/* 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 android.media.session;
+
+import android.view.KeyEvent;
+
+/**
+ * Listener to handle volume key long-press.
+ * @hide
+ */
+oneway interface IOnVolumeKeyLongPressListener {
+    void onVolumeKeyLongPress(in KeyEvent event);
+}
+
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index bb59e5b..8a98773 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,6 +18,8 @@
 import android.content.ComponentName;
 import android.media.IRemoteVolumeController;
 import android.media.session.IActiveSessionsListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
+import android.media.session.IOnMediaKeyListener;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.os.Bundle;
@@ -31,6 +33,7 @@
     ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
     List<IBinder> getSessions(in ComponentName compName, int userId);
     void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
+    void dispatchVolumeKeyEvent(in KeyEvent keyEvent, int stream, boolean musicOnly);
     void dispatchAdjustVolume(int suggestedStream, int delta, int flags);
     void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
             int userId);
@@ -41,4 +44,8 @@
 
     // For PhoneWindowManager to precheck media keys
     boolean isGlobalPriorityActive();
-}
\ No newline at end of file
+
+    void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
+    void setOnMediaKeyListener(in IOnMediaKeyListener listener);
+}
+
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 95cb8ae..7c3af31 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -176,54 +176,12 @@
         }
     }
 
-    public void sendVolumeKeyEvent(KeyEvent keyEvent, boolean musicOnly) {
+    public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
         if (keyEvent == null) {
             Log.w(TAG, "Tried to send a null key event. Ignoring.");
             return;
         }
-        boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
-        boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
-        int direction = 0;
-        boolean isMute = false;
-        switch (keyEvent.getKeyCode()) {
-            case KeyEvent.KEYCODE_VOLUME_UP:
-                direction = AudioManager.ADJUST_RAISE;
-                break;
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
-                direction = AudioManager.ADJUST_LOWER;
-                break;
-            case KeyEvent.KEYCODE_VOLUME_MUTE:
-                isMute = true;
-                break;
-        }
-        if (down || up) {
-            int flags = AudioManager.FLAG_FROM_KEY;
-            if (musicOnly) {
-                // This flag is used when the screen is off to only affect
-                // active media
-                flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
-            } else {
-                // These flags are consistent with the home screen
-                if (up) {
-                    flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
-                } else {
-                    flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
-                }
-            }
-            if (direction != 0) {
-                // If this is action up we want to send a beep for non-music events
-                if (up) {
-                    direction = 0;
-                }
-                mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
-                        direction, flags);
-            } else if (isMute) {
-                if (down && keyEvent.getRepeatCount() == 0) {
-                    mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
-                            AudioManager.ADJUST_TOGGLE_MUTE, flags);
-                }
-            }
-        }
+        mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
     }
 
     public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 2364a13..80d2a0c 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.media.AudioManager;
@@ -26,6 +27,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
@@ -50,6 +52,18 @@
 public final class MediaSessionManager {
     private static final String TAG = "SessionManager";
 
+    /**
+     * Used by IOnMediaKeyListener to indicate that the media key event isn't handled.
+     * @hide
+     */
+    public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0;
+
+    /**
+     * Used by IOnMediaKeyListener to indicate that the media key event is handled.
+     * @hide
+     */
+    public static final int RESULT_MEDIA_KEY_HANDLED = 1;
+
     private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
             = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
     private final Object mLock = new Object();
@@ -57,6 +71,9 @@
 
     private Context mContext;
 
+    private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
+    private OnMediaKeyListenerImpl mOnMediaKeyListener;
+
     /**
      * @hide
      */
@@ -278,6 +295,21 @@
     }
 
     /**
+     * Send a volume key event. The receiver will be selected automatically.
+     *
+     * @param keyEvent The volume KeyEvent to send.
+     * @param needWakeLock True if a wake lock should be held while sending the key.
+     * @hide
+     */
+    public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
+        try {
+            mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to send volume key event.", e);
+        }
+    }
+
+    /**
      * Dispatch an adjust volume request to the system. It will be sent to the
      * most relevant audio stream or media session. The direction must be one of
      * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
@@ -313,6 +345,72 @@
     }
 
     /**
+     * Set the volume key long-press listener. While the listener is set, the listener
+     * gets the volume key long-presses instead of changing volume.
+     *
+     * <p>System can only have a single volume key long-press listener.
+     *
+     * @param listener The volume key long-press listener. {@code null} to reset.
+     * @param handler The handler on which the listener should be invoked, or {@code null}
+     *            if the listener should be invoked on the calling thread's looper.
+     * @hide
+     */
+    @SystemApi
+    public void setOnVolumeKeyLongPressListener(
+            OnVolumeKeyLongPressListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            try {
+                if (listener == null) {
+                    mOnVolumeKeyLongPressListener = null;
+                    mService.setOnVolumeKeyLongPressListener(null);
+                } else {
+                    if (handler == null) {
+                        handler = new Handler();
+                    }
+                    mOnVolumeKeyLongPressListener =
+                            new OnVolumeKeyLongPressListenerImpl(listener, handler);
+                    mService.setOnVolumeKeyLongPressListener(mOnVolumeKeyLongPressListener);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to set volume key long press listener", e);
+            }
+        }
+    }
+
+    /**
+     * Set the media key listener. While the listener is set, the listener
+     * gets the media key before any other media sessions but after the global priority session.
+     * If the listener handles the key (i.e. returns {@code true}),
+     * other sessions will not get the event.
+     *
+     * <p>System can only have a single media key listener.
+     *
+     * @param listener The media key listener. {@code null} to reset.
+     * @param handler The handler on which the listener should be invoked, or {@code null}
+     *            if the listener should be invoked on the calling thread's looper.
+     * @hide
+     */
+    @SystemApi
+    public void setOnMediaKeyListener(OnMediaKeyListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            try {
+                if (listener == null) {
+                    mOnMediaKeyListener = null;
+                    mService.setOnMediaKeyListener(null);
+                } else {
+                    if (handler == null) {
+                        handler = new Handler();
+                    }
+                    mOnMediaKeyListener = new OnMediaKeyListenerImpl(listener, handler);
+                    mService.setOnMediaKeyListener(mOnMediaKeyListener);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to set media key listener", e);
+            }
+        }
+    }
+
+    /**
      * Listens for changes to the list of active sessions. This can be added
      * using {@link #addOnActiveSessionsChangedListener}.
      */
@@ -320,6 +418,33 @@
         public void onActiveSessionsChanged(@Nullable List<MediaController> controllers);
     }
 
+    /**
+     * Listens the volume key long-presses.
+     * @hide
+     */
+    @SystemApi
+    public interface OnVolumeKeyLongPressListener {
+        /**
+         * Called when the volume key is long-pressed.
+         * <p>This will be called for both down and up events.
+         */
+        void onVolumeKeyLongPress(KeyEvent event);
+    }
+
+    /**
+     * Listens the media key.
+     * @hide
+     */
+    @SystemApi
+    public interface OnMediaKeyListener {
+        /**
+         * Called when the media key is pressed.
+         * <p>If it takes more than 1s to return, the key event will be sent to
+         * other media sessions.
+         */
+        boolean onMediaKey(KeyEvent event);
+    }
+
     private static final class SessionsChangedWrapper {
         private Context mContext;
         private OnActiveSessionsChangedListener mListener;
@@ -360,4 +485,60 @@
             mHandler = null;
         }
     }
+
+    private static final class OnVolumeKeyLongPressListenerImpl
+            extends IOnVolumeKeyLongPressListener.Stub {
+        private OnVolumeKeyLongPressListener mListener;
+        private Handler mHandler;
+
+        public OnVolumeKeyLongPressListenerImpl(
+                OnVolumeKeyLongPressListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = handler;
+        }
+
+        @Override
+        public void onVolumeKeyLongPress(KeyEvent event) {
+            if (mListener == null || mHandler == null) {
+                Log.w(TAG, "Failed to call volume key long-press listener." +
+                        " Either mListener or mHandler is null");
+                return;
+            }
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onVolumeKeyLongPress(event);
+                }
+            });
+        }
+    }
+
+    private static final class OnMediaKeyListenerImpl extends IOnMediaKeyListener.Stub {
+        private OnMediaKeyListener mListener;
+        private Handler mHandler;
+
+        public OnMediaKeyListenerImpl(OnMediaKeyListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = handler;
+        }
+
+        @Override
+        public void onMediaKey(KeyEvent event, ResultReceiver result) {
+            if (mListener == null || mHandler == null) {
+                Log.w(TAG, "Failed to call media key listener." +
+                        " Either mListener or mHandler is null");
+                return;
+            }
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    boolean handled = mListener.onMediaKey(event);
+                    Log.d(TAG, "The media key listener is returned " + handled);
+                    result.send(
+                            handled ? RESULT_MEDIA_KEY_HANDLED : RESULT_MEDIA_KEY_NOT_HANDLED,
+                            null);
+                }
+            });
+        }
+    }
 }
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 20706fd..3ee80af 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -543,6 +543,17 @@
          */
         public static final String TYPE_S_DMB = "TYPE_S_DMB";
 
+        /**
+         * The channel type for preview videos.
+         *
+         * <P>Unlike other broadcast TV channel types, the programs in the preview channel usually
+         * are promotional videos. The UI may treat the preview channels differently from the other
+         * broadcast channels.
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_PREVIEW = "TYPE_PREVIEW";
+
         /** A generic service type. */
         public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER";
 
@@ -1001,6 +1012,20 @@
          */
         public static final String COLUMN_VERSION_NUMBER = "version_number";
 
+        /**
+         * The flag indicating whether this TV channel is transient or not.
+         *
+         * <p>A value of 1 indicates that the channel will be automatically removed by the system on
+         * reboot, and a value of 0 indicates that the channel is persistent across reboot. If not
+         * specified, this value is set to 0 (not transient) by default.
+         *
+         * <p>Type: INTEGER (boolean)
+         * @see Programs#COLUMN_TRANSIENT
+         * @hide
+         */
+        @SystemApi
+        public static final String COLUMN_TRANSIENT = "transient";
+
         private Channels() {}
 
         /**
@@ -1165,6 +1190,8 @@
          * previous program in the same channel. In practice, start time will usually be the end
          * time of the previous program.
          *
+         * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel.
+         *
          * <p>Type: INTEGER (long)
          */
         public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
@@ -1176,6 +1203,8 @@
          * next program in the same channel. In practice, end time will usually be the start time of
          * the next program.
          *
+         * <p>Can be empty if this program belongs to a {@link Channels#TYPE_PREVIEW} channel.
+         *
          * <p>Type: INTEGER (long)
          */
         public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
@@ -1410,6 +1439,102 @@
          */
         public static final String COLUMN_VERSION_NUMBER = "version_number";
 
+        /**
+         * The internal ID used by individual TV input services.
+         *
+         * <p>This is internal to the provider that inserted it, and should not be decoded by other
+         * apps.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
+        /**
+         * The URI for the preview video.
+         *
+         * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}. The data in the column must be
+         * a URL, or a URI in one of the following formats:
+         *
+         * <ul>
+         * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+         * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+         * </li>
+         * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_PREVIEW_VIDEO_URI = "preview_video_uri";
+
+        /**
+         * The last playback position (in milliseconds) of the preview video.
+         *
+         * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: INTEGER
+         */
+        public static final String COLUMN_PREVIEW_LAST_PLAYBACK_POSITION =
+                "preview_last_playback_position";
+
+        /**
+         * The duration (in milliseconds) of the preview video.
+         *
+         * <p>This is only relevant to {@link Channels#TYPE_PREVIEW}.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: INTEGER
+         */
+        public static final String COLUMN_PREVIEW_DURATION = "preview_duration";
+
+        /**
+         * The intent URI which is launched when the preview video is selected.
+         *
+         * <p>The URI is created using {@link Intent#toUri} with {@link Intent#URI_INTENT_SCHEME}
+         * and converted back to the original intent with {@link Intent#parseUri}. The intent is
+         * launched when the user selects the preview video item.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_PREVIEW_INTENT_URI =
+                "preview_intent_uri";
+
+        /**
+         * The weight of the preview program within the channel.
+         *
+         * <p>The UI may choose to show this item in a different position in the channel row.
+         * A larger weight value means the program is more important than other programs having
+         * smaller weight values. The value is relevant for the preview programs in the same
+         * channel. This is only relevant to {@link Channels#TYPE_PREVIEW}.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: INTEGER
+         */
+        public static final String COLUMN_PREVIEW_WEIGHT = "preview_weight";
+
+        /**
+         * The flag indicating whether this program is transient or not.
+         *
+         * <p>A value of 1 indicates that the channel will be automatically removed by the system on
+         * reboot, and a value of 0 indicates that the channel is persistent across reboot. If not
+         * specified, this value is set to 0 (not transient) by default.
+         *
+         * <p>Type: INTEGER (boolean)
+         * @see Channels#COLUMN_TRANSIENT
+         * @hide
+         */
+        @SystemApi
+        public static final String COLUMN_TRANSIENT = "transient";
+
         private Programs() {}
 
         /** Canonical genres for TV programs. */
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index dfddaa5..1eae8db 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -317,6 +317,13 @@
      */
     public static final String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
 
+    /**
+     * Activity action to display the recording schedules. When invoked, the system will display an
+     * appropriate UI to browse the schedules.
+     */
+    public static final String ACTION_VIEW_RECORDING_SCHEDULES =
+            "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
+
     private final ITvInputManager mService;
 
     private final Object mLock = new Object();
diff --git a/media/jni/android_media_BufferingParams.h b/media/jni/android_media_BufferingParams.h
new file mode 100644
index 0000000..24c51f5
--- /dev/null
+++ b/media/jni/android_media_BufferingParams.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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_BUFFERING_PARAMS_H_
+#define _ANDROID_MEDIA_BUFFERING_PARAMS_H_
+
+#include <media/BufferingSettings.h>
+
+namespace android {
+
+// This entire class is inline
+struct BufferingParams {
+    BufferingSettings settings;
+
+    struct fields_t {
+        jclass      clazz;
+        jmethodID   constructID;
+
+        jfieldID    initial_buffering_mode;
+        jfieldID    rebuffering_mode;
+        jfieldID    initial_watermark_ms;
+        jfieldID    initial_watermark_kb;
+        jfieldID    rebuffering_watermark_low_ms;
+        jfieldID    rebuffering_watermark_high_ms;
+        jfieldID    rebuffering_watermark_low_kb;
+        jfieldID    rebuffering_watermark_high_kb;
+
+        void init(JNIEnv *env) {
+            jclass lclazz = env->FindClass("android/media/BufferingParams");
+            if (lclazz == NULL) {
+                return;
+            }
+
+            clazz = (jclass)env->NewGlobalRef(lclazz);
+            if (clazz == NULL) {
+                return;
+            }
+
+            constructID = env->GetMethodID(clazz, "<init>", "()V");
+
+            initial_buffering_mode = env->GetFieldID(clazz, "mInitialBufferingMode", "I");
+            rebuffering_mode = env->GetFieldID(clazz, "mRebufferingMode", "I");
+            initial_watermark_ms = env->GetFieldID(clazz, "mInitialWatermarkMs", "I");
+            initial_watermark_kb = env->GetFieldID(clazz, "mInitialWatermarkKB", "I");
+            rebuffering_watermark_low_ms = env->GetFieldID(clazz, "mRebufferingWatermarkLowMs", "I");
+            rebuffering_watermark_high_ms = env->GetFieldID(clazz, "mRebufferingWatermarkHighMs", "I");
+            rebuffering_watermark_low_kb = env->GetFieldID(clazz, "mRebufferingWatermarkLowKB", "I");
+            rebuffering_watermark_high_kb = env->GetFieldID(clazz, "mRebufferingWatermarkHighKB", "I");
+
+            env->DeleteLocalRef(lclazz);
+        }
+
+        void exit(JNIEnv *env) {
+            env->DeleteGlobalRef(clazz);
+            clazz = NULL;
+        }
+    };
+
+    void fillFromJobject(JNIEnv *env, const fields_t& fields, jobject params) {
+        settings.mInitialBufferingMode =
+            (BufferingMode)env->GetIntField(params, fields.initial_buffering_mode);
+        settings.mRebufferingMode =
+            (BufferingMode)env->GetIntField(params, fields.rebuffering_mode);
+        settings.mInitialWatermarkMs =
+            env->GetIntField(params, fields.initial_watermark_ms);
+        settings.mInitialWatermarkKB =
+            env->GetIntField(params, fields.initial_watermark_kb);
+        settings.mRebufferingWatermarkLowMs =
+            env->GetIntField(params, fields.rebuffering_watermark_low_ms);
+        settings.mRebufferingWatermarkHighMs =
+            env->GetIntField(params, fields.rebuffering_watermark_high_ms);
+        settings.mRebufferingWatermarkLowKB =
+            env->GetIntField(params, fields.rebuffering_watermark_low_kb);
+        settings.mRebufferingWatermarkHighKB =
+            env->GetIntField(params, fields.rebuffering_watermark_high_kb);
+    }
+
+    jobject asJobject(JNIEnv *env, const fields_t& fields) {
+        jobject params = env->NewObject(fields.clazz, fields.constructID);
+        if (params == NULL) {
+            return NULL;
+        }
+        env->SetIntField(params, fields.initial_buffering_mode, (jint)settings.mInitialBufferingMode);
+        env->SetIntField(params, fields.rebuffering_mode, (jint)settings.mRebufferingMode);
+        env->SetIntField(params, fields.initial_watermark_ms, (jint)settings.mInitialWatermarkMs);
+        env->SetIntField(params, fields.initial_watermark_kb, (jint)settings.mInitialWatermarkKB);
+        env->SetIntField(params, fields.rebuffering_watermark_low_ms, (jint)settings.mRebufferingWatermarkLowMs);
+        env->SetIntField(params, fields.rebuffering_watermark_high_ms, (jint)settings.mRebufferingWatermarkHighMs);
+        env->SetIntField(params, fields.rebuffering_watermark_low_kb, (jint)settings.mRebufferingWatermarkLowKB);
+        env->SetIntField(params, fields.rebuffering_watermark_high_kb, (jint)settings.mRebufferingWatermarkHighKB);
+
+        return params;
+    }
+};
+
+}  // namespace android
+
+#endif  // _ANDROID_MEDIA_BUFFERING_PARAMS_H_
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index c52ed94..8225052 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -37,6 +37,7 @@
 #include "utils/Errors.h"  // for status_t
 #include "utils/KeyedVector.h"
 #include "utils/String8.h"
+#include "android_media_BufferingParams.h"
 #include "android_media_MediaDataSource.h"
 #include "android_media_PlaybackParams.h"
 #include "android_media_SyncParams.h"
@@ -69,6 +70,7 @@
 };
 static fields_t fields;
 
+static BufferingParams::fields_t gBufferingParamsFields;
 static PlaybackParams::fields_t gPlaybackParamsFields;
 static SyncParams::fields_t gSyncParamsFields;
 
@@ -343,6 +345,66 @@
     setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
 }
 
+static jobject
+android_media_MediaPlayer_getDefaultBufferingParams(JNIEnv *env, jobject thiz)
+{
+    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+    if (mp == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    BufferingParams bp;
+    BufferingSettings &settings = bp.settings;
+    process_media_player_call(
+            env, thiz, mp->getDefaultBufferingSettings(&settings),
+            "java/lang/IllegalStateException", "unexpected error");
+    ALOGV("getDefaultBufferingSettings:{%s}", settings.toString().string());
+
+    return bp.asJobject(env, gBufferingParamsFields);
+}
+
+static jobject
+android_media_MediaPlayer_getBufferingParams(JNIEnv *env, jobject thiz)
+{
+    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+    if (mp == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+
+    BufferingParams bp;
+    BufferingSettings &settings = bp.settings;
+    process_media_player_call(
+            env, thiz, mp->getBufferingSettings(&settings),
+            "java/lang/IllegalStateException", "unexpected error");
+    ALOGV("getBufferingSettings:{%s}", settings.toString().string());
+
+    return bp.asJobject(env, gBufferingParamsFields);
+}
+
+static void
+android_media_MediaPlayer_setBufferingParams(JNIEnv *env, jobject thiz, jobject params)
+{
+    if (params == NULL) {
+        return;
+    }
+
+    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+    if (mp == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    BufferingParams bp;
+    bp.fillFromJobject(env, gBufferingParamsFields, params);
+    ALOGV("setBufferingParams:{%s}", bp.settings.toString().string());
+
+    process_media_player_call(
+            env, thiz, mp->setBufferingSettings(bp.settings),
+            "java/lang/IllegalStateException", "unexpected error");
+}
+
 static void
 android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)
 {
@@ -860,6 +922,7 @@
 
     env->DeleteLocalRef(clazz);
 
+    gBufferingParamsFields.init(env);
     gPlaybackParamsFields.init(env);
     gSyncParamsFields.init(env);
 }
@@ -1046,6 +1109,9 @@
     {"_setDataSource",      "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},
     {"_setDataSource",      "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback },
     {"_setVideoSurface",    "(Landroid/view/Surface;)V",        (void *)android_media_MediaPlayer_setVideoSurface},
+    {"getDefaultBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getDefaultBufferingParams},
+    {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getBufferingParams},
+    {"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer_setBufferingParams},
     {"_prepare",            "()V",                              (void *)android_media_MediaPlayer_prepare},
     {"prepareAsync",        "()V",                              (void *)android_media_MediaPlayer_prepareAsync},
     {"_start",              "()V",                              (void *)android_media_MediaPlayer_start},
diff --git a/native/android/hardware_buffer.cpp b/native/android/hardware_buffer.cpp
index 6a10cb5..2f75c10 100644
--- a/native/android/hardware_buffer.cpp
+++ b/native/android/hardware_buffer.cpp
@@ -89,6 +89,12 @@
         return BAD_VALUE;
     }
 
+    if (desc->format == AHARDWAREBUFFER_FORMAT_BLOB && desc->height != 1) {
+        ALOGE("Height must be 1 when using the AHARDWAREBUFFER_FORMAT_BLOB "
+                "format");
+        return BAD_VALUE;
+    }
+
     status_t err;
     uint32_t usage = android_hardware_HardwareBuffer_convertToGrallocUsageBits(
             desc->usage0, desc->usage1);
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 28d9e5c..e2080b0 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -37,5 +37,7 @@
         <activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar"
             android:excludeFromRecents="true"/>
+        <service android:name="com.android.carrierdefaultapp.ProvisionObserver"
+                 android:permission="android.permission.BIND_JOB_SERVICE"/>
     </application>
 </manifest>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
index bc0fa02..3fd89d9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
@@ -28,6 +28,10 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         Log.d(TAG, "onReceive intent: " + intent.getAction());
+        if (ProvisionObserver.isDeferredForProvision(context, intent)) {
+            Log.d(TAG, "skip carrier actions during provisioning");
+            return;
+        }
         List<Integer> actionList = CustomConfigLoader.loadCarrierActionList(context, intent);
         for (int actionIdx : actionList) {
             Log.d(TAG, "apply carrier action idx: " + actionIdx);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
new file mode 100644
index 0000000..3e34f0a
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -0,0 +1,114 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * Service to run {@link android.app.job.JobScheduler} job.
+ * Service to monitor when there is a change to conent URI
+ * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}
+ */
+public class ProvisionObserver extends JobService {
+
+    private static final String TAG = ProvisionObserver.class.getSimpleName();
+    public static final int PROVISION_OBSERVER_REEVALUATION_JOB_ID = 1;
+    // minimum & maximum update delay TBD
+    private static final int CONTENT_UPDATE_DELAY_MS = 100;
+    private static final int CONTENT_MAX_DELAY_MS = 200;
+
+    @Override
+    public boolean onStartJob(JobParameters jobParameters) {
+        switch (jobParameters.getJobId()) {
+            case PROVISION_OBSERVER_REEVALUATION_JOB_ID:
+                if (isProvisioned(this)) {
+                    Log.d(TAG, "device provisioned, force network re-evaluation");
+                    final ConnectivityManager connMgr = ConnectivityManager.from(this);
+                    Network[] info = connMgr.getAllNetworks();
+                    for (Network nw : info) {
+                        final NetworkCapabilities nc = connMgr.getNetworkCapabilities(nw);
+                        if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                            // force connectivity re-evaluation to assume skipped carrier actions.
+                            // one of the following calls will match the last evaluation.
+                            connMgr.reportNetworkConnectivity(nw, true);
+                            connMgr.reportNetworkConnectivity(nw, false);
+                            break;
+                        }
+                    }
+                }
+            default:
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        return false;
+    }
+
+    // Returns true if the device is not provisioned yet (in setup wizard), false otherwise
+    private static boolean isProvisioned(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+    }
+
+    /**
+     * Static utility function to schedule a job to execute upon the change of content URI
+     * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}.
+     * @param context The context used to retrieve the {@link ComponentName} and system services
+     * @return true carrier actions are deferred due to phone provisioning process, false otherwise
+     */
+    public static boolean isDeferredForProvision(Context context, Intent intent) {
+        if (isProvisioned(context)) {
+            return false;
+        }
+        int jobId;
+        switch(intent.getAction()) {
+            case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+                jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
+                break;
+            default:
+                return false;
+        }
+        final JobScheduler jobScheduler =  (JobScheduler) context.getSystemService(
+                Context.JOB_SCHEDULER_SERVICE);
+        final JobInfo job = new JobInfo.Builder(jobId,
+                new ComponentName(context, ProvisionObserver.class))
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(
+                        Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), 0))
+                .setTriggerContentUpdateDelay(CONTENT_UPDATE_DELAY_MS)
+                .setTriggerContentMaxDelay(CONTENT_MAX_DELAY_MS)
+                .build();
+        jobScheduler.schedule(job);
+        return true;
+    }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a1b2bdf..0129632 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -893,4 +893,11 @@
 
     <!-- Label for Greenwich mean time, used in a string like GMT+05:00. [CHAR LIMIT=NONE] -->
     <string name="time_zone_gmt">GMT</string>
+
+    <!-- Label for carrier demo mode factory reset confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="retail_demo_reset_message">Enter password to perform factory reset in demo mode</string>
+    <!-- Label for positive button on carrier demo  mode factory reset confirmation dialog [CHAR LIMIT=40] -->
+    <string name="retail_demo_reset_next">Next</string>
+    <!-- Title for carrier demo mode factory reset confirmation dialog. [CHAR LIMIT=40] -->
+    <string name="retail_demo_reset_title">Password required</string>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
new file mode 100644
index 0000000..1d9d03a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib;
+
+import android.content.Context;
+import android.net.ScoredNetwork;
+
+import com.android.internal.logging.MetricsLogger;
+
+/** Utilites for Tron Logging. */
+public final class TronUtils {
+
+    private TronUtils() {};
+
+    public static void logWifiSettingsBadge(Context context, int badgeEnum) {
+        logNetworkBadgeMetric(context, "settings_wifibadging", badgeEnum);
+    }
+
+    /**
+     * Logs an occurrence of the given network badge to a Histogram.
+     *
+     * @param context Context
+     * @param histogram the Tron histogram name to write to
+     * @param badgeEnum the {@link ScoredNetwork.Badging} badge value
+     * @throws IllegalArgumentException if the given badge enum is not supported
+     */
+    private static void logNetworkBadgeMetric(
+            Context context, String histogram, int badgeEnum)
+            throws IllegalArgumentException {
+        int bucket;
+        switch (badgeEnum) {
+            case ScoredNetwork.BADGING_NONE:
+                bucket = 0;
+                break;
+            case ScoredNetwork.BADGING_SD:
+                bucket = 1;
+                break;
+            case ScoredNetwork.BADGING_HD:
+                bucket = 2;
+                break;
+            case ScoredNetwork.BADGING_4K:
+                bucket = 3;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported badge enum: " + badgeEnum);
+        }
+
+        MetricsLogger.histogram(context, histogram, bucket);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index fbc6aa3..ae6ada2a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -21,6 +21,8 @@
 import android.os.BatteryManager;
 import android.os.UserManager;
 import android.print.PrintManager;
+import android.view.View;
+
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.UserIconDrawable;
 
@@ -32,7 +34,7 @@
     private static String sServicesSystemSharedLibPackageName;
     private static String sSharedSystemSharedLibPackageName;
 
-    static final int[] WIFI_PIE_FOR_BADGING = {
+    public static final int[] WIFI_PIE_FOR_BADGING = {
           com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars,
           com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar,
           com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars,
@@ -288,8 +290,15 @@
                 });
     }
 
-    private static int getWifiBadgeResource(int badge) {
+    /**
+     * Returns the resource id for the given badge or {@link View.NO_ID} if no badge is to be shown.
+     *
+     * @throws IllegalArgumentException if the given badge value is not supported.
+     */
+    public static int getWifiBadgeResource(int badge) {
         switch (badge) {
+            case ScoredNetwork.BADGING_NONE:
+                return View.NO_ID;
             case ScoredNetwork.BADGING_SD:
                 return com.android.internal.R.drawable.ic_signal_wifi_badged_sd;
             case ScoredNetwork.BADGING_HD:
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 2b1582d..24a3aa9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1429,6 +1429,23 @@
         }
     };
 
+    public static final AppFilter FILTER_GAMES = new AppFilter() {
+        @Override
+        public void init() {
+        }
+
+        @Override
+        public boolean filterApp(ApplicationsState.AppEntry info) {
+            // TODO: Update for the new game category.
+            boolean isGame;
+            synchronized (info.info) {
+                isGame = ((info.info.flags & ApplicationInfo.FLAG_IS_GAME) != 0)
+                        || info.info.category == ApplicationInfo.CATEGORY_GAME;
+            }
+            return isGame;
+        }
+    };
+
     public static class VolumeFilter implements AppFilter {
         private final String mVolumeUuid;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 8ebea61..703bc17 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -34,7 +34,9 @@
 import android.util.SparseArray;
 import android.widget.ImageView;
 import android.widget.TextView;
+
 import com.android.settingslib.R;
+import com.android.settingslib.TronUtils;
 import com.android.settingslib.Utils;
 
 public class AccessPointPreference extends Preference {
@@ -184,7 +186,8 @@
         if (level == -1) {
             safeSetDefaultIcon();
         } else {
-           if (mWifiBadge != ScoredNetwork.BADGING_NONE) {
+            TronUtils.logWifiSettingsBadge(context, mWifiBadge);
+            if (mWifiBadge != ScoredNetwork.BADGING_NONE) {
                 // TODO(sghuman): Refactor this to reuse drawable to save memory and add to a
                 // special subclass of AccessPointPreference
                 LayerDrawable drawable = Utils.getBadgedWifiIcon(context, level, mWifiBadge);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index fabae57..6f52dca 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -12,13 +12,17 @@
 
 import android.content.Intent;
 import android.net.NetworkInfo;
+import android.net.NetworkKey;
+import android.net.WifiKey;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.util.Log;
 
 import java.util.List;
 
 public class WifiStatusTracker {
+    private static final String TAG = "WifiStatusTracker";
 
     private final WifiManager mWifiManager;
     public boolean enabled;
@@ -26,6 +30,7 @@
     public String ssid;
     public int rssi;
     public int level;
+    public NetworkKey networkKey;
 
     public WifiStatusTracker(WifiManager wifiManager) {
         mWifiManager = wifiManager;
@@ -40,19 +45,32 @@
             final NetworkInfo networkInfo = (NetworkInfo)
                     intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
             connected = networkInfo != null && networkInfo.isConnected();
+            WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
+                    ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
+                    : mWifiManager.getConnectionInfo();
+
             // If Connected grab the signal strength and ssid.
-            if (connected) {
-                // try getting it out of the intent first
-                WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
-                        ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
-                        : mWifiManager.getConnectionInfo();
-                if (info != null) {
-                    ssid = getSsid(info);
+            if (connected && info != null) {
+                ssid = getSsid(info);
+                String bssid = info.getBSSID();
+                if ((ssid != null) && (bssid != null)) {
+                    // Reuse existing network key object if possible.
+                    if ((networkKey == null)
+                            || !networkKey.wifiKey.ssid.equals(ssid)
+                            || !networkKey.wifiKey.bssid.equals(bssid)) {
+                        try {
+                            networkKey = new NetworkKey(
+                                    new WifiKey(ssid, bssid));
+                        } catch (IllegalArgumentException e) {
+                            Log.e(TAG, "Cannot create NetworkKey", e);
+                        }
+                    }
                 } else {
-                    ssid = null;
+                    networkKey = null;
                 }
-            } else if (!connected) {
+            } else {
                 ssid = null;
+                networkKey = null;
             }
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
             // Default to -200 as its below WifiManager.MIN_RSSI.
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
index 98bce0c..bd910dd 100644
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ b/packages/SettingsLib/tests/integ/Android.mk
@@ -28,7 +28,8 @@
     android-support-test \
     espresso-core \
     mockito-target-minus-junit4 \
-    legacy-android-test
+    legacy-android-test \
+    truth-prebuilt
 
 include frameworks/base/packages/SettingsLib/common.mk
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
new file mode 100644
index 0000000..4f2347d
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.ApplicationInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ApplicationsStateTest {
+    private ApplicationsState.AppFilter mFilter;
+    private ApplicationsState.AppEntry mEntry;
+
+    @Before
+    public void setUp() {
+        mFilter = ApplicationsState.FILTER_GAMES;
+        mEntry = mock(ApplicationsState.AppEntry.class);
+        mEntry.info = mock(ApplicationInfo.class);
+    }
+
+    @Test
+    public void testGamesFilterAcceptsGameDeprecated() {
+        mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
+
+        assertThat(mFilter.filterApp(mEntry)).isTrue();
+    }
+
+    @Test
+    public void testGameFilterAcceptsCategorizedGame() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+        assertThat(mFilter.filterApp(mEntry)).isTrue();
+    }
+
+    @Test
+    public void testGameFilterAcceptsCategorizedGameAndDeprecatedIsGame() {
+        mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME;
+        mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+        assertThat(mFilter.filterApp(mEntry)).isTrue();
+    }
+
+    @Test
+    public void testGamesFilterRejectsNotGame() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_UNDEFINED;
+
+        assertThat(mFilter.filterApp(mEntry)).isFalse();
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 6979995..25e1f16 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -80,16 +80,20 @@
 import java.io.FileNotFoundException;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
 
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SHELL_UID;
@@ -996,7 +1000,7 @@
                     continue;
                 }
 
-                // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+                // As of Android O, the SSAID is read from an app-specific entry in table
                 // SETTINGS_FILE_SSAID, unless accessed by a system process.
                 final Setting setting;
                 if (isNewSsaidSetting(name)) {
@@ -1035,7 +1039,7 @@
 
         // Get the value.
         synchronized (mLock) {
-            // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+            // As of Android O, the SSAID is read from an app-specific entry in table
             // SETTINGS_FILE_SSAID, unless accessed by a system process.
             if (isNewSsaidSetting(name)) {
                 return getSsaidSettingLocked(owningUserId);
@@ -1978,12 +1982,12 @@
 
         private void generateUserKeyLocked(int userId) {
             // Generate a random key for each user used for creating a new ssaid.
-            final byte[] keyBytes = new byte[16];
+            final byte[] keyBytes = new byte[32];
             final SecureRandom rand = new SecureRandom();
             rand.nextBytes(keyBytes);
 
             // Convert to string for storage in settings table.
-            final String userKey = ByteStringUtils.toString(keyBytes);
+            final String userKey = ByteStringUtils.toHexString(keyBytes);
 
             // Store the key in the ssaid table.
             final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
@@ -1995,6 +1999,10 @@
             }
         }
 
+        private byte[] getLengthPrefix(byte[] data) {
+            return ByteBuffer.allocate(4).putInt(data.length).array();
+        }
+
         public Setting generateSsaidLocked(String packageName, int userId) {
             final PackageInfo packageInfo;
             try {
@@ -2019,25 +2027,37 @@
             final String userKey = userKeySetting.getValue();
 
             // Convert the user's key back to a byte array.
-            final byte[] keyBytes = ByteStringUtils.toByteArray(userKey);
-            if (keyBytes == null || keyBytes.length != 16) {
+            final byte[] keyBytes = ByteStringUtils.fromHexToByteArray(userKey);
+
+            // Validate that the key is of expected length.
+            // Keys are currently 32 bytes, but were once 16 bytes during Android O development.
+            if (keyBytes == null || (keyBytes.length != 16 && keyBytes.length != 32)) {
                 throw new IllegalStateException("User key invalid");
             }
 
-            final MessageDigest md;
+            final Mac m;
             try {
-                // Hash package name and signature.
-                md = MessageDigest.getInstance("SHA-256");
+                m = Mac.getInstance("HmacSHA256");
+                m.init(new SecretKeySpec(keyBytes, m.getAlgorithm()));
             } catch (NoSuchAlgorithmException e) {
-                throw new IllegalStateException("HmacSHA256 is not available");
+                throw new IllegalStateException("HmacSHA256 is not available", e);
+            } catch (InvalidKeyException e) {
+                throw new IllegalStateException("Key is corrupted", e);
             }
-            md.update(keyBytes);
-            md.update(packageInfo.packageName.getBytes(StandardCharsets.UTF_8));
-            md.update(packageInfo.signatures[0].toByteArray());
+
+            // Mac the package name and each of the signatures.
+            byte[] packageNameBytes = packageInfo.packageName.getBytes(StandardCharsets.UTF_8);
+            m.update(getLengthPrefix(packageNameBytes), 0, 4);
+            m.update(packageNameBytes);
+            for (int i = 0; i < packageInfo.signatures.length; i++) {
+                byte[] sig = packageInfo.signatures[i].toByteArray();
+                m.update(getLengthPrefix(sig), 0, 4);
+                m.update(sig);
+            }
 
             // Convert result to a string for storage in settings table. Only want first 64 bits.
-            final String ssaid = ByteStringUtils.toString(md.digest()).substring(0, 16)
-                    .toLowerCase();
+            final String ssaid = ByteStringUtils.toHexString(m.doFinal()).substring(0, 16)
+                    .toLowerCase(Locale.US);
 
             // Save the ssaid in the ssaid table.
             final String uid = Integer.toString(packageInfo.applicationInfo.uid);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
new file mode 100644
index 0000000..c0a48a8
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java
@@ -0,0 +1,74 @@
+
+package com.android.systemui.plugins.statusbar;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+import android.view.View;
+
+import java.util.ArrayList;
+
+import com.android.systemui.plugins.Plugin;
+
+public interface NotificationMenuRowProvider extends Plugin {
+
+    public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
+
+    public static final int VERSION = 1;
+
+    /**
+     * Returns a list of items to populate the menu 'behind' a notification.
+     */
+    public ArrayList<MenuItem> getMenuItems(Context context);
+
+    public interface OnMenuClickListener {
+        public void onMenuClicked(View row, int x, int y, MenuItem menu);
+
+        public void onMenuReset(View row);
+    }
+
+    public interface GutsInteractionListener {
+        public void onInteraction(View view);
+
+        public void closeGuts(View view);
+    }
+
+    public interface GutsContent {
+        public void setInteractionListener(GutsInteractionListener listener);
+
+        public View getContentView();
+
+        public boolean handleCloseControls();
+    }
+
+    public interface SnoozeGutsContent extends GutsContent {
+        public void setSnoozeListener(SnoozeListener listener);
+
+        public void setStatusBarNotification(StatusBarNotification sbn);
+    }
+
+    public interface SnoozeListener {
+        public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil);
+    }
+
+    public static class MenuItem {
+        public Drawable icon;
+        public String menuDescription;
+        public View menuView;
+        public GutsContent gutsContent;
+
+        public MenuItem(Drawable i, String s, GutsContent content) {
+            icon = i;
+            menuDescription = s;
+            gutsContent = content;
+        }
+
+        public View getGutsView() {
+            return gutsContent.getContentView();
+        }
+
+        public boolean onTouch(View v, int x, int y) {
+            return false;
+        }
+    }
+}
diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml
index 5e08fcb..abafb68 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2016 The Android Open Source Project
+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.
@@ -19,20 +19,22 @@
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
     <path
-        android:fillColor="#00796B"
-        android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+        android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
+        android:fillAlpha="0.066"
+        android:fillColor="#000000"/>
     <path
-        android:fillColor="#00796B"
-        android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+        android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
+        android:fillColor="#FFC107"/>
     <path
-        android:fillColor="#40000000"
-        android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
+        android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
+        android:fillColor="#FE9F00"/>
     <path
-        android:fillColor="#40000000"
-        android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
+        android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
+        android:fillColor="#FED44F"/>
     <path
-        android:fillColor="#4DB6AC"
-        android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+        android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
+        android:fillColor="#FFC107"/>
+    <path
+        android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
+        android:fillColor="#FFFFFF"/>
 </vector>
-
-
diff --git a/packages/SystemUI/res/drawable/ic_left.xml b/packages/SystemUI/res/drawable/ic_left.xml
new file mode 100644
index 0000000..cea4cfc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_left.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M15.41,16.09l-4.58,-4.59 4.58,-4.59L14.0,5.5l-6.0,6.0 6.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_menu.xml b/packages/SystemUI/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..994bc5e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_menu.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
index 439ee3b..6264484 100644
--- a/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_data_disabled.xml
@@ -16,8 +16,8 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="17dp"
         android:height="17.0dp"
-        android:viewportWidth="20.0"
-        android:viewportHeight="20.0">
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M19.0,6.41L17.59,5.0 12.0,10.59 6.41,5.0 5.0,6.41 10.59,12.0 5.0,17.59 6.41,19.0 12.0,13.41 17.59,19.0 19.0,17.59 13.41,12.0z"/>
diff --git a/packages/SystemUI/res/drawable/ic_remove.xml b/packages/SystemUI/res/drawable/ic_remove.xml
new file mode 100644
index 0000000..75b2793
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_remove.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M19.0,13.0L5.0,13.0l0.0,-2.0l14.0,0.0l0.0,2.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_right.xml b/packages/SystemUI/res/drawable/ic_right.xml
new file mode 100644
index 0000000..35699f73
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_right.xml
@@ -0,0 +1,24 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10.0,5.75l6.0,6.0 -6.0,6.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
new file mode 100644
index 0000000..b0b03a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
+        android:fillColor="#757575"/>
+    <path
+        android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
+        android:fillColor="#757575"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_accessibility.xml b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml
new file mode 100644
index 0000000..657efaa
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_accessibility.xml
@@ -0,0 +1,25 @@
+<!--
+  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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32dp"
+        android:height="32dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1s-5.89,-0.3 -8.5,-1L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1l-0.5,-2zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
index 694019e9..ea794d4 100644
--- a/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_data_disabled.xml
@@ -14,9 +14,10 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="8.5dp"
+        android:autoMirrored="true"
+        android:width="17.0dp"
         android:height="17.0dp"
-        android:viewportWidth="20.0"
+        android:viewportWidth="40.0"
         android:viewportHeight="40.0">
     <path
         android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index 3948dc4..9d8ef83 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -23,125 +23,4 @@
     android:visibility="gone"
     android:clickable="true"
     android:gravity="top|start"
-    android:orientation="vertical"
-    android:paddingStart="@*android:dimen/notification_content_margin_start"
-    android:paddingEnd="8dp"
-    android:background="@color/notification_guts_bg_color"
-    android:theme="@*android:style/Theme.DeviceDefault.Light">
-
-    <!-- header -->
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingTop="20dp"
-        android:paddingEnd="8dp"
-        android:paddingBottom="15dp"
-        android:id="@+id/notification_guts_header">
-        <TextView
-            android:id="@+id/pkgname"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            style="@style/TextAppearance.NotificationGuts.Secondary" />
-        <TextView
-            android:id="@+id/channel_name"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:layout_below="@id/pkgname"
-            style="@style/TextAppearance.NotificationGuts.Header" />
-        <Switch
-            android:id="@+id/channel_enabled_switch"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentRight="true"
-            android:layout_centerVertical="true"
-            android:background="@null" />
-    </RelativeLayout>
-    <!-- Importance radio buttons -->
-    <LinearLayout
-        android:id="@+id/importance"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <RadioGroup
-            android:id="@+id/importance_buttons"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingEnd="@*android:dimen/notification_content_margin_end">
-            <RadioButton
-                android:id="@+id/high_importance"
-                android:layout_width="wrap_content"
-                android:layout_height="@dimen/notification_inline_importance_height"
-                style="@style/TextAppearance.NotificationGuts.Radio"
-                android:buttonTint="@color/notification_guts_buttons" />
-            <RadioButton
-                android:id="@+id/default_importance"
-                android:layout_width="wrap_content"
-                android:layout_height="@dimen/notification_inline_importance_height"
-                style="@style/TextAppearance.NotificationGuts.Radio"
-                android:buttonTint="@color/notification_guts_buttons" />
-            <RadioButton
-                android:id="@+id/low_importance"
-                android:layout_width="wrap_content"
-                android:layout_height="@dimen/notification_inline_importance_height"
-                style="@style/TextAppearance.NotificationGuts.Radio"
-                android:buttonTint="@color/notification_guts_buttons" />
-            <RadioButton
-                android:id="@+id/min_importance"
-                android:layout_width="wrap_content"
-                android:layout_height="@dimen/notification_inline_importance_height"
-                style="@style/TextAppearance.NotificationGuts.Radio"
-                android:buttonTint="@color/notification_guts_buttons" />
-        </RadioGroup>
-        <LinearLayout
-            android:id="@+id/importance_buttons_text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical">
-            <include layout="@layout/notification_guts_importance_text"/>
-            <include layout="@layout/notification_guts_importance_text"/>
-            <include layout="@layout/notification_guts_importance_text"/>
-            <include layout="@layout/notification_guts_importance_text"/>
-        </LinearLayout>
-    </LinearLayout>
-    <!-- Channel Disabled Text -->
-    <TextView
-        android:id="@+id/channel_disabled"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/notification_channel_disabled"
-        style="@style/TextAppearance.NotificationGuts.Secondary" />
-    <!-- Settings and Done buttons -->
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="end"
-        android:paddingTop="16dp"
-        android:paddingBottom="8dp" >
-
-        <TextView
-            android:id="@+id/more_settings"
-            android:text="@string/notification_more_settings"
-            android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            style="@style/TextAppearance.NotificationGuts.Button"
-            android:background="@drawable/btn_borderless_rect"
-            android:gravity="center"
-            android:paddingEnd="8dp"
-            android:paddingStart="8dp"
-            android:focusable="true" />
-
-        <TextView
-            android:id="@+id/done"
-            android:text="@string/notification_done"
-            android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            style="@style/TextAppearance.NotificationGuts.Button"
-            android:background="@drawable/btn_borderless_rect"
-            android:gravity="center"
-            android:layout_marginStart="8dp"
-            android:layout_marginEnd="8dp"
-            android:focusable="true"/>
-    </LinearLayout>
-</com.android.systemui.statusbar.NotificationGuts>
+    android:theme="@*android:style/Theme.DeviceDefault.Light"/>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
new file mode 100644
index 0000000..9770ecc
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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.
+-->
+
+<com.android.systemui.statusbar.NotificationInfo
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/notification_guts"
+        android:clickable="true"
+        android:gravity="top|start"
+        android:orientation="vertical"
+        android:paddingStart="@*android:dimen/notification_content_margin_start"
+        android:paddingEnd="8dp"
+        android:background="@color/notification_guts_bg_color"
+        android:theme="@*android:style/Theme.DeviceDefault.Light">
+
+    <!-- header -->
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="20dp"
+        android:paddingEnd="8dp"
+        android:paddingBottom="15dp"
+        android:id="@+id/notification_guts_header">
+        <TextView
+            android:id="@+id/pkgname"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            style="@style/TextAppearance.NotificationGuts.Secondary" />
+        <TextView
+            android:id="@+id/channel_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentStart="true"
+            android:layout_below="@id/pkgname"
+            style="@style/TextAppearance.NotificationGuts.Header" />
+        <Switch
+            android:id="@+id/channel_enabled_switch"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_centerVertical="true"
+            android:background="@null" />
+    </RelativeLayout>
+    <!-- Importance radio buttons -->
+    <LinearLayout
+        android:id="@+id/importance"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <RadioGroup
+            android:id="@+id/importance_buttons"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingEnd="@*android:dimen/notification_content_margin_end">
+            <RadioButton
+                android:id="@+id/high_importance"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/notification_inline_importance_height"
+                style="@style/TextAppearance.NotificationGuts.Radio"
+                android:buttonTint="@color/notification_guts_buttons" />
+            <RadioButton
+                android:id="@+id/default_importance"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/notification_inline_importance_height"
+                style="@style/TextAppearance.NotificationGuts.Radio"
+                android:buttonTint="@color/notification_guts_buttons" />
+            <RadioButton
+                android:id="@+id/low_importance"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/notification_inline_importance_height"
+                style="@style/TextAppearance.NotificationGuts.Radio"
+                android:buttonTint="@color/notification_guts_buttons" />
+            <RadioButton
+                android:id="@+id/min_importance"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/notification_inline_importance_height"
+                style="@style/TextAppearance.NotificationGuts.Radio"
+                android:buttonTint="@color/notification_guts_buttons" />
+        </RadioGroup>
+        <LinearLayout
+            android:id="@+id/importance_buttons_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <include layout="@layout/notification_guts_importance_text"/>
+            <include layout="@layout/notification_guts_importance_text"/>
+            <include layout="@layout/notification_guts_importance_text"/>
+            <include layout="@layout/notification_guts_importance_text"/>
+        </LinearLayout>
+    </LinearLayout>
+    <!-- Channel Disabled Text -->
+    <TextView
+        android:id="@+id/channel_disabled"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/notification_channel_disabled"
+        style="@style/TextAppearance.NotificationGuts.Secondary" />
+    <!-- Settings and Done buttons -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="end"
+        android:paddingTop="16dp"
+        android:paddingBottom="8dp" >
+
+        <TextView
+            android:id="@+id/more_settings"
+            android:text="@string/notification_more_settings"
+            android:layout_width="wrap_content"
+            android:layout_height="36dp"
+            style="@style/TextAppearance.NotificationGuts.Button"
+            android:background="@drawable/btn_borderless_rect"
+            android:gravity="center"
+            android:paddingEnd="8dp"
+            android:paddingStart="8dp"
+            android:focusable="true" />
+
+        <TextView
+            android:id="@+id/done"
+            android:text="@string/notification_done"
+            android:layout_width="wrap_content"
+            android:layout_height="36dp"
+            style="@style/TextAppearance.NotificationGuts.Button"
+            android:background="@drawable/btn_borderless_rect"
+            android:gravity="center"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:focusable="true"/>
+    </LinearLayout>
+</com.android.systemui.statusbar.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_menu_row.xml b/packages/SystemUI/res/layout/notification_menu_row.xml
new file mode 100644
index 0000000..12bcf81
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_menu_row.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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.
+-->
+<com.android.systemui.statusbar.NotificationMenuRow
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:visibility="invisible"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_settings_icon_row.xml b/packages/SystemUI/res/layout/notification_settings_icon_row.xml
deleted file mode 100644
index da3461b90..0000000
--- a/packages/SystemUI/res/layout/notification_settings_icon_row.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright 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.
--->
-<com.android.systemui.statusbar.NotificationSettingsIconRow
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:visibility="invisible"
-    >
-
-    <com.android.systemui.statusbar.AlphaOptimizedImageView
-        android:id="@+id/gear_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:padding="@dimen/notification_gear_padding"
-        android:src="@drawable/ic_settings"
-        android:tint="@color/notification_gear_color"
-        android:alpha="0"
-        android:background="?android:attr/selectableItemBackgroundBorderless"
-        />
-
-</com.android.systemui.statusbar.NotificationSettingsIconRow>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
new file mode 100644
index 0000000..5bd64de
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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.
+-->
+
+<com.android.systemui.statusbar.NotificationSnooze
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/snooze_snackbar_min_height"
+    android:id="@+id/notification_snooze"
+    android:clickable="true"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:paddingStart="24dp"
+    android:paddingEnd="24dp"
+    android:background="@color/snooze_snackbar_bg">
+    
+    <TextView
+        android:id="@+id/snooze_option_default"
+        style="@style/TextAppearance.SnoozeSnackBar"
+        android:layout_width="wrap_content"
+       	android:layout_height="match_parent"
+       	android:gravity="center_vertical"
+      	android:drawableTint="@android:color/white"
+       	android:drawableEnd="@drawable/notification_expand_more"/>
+    
+    <android.widget.Space
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        />
+    
+    <TextView
+        android:id="@+id/undo"
+        style="@style/TextAppearance.SnoozeSnackBar.Button"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="8dp"
+        android:background="@drawable/btn_borderless_rect"
+        android:layout_gravity="end"
+        android:text="@string/snooze_undo" />
+    
+    <LinearLayout
+        android:id="@+id/snooze_options"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp"
+        android:orientation="vertical"/>
+    
+</com.android.systemui.statusbar.NotificationSnooze>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 4122707..174776c 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -148,7 +148,7 @@
         android:layout_alignParentTop="true"
         android:focusable="true"
         android:gravity="center_vertical"
-        android:paddingEnd="16dp"
+        android:paddingStart="16dp"
         android:paddingTop="6dp"
         android:singleLine="true"
         android:text="@*android:string/emergency_calls_only"
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 5ee242d..789b765 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -23,7 +23,6 @@
     android:layout_gravity="top|center_horizontal">
     <com.android.systemui.recents.views.FixedSizeImageView
         android:id="@+id/icon"
-        android:contentDescription="@string/recents_app_info_button_label"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical|start"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index e456984..d62cc18 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -24,10 +24,10 @@
     >
 
     <ViewStub
-        android:layout="@layout/notification_settings_icon_row"
-        android:id="@+id/settings_icon_row_stub"
-        android:inflatedId="@+id/notification_settings_icon_row"
-        android:layout_width="wrap_content"
+        android:layout="@layout/notification_menu_row"
+        android:id="@+id/menu_row_stub"
+        android:inflatedId="@+id/notification_menu_row"
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
         />
 
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index fd147cd..63b9d75 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -34,7 +34,7 @@
     <bool name="config_keyguardUserSwitcher">true</bool>
 
     <!-- Nav bar button default ordering/layout -->
-    <string name="config_navBarLayout" translatable="false">space;back,home,recent;menu_ime</string>
+    <string name="config_navBarLayout" translatable="false">left;back,home,recent;right</string>
 
     <!-- Animation duration when using long press on recents to dock -->
     <integer name="long_press_dock_anim_duration">290</integer>
diff --git a/packages/SystemUI/res/values-sw900dp/config.xml b/packages/SystemUI/res/values-sw900dp/config.xml
index d8f9ef4..221b013 100644
--- a/packages/SystemUI/res/values-sw900dp/config.xml
+++ b/packages/SystemUI/res/values-sw900dp/config.xml
@@ -19,6 +19,6 @@
 <resources>
 
     <!-- Nav bar button default ordering/layout -->
-    <string name="config_navBarLayout" translatable="false">back,home;space;menu_ime,recent</string>
+    <string name="config_navBarLayout" translatable="false">back,home,left;space;right,recent</string>
 
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index b18b6ac..1ec611a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -75,6 +75,9 @@
     <!-- The color of the material notification background when dimmed -->
     <color name="notification_material_background_dimmed_color">#ccffffff</color>
 
+    <!-- The color of the material notification background when dark -->
+    <color name="notification_material_background_dark_color">#ff333333</color>
+
     <!-- The color of the material notification background when low priority -->
     <color name="notification_material_background_low_priority_color">#fff5f5f5</color>
 
@@ -100,6 +103,10 @@
     <color name="notification_guts_icon_tint">#8a000000</color>
     <color name="notification_guts_disabled_icon_tint">#4d000000</color>
 
+    <!-- Colors of the snooze menu reached via snooze icon behind a notification -->
+    <color name="snooze_snackbar_bg">#FF4A4A4A</color>
+	<color name="snooze_snackbar_text">#FFA6BAFF</color>
+
     <color name="assist_orb_color">#ffffff</color>
 
     <color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ac86439..b7647cf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -286,12 +286,12 @@
     <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
 
     <!-- Nav bar button default ordering/layout -->
-    <string name="config_navBarLayout" translatable="false">space,back;home;recent,menu_ime</string>
+    <string name="config_navBarLayout" translatable="false">left,back;home;recent,right</string>
 
     <bool name="quick_settings_show_full_alarm">false</bool>
 
     <!-- Whether to show a warning notification when the device reaches a certain temperature. -->
-    <bool name="config_showTemperatureWarning">false</bool>
+    <integer name="config_showTemperatureWarning">0</integer>
 
     <!-- Temp at which to show a warning notification if config_showTemperatureWarning is true.
          If < 0, uses the value from HardwarePropertiesManager#getDeviceTemperatures. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79529a7..ddcc4ba 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -106,11 +106,20 @@
     <!-- Minimum layouted height of a notification in the statusbar-->
     <dimen name="min_notification_layout_height">48dp</dimen>
 
-    <!-- Width of the space containing the gear icon behind a notification -->
-    <dimen name="notification_gear_width">64dp</dimen>
+    <!-- Size of the space to place a notification menu item -->
+    <dimen name="notification_menu_icon_size">64dp</dimen>
 
-    <!-- The space around the gear icon displayed behind a notification  -->
-    <dimen name="notification_gear_padding">20dp</dimen>
+    <!-- The space around a notification menu item  -->
+    <dimen name="notification_menu_icon_padding">20dp</dimen>
+
+    <!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
+    <dimen name="snooze_snackbar_min_height">48dp</dimen>
+
+    <!-- The text size of options in the snooze menu. -->
+    <dimen name="snooze_option_text_size">14sp</dimen>
+
+    <!-- The padding around options int the snooze menu. -->
+    <dimen name="snooze_option_padding">8dp</dimen>
 
     <!-- size at which Notification icons will be drawn in the status bar -->
     <dimen name="status_bar_icon_drawing_size">17dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 4a19dde..fc0c82c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -58,7 +58,6 @@
     <item type="id" name="transformation_start_y_tag"/>
     <item type="id" name="transformation_start_scale_x_tag"/>
     <item type="id" name="transformation_start_scale_y_tag"/>
-    <item type="id" name="custom_background_color"/>
 
     <!-- Whether the icon is from a notification for which targetSdk < L -->
     <item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 422431e..ef08121 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1375,8 +1375,26 @@
     <!-- Notification: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
     <string name="notification_done">Done</string>
 
-    <!-- Notification: Gear: Content description for the gear. [CHAR LIMIT=NONE] -->
-    <string name="notification_gear_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> notification controls</string>
+    <!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
+    <string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
+
+    <!-- Notification: Menu row: Content description for the gear menu item. [CHAR LIMIT=NONE] -->
+    <string name="notification_menu_gear_description">notification controls</string>
+
+    <!-- Notification: Menu row: Content description for the snooze icon. [CHAR LIMIT=NONE] -->
+    <string name="notification_menu_snooze_description">notification snooze options</string>
+
+    <!-- Notification: Menu row: Snooze options: 15 minute option. [CHAR LIMIT=50]-->
+    <string name="snooze_option_15_min">15 minutes</string>
+    <!-- Notification: Menu row: Snooze options: 30 minute option. [CHAR LIMIT=50]-->
+    <string name="snooze_option_30_min">30 minutes</string>
+    <!-- Notification: Menu row: Snooze options: 1 hour option. [CHAR LIMIT=50]-->
+    <string name="snooze_option_1_hour">1 hour</string>
+    <!-- Notification: Menu row: Snooze undo button label. [CHAR LIMIT=50]-->
+    <string name="snooze_undo">UNDO</string>
+
+    <!-- Notification: Menu row: Snooze: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
+    <string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
 
     <!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
     <string name="battery_panel_title">Battery usage</string>
@@ -1523,56 +1541,71 @@
     <!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
     <string name="nav_bar">Navigation bar</string>
 
-    <!-- SysUI Tuner: Group of buttons that show on the start of the screen [CHAR LIMIT=30] -->
-    <string name="start">Start</string>
-    <!-- SysUI Tuner: Group of buttons that show on the center of the screen [CHAR LIMIT=30] -->
-    <string name="center">Center</string>
-    <!-- SysUI Tuner: Group of buttons that show on the end of the screen [CHAR LIMIT=30] -->
-    <string name="end">End</string>
-    <!-- SysUI Tuner: Name of space used in custom navigation bar layouts [CHAR LIMIT=30] -->
-    <string name="space">Spacer</string>
+    <!-- SysUI Tuner: Button that controls layout of navigation bar [CHAR LIMIT=60] -->
+    <string name="nav_bar_layout">Layout</string>
+
+    <!-- SysUI Tuner: Label for section of settings about the left nav button [CHAR LIMIT=60] -->
+    <string name="nav_bar_left">Left</string>
+
+    <!-- SysUI Tuner: Label for section of settings about the right nav button [CHAR LIMIT=60] -->
+    <string name="nav_bar_right">Right</string>
+
+    <!-- SysUI Tuner: Setting for button type in nav bar [CHAR LIMIT=60] -->
+    <string name="nav_bar_button_type">Button type</string>
+
+    <!-- SysUI Tuner: Added to nav bar option to indicate it is the default [CHAR LIMIT=60] -->
+    <string name="nav_bar_default"> (default)</string>
+
+    <!-- SysUI Tuner: Labels for different types of navigation bar buttons [CHAR LIMIT=60] -->
+    <string-array name="nav_bar_buttons">
+        <item>Clipboard</item>
+        <item>Keycode</item>
+        <item>Menu / Keyboard Switcher</item>
+        <item>None</item>
+    </string-array>
+    <string-array name="nav_bar_button_values" translatable="false">
+        <item>clipboard</item>
+        <item>key</item>
+        <item>menu_ime</item>
+        <item>space</item>
+    </string-array>
+
+    <!-- SysUI Tuner: Labels for different types of navigation bar layouts [CHAR LIMIT=60] -->
+    <string-array name="nav_bar_layouts">
+        <item>Divided (default)</item>
+        <item>Centered</item>
+        <item>Left-aligned</item>
+        <item>Right-aligned</item>
+    </string-array>
+
+    <string-array name="nav_bar_layouts_values" translatable="false">
+        <item>default</item>
+        <item>left;back,home,recent;right</item>
+        <item>left,back,home,recent,right;space;space</item>
+        <item>space;space;left,back,home,recent,right</item>
+    </string-array>
+
     <!-- SysUI Tuner: Name of Combination Menu / Keyboard Switcher button [CHAR LIMIT=30] -->
     <string name="menu_ime">Menu / Keyboard Switcher</string>
-    <!-- SysUI Tuner: Title for dialog to add a button [CHAR LIMIT=30] -->
-    <string name="select_button">Select button to add</string>
-    <!-- SysUI Tuner: Button to add a button [CHAR LIMIT=30] -->
-    <string name="add_button">Add button</string>
     <!-- SysUI Tuner: Save the current settings [CHAR LIMIT=30] -->
     <string name="save">Save</string>
     <!-- SysUI Tuner: Reset to default settings [CHAR LIMIT=30] -->
     <string name="reset">Reset</string>
 
-    <!-- SysUI Tuner: Title of no home warning dialog [CHAR LIMIT=30] -->
-    <string name="no_home_title">No home button found</string>
-    <!-- SysUI Tuner: Message of no home warning dialog [CHAR LIMIT=NONE] -->
-    <string name="no_home_message">A home button is required to be able to navigate this device. Please add a home button before saving.</string>
-
     <!-- SysUI Tuner: Adjust button width dialog title [CHAR LIMIT=60] -->
     <string name="adjust_button_width">Adjust button width</string>
 
     <!-- SysUI Tuner: Nav bar button that holds the clipboard [CHAR LIMIT=30] -->
     <string name="clipboard">Clipboard</string>
 
-    <!-- SysUI Tuner: Description of nav bar button that holds the clipboard [CHAR LIMIT=NONE] -->
-    <string name="clipboard_description">The Clipboard allows items to be dragged directly to the clipboard. Items can also be dragged directly out of the clipboard when present.</string>
-
     <!-- SysUI Tuner: Accessibility description for custom nav key [CHAR LIMIT=NONE] -->
     <string name="accessibility_key">Custom navigation button</string>
 
     <!-- SysUI Tuner: Nav bar button that emulates a keycode [CHAR LIMIT=30] -->
     <string name="keycode">Keycode</string>
 
-    <!-- SysUI Tuner: Description of nav bar button that emulates a keycode [CHAR LIMIT=NONE] -->
-    <string name="keycode_description">Keycode buttons allow keyboard keys to
-        be added to the Navigation Bar. When pressed they emulate the selected
-        keyboard key. First the key must be selected for the button, followed
-        by an image to be shown on the button.</string>
-
-    <!-- SysUI Tuner: Title of dialog to select which key to emulate [CHAR LIMIT=60] -->
-    <string name="select_keycode">Select Keyboard Button</string>
-
-    <!-- SysUI Tuner: Label for preview area in navigation bar tuner [CHAR LIMIT=NONE] -->
-    <string name="preview">Preview</string>
+    <!-- SysUI Tuner: Settings to change nav bar icon [CHAR LIMIT=30] -->
+    <string name="icon">Icon</string>
 
     <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
     <string name="drag_to_add_tiles">Drag to add tiles</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6535a81..c5a5518 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -383,6 +383,20 @@
         <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
 
+    <style name="TextAppearance.SnoozeSnackBar">
+        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">roboto-regular</item>
+        <item name="android:textColor">@android:color/white</item>
+    </style>
+
+    <style name="TextAppearance.SnoozeSnackBar.Button">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">@color/snooze_snackbar_text</item>
+    </style>
+
     <style name="edit_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
         <item name="android:colorBackground">?android:attr/colorSecondary</item>
     </style>
diff --git a/packages/SystemUI/res/xml/nav_bar_tuner.xml b/packages/SystemUI/res/xml/nav_bar_tuner.xml
new file mode 100644
index 0000000..6fa8bec
--- /dev/null
+++ b/packages/SystemUI/res/xml/nav_bar_tuner.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:title="@string/nav_bar">
+
+    <ListPreference
+        android:key="layout"
+        android:title="@string/nav_bar_layout"
+        android:summary="%s"
+        android:persistent="false"
+        android:entries="@array/nav_bar_layouts"
+        android:entryValues="@array/nav_bar_layouts_values" />
+
+    <PreferenceCategory
+        android:key="left"
+        android:title="@string/nav_bar_left">
+
+        <DropDownPreference
+            android:key="type_left"
+            android:title="@string/nav_bar_button_type"
+            android:persistent="false"
+            android:summary="%s"
+            android:entries="@array/nav_bar_buttons"
+            android:entryValues="@array/nav_bar_button_values" />
+
+        <Preference
+            android:key="keycode_left"
+            android:persistent="false"
+            android:title="@string/keycode" />
+
+        <com.android.systemui.tuner.BetterListPreference
+            android:key="icon_left"
+            android:persistent="false"
+            android:summary="%s"
+            android:title="@string/icon" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="right"
+        android:title="@string/nav_bar_right">
+
+        <DropDownPreference
+            android:key="type_right"
+            android:title="@string/nav_bar_button_type"
+            android:summary="%s"
+            android:persistent="false"
+            android:entries="@array/nav_bar_buttons"
+            android:entryValues="@array/nav_bar_button_values" />
+
+        <Preference
+            android:key="keycode_right"
+            android:persistent="false"
+            android:title="@string/keycode" />
+
+        <com.android.systemui.tuner.BetterListPreference
+            android:key="icon_right"
+            android:persistent="false"
+            android:summary="%s"
+            android:title="@string/icon" />
+
+    </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/other_settings.xml b/packages/SystemUI/res/xml/other_settings.xml
index 18cb930..7719d5e 100644
--- a/packages/SystemUI/res/xml/other_settings.xml
+++ b/packages/SystemUI/res/xml/other_settings.xml
@@ -23,10 +23,5 @@
             android:key="power_notification_controls"
             android:title="@string/tuner_full_importance_settings"
             android:fragment="com.android.systemui.tuner.PowerNotificationControlsFragment"/>
-e
-    <com.android.systemui.tuner.ThemePreference
-        android:key="theme"
-        android:title="@string/theme"
-        android:summary="%s" />
 
 </PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 59a10da..3c6315b 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -150,12 +150,10 @@
 
     </PreferenceScreen>
 
-    <!--
     <Preference
         android:key="nav_bar"
         android:title="@string/nav_bar"
         android:fragment="com.android.systemui.tuner.NavBarTuner" />
-    -->
 
     <Preference
             android:key="other"
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 5c50b5c..a95713f 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -32,7 +32,9 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
 import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.NotificationMenuRow;
 
 import java.util.HashMap;
 
@@ -85,10 +87,12 @@
     private int mFalsingThreshold;
     private boolean mTouchAboveFalsingThreshold;
     private boolean mDisableHwLayers;
+    private Context mContext;
 
     private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
 
     public SwipeHelper(int swipeDirection, Callback callback, Context context) {
+        mContext = context;
         mCallback = callback;
         mHandler = new Handler();
         mSwipeDirection = swipeDirection;
@@ -249,6 +253,7 @@
         }
     }
 
+    @Override
     public boolean onInterceptTouchEvent(final MotionEvent ev) {
         final int action = ev.getAction();
 
@@ -280,7 +285,8 @@
                                         mCurrView.getLocationOnScreen(mTmpPos);
                                         final int x = (int) ev.getRawX() - mTmpPos[0];
                                         final int y = (int) ev.getRawY() - mTmpPos[1];
-                                        mLongPressListener.onLongPress(mCurrView, x, y);
+                                        mLongPressListener.onLongPress(mCurrView, x, y,
+                                                NotificationMenuRow.getLongpressMenuItem(mContext));
                                     }
                                 }
                             };
@@ -379,6 +385,7 @@
             animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         }
         AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
             }
@@ -401,10 +408,12 @@
         anim.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
+            @Override
             public void onAnimationCancel(Animator animation) {
                 mCancelled = true;
             }
 
+            @Override
             public void onAnimationEnd(Animator animation) {
                 updateSwipeProgressFromOffset(animView, canBeDismissed);
                 mDismissPendingMap.remove(animView);
@@ -435,6 +444,7 @@
     public void snapChild(final View animView, final float targetLeft, float velocity) {
         final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
         AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
             }
@@ -447,6 +457,7 @@
         int duration = SNAP_ANIM_LEN;
         anim.setDuration(duration);
         anim.addListener(new AnimatorListenerAdapter() {
+            @Override
             public void onAnimationEnd(Animator animator) {
                 mSnappingChild = false;
                 updateSwipeProgressFromOffset(animView, canBeDismissed);
@@ -522,6 +533,7 @@
         }
     }
 
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (mLongPressSent) {
             return true;
@@ -690,6 +702,6 @@
          * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
          * @return whether the longpress was handled
          */
-        boolean onLongPress(View v, int x, int y);
+        boolean onLongPress(View v, int x, int y, MenuItem item);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 228996a..ec11812 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -30,9 +30,11 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -115,6 +117,11 @@
         return new NotificationIconAreaController(context, phoneStatusBar);
     }
 
+    public KeyguardIndicationController createKeyguardIndicationController(Context context,
+            ViewGroup indicationArea, LockIcon lockIcon) {
+        return new KeyguardIndicationController(context, indicationArea, lockIcon);
+    }
+
     public QSTileHost createQSTileHost(Context context, PhoneStatusBar statusBar,
             StatusBarIconController iconController) {
         return new QSTileHost(context, statusBar, iconController);
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 3103267..3df557d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -86,9 +86,12 @@
             // another package than the top activity in the stack
             boolean expandPipToFullscreen = true;
             if (sourceComponent != null) {
-                ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager);
-                expandPipToFullscreen = topActivity != null && topActivity.getPackageName().equals(
-                        sourceComponent.getPackageName());
+                ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+                        mActivityManager);
+                if (topActivity != null && topActivity.getPackageName().equals(
+                        sourceComponent.getPackageName())) {
+                    expandPipToFullscreen = false;
+                }
             }
             if (expandPipToFullscreen) {
                 mTouchHandler.expandPinnedStackToFullscreen();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
index 2284013..d96baa6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
@@ -148,7 +148,8 @@
      */
     private void resolveActiveMediaController(List<MediaController> controllers) {
         if (controllers != null) {
-            final ComponentName topActivity = PipUtils.getTopPinnedActivity(mActivityManager);
+            final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
+                    mActivityManager);
             if (topActivity != null) {
                 for (int i = 0; i < controllers.size(); i++) {
                     final MediaController controller = controllers.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
index 9c03830..a8cdd1b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
@@ -21,6 +21,7 @@
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -29,14 +30,23 @@
     private static final String TAG = "PipUtils";
 
     /**
-     * @return the ComponentName of the top activity in the pinned stack, or null if none exists.
+     * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if
+     *         none exists.
      */
-    public static ComponentName getTopPinnedActivity(IActivityManager activityManager) {
+    public static ComponentName getTopPinnedActivity(Context context,
+            IActivityManager activityManager) {
         try {
-            StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+            final String sysUiPackageName = context.getPackageName();
+            final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
-                return pinnedStackInfo.topActivity;
+                for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
+                    ComponentName cn = ComponentName.unflattenFromString(
+                            pinnedStackInfo.taskNames[i]);
+                    if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
+                        return cn;
+                    }
+                }
             }
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to get pinned stack.");
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 56947e5..964fefa 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -377,16 +377,18 @@
                 mCurrentPipBounds = mPipBounds;
                 break;
         }
-        try {
-            int animationDurationMs = -1;
-            if (wasRecentsShown
-                    && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
-                animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
+        if (mCurrentPipBounds != null) {
+            try {
+                int animationDurationMs = -1;
+                if (wasRecentsShown
+                        && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
+                    animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
+                }
+                mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+                        true, true, true, animationDurationMs);
+            } catch (RemoteException e) {
+                Log.e(TAG, "resizeStack failed", e);
             }
-            mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
-                    true, true, true, animationDurationMs);
-        } catch (RemoteException e) {
-            Log.e(TAG, "resizeStack failed", e);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 71dda2d..2fe9e77 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -217,8 +217,16 @@
             return;
         }
         mTempWarning = false;
-        mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP,
-                UserHandle.ALL);
+        dismissTemperatureWarningInternal();
+    }
+
+    /**
+     * Internal only version of {@link #dismissTemperatureWarning()} that simply dismisses
+     * the notification. As such, the notification will not show again until
+     * {@link #dismissTemperatureWarning()} is called.
+     */
+    private void dismissTemperatureWarningInternal() {
+        mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, UserHandle.ALL);
     }
 
     @Override
@@ -390,10 +398,10 @@
             } else if (action.equals(ACTION_DISMISSED_WARNING)) {
                 dismissLowBatteryWarning();
             } else if (ACTION_CLICKED_TEMP_WARNING.equals(action)) {
-                dismissTemperatureWarning();
+                dismissTemperatureWarningInternal();
                 showTemperatureDialog();
             } else if (ACTION_DISMISSED_TEMP_WARNING.equals(action)) {
-                dismissTemperatureWarning();
+                dismissTemperatureWarningInternal();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 28ca6a3..1d4a5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.BatteryManager;
 import android.os.Handler;
@@ -221,11 +222,15 @@
     };
 
     private void initTemperatureWarning() {
-        if (!mContext.getResources().getBoolean(R.bool.config_showTemperatureWarning)) {
+        ContentResolver resolver = mContext.getContentResolver();
+        Resources resources = mContext.getResources();
+        if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
+                resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
             return;
         }
 
-        mThrottlingTemp = mContext.getResources().getInteger(R.integer.config_warningTemperature);
+        mThrottlingTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
+                resources.getInteger(R.integer.config_warningTemperature));
 
         if (mThrottlingTemp < 0f) {
             // Get the throttling temperature. No need to check if we're not throttling.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 0bf3f15..1835afd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -60,7 +60,7 @@
 
     private AlertDialog mDialog;
     private QSTileHost mHost;
-    protected Handler mHandler;
+    protected H mHandler;
 
     private boolean mIsVisible;
     private boolean mIsIconVisible;
@@ -83,7 +83,7 @@
         mMainHandler = new Handler(Looper.getMainLooper());
         mActivityStarter = Dependency.get(ActivityStarter.class);
         mSecurityController = Dependency.get(SecurityController.class);
-        mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
+        mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
     }
 
     public void setHostEnvironment(QSTileHost host) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 0265c9e..8d18a75 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -438,7 +438,7 @@
                 ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
         if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
             logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
-            if (runningTask.isDockable) {
+            if (runningTask.supportsSplitScreenMultiWindow) {
                 if (metricsDockAction != -1) {
                     MetricsLogger.action(mContext, metricsDockAction,
                             runningTask.topActivity.flattenToShortString());
@@ -486,7 +486,6 @@
             case ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE:
                 return COUNTER_WINDOW_UNSUPPORTED;
             case ActivityInfo.RESIZE_MODE_RESIZEABLE:
-            case ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE:
             case ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION:
                 return COUNTER_WINDOW_SUPPORTED;
             default:
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 5c7496d..11b5984 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -197,7 +197,7 @@
             Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
                     thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
                     activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
-                    t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
+                    t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
                     isLocked);
 
             allTasks.add(task);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 0777163..b318ea7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -462,7 +462,6 @@
                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
                 mLightDismissDrawable : mDarkDismissDrawable);
-        mDismissButton.setContentDescription(t.dismissDescription);
         mDismissButton.setOnClickListener(this);
         mDismissButton.setClickable(false);
         ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
@@ -499,7 +498,6 @@
 
         // In accessibility, a single click on the focused app info button will show it
         if (touchExplorationEnabled) {
-            mIconView.setContentDescription(t.appInfoDescription);
             mIconView.setOnClickListener(this);
             mIconView.setClickable(true);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index b5358bf..b9ed725 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -463,6 +463,7 @@
         }
         mDark = dark;
         updateBackground();
+        updateBackgroundTint(fade);
         if (!dark && fade && !shouldHideBackground()) {
             fadeInFromDark(delay);
         }
@@ -700,8 +701,8 @@
     protected void updateBackground() {
         cancelFadeAnimations();
         if (shouldHideBackground()) {
-            mBackgroundDimmed.setVisibility(View.INVISIBLE);
-            mBackgroundNormal.setVisibility(View.INVISIBLE);
+            mBackgroundDimmed.setVisibility(INVISIBLE);
+            mBackgroundNormal.setVisibility(mActivated ? VISIBLE : INVISIBLE);
         } else if (mDimmed) {
             // When groups are animating to the expanded state from the lockscreen, show the
             // normal background instead of the dimmed background
@@ -940,6 +941,9 @@
      * @return the calculated background color
      */
     private int calculateBgColor(boolean withTint, boolean withOverRide) {
+        if (mDark) {
+            return getContext().getColor(R.color.notification_material_background_dark_color);
+        }
         if (withOverRide && mOverrideTint != NO_COLOR) {
             int defaultTint = calculateBgColor(withTint, false);
             return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 6ac5cb8..f0de696 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -24,6 +24,7 @@
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
@@ -41,6 +42,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -51,6 +53,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -98,6 +101,10 @@
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.SystemUI;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -244,6 +251,7 @@
 
     // which notification is currently being longpress-examined by the user
     private NotificationGuts mNotificationGutsExposed;
+    private MenuItem mGutsMenuItem;
 
     private KeyboardShortcuts mKeyboardShortcuts;
 
@@ -316,9 +324,16 @@
     };
 
     private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+        private final int[] mTmpInt2 = new int[2];
+
         @Override
         public boolean onClickHandler(
                 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+            view.getLocationInWindow(mTmpInt2);
+            wakeUpIfDozing(SystemClock.uptimeMillis(), new PointF(
+                    mTmpInt2[0] + view.getWidth() / 2, mTmpInt2[1] + view.getHeight() / 2));
+
+
             if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
                 return true;
             }
@@ -692,6 +707,7 @@
         }
     }
 
+    @Override
     public void start() {
         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -957,7 +973,7 @@
             entry.row.reInflateViews();
             if (exposedGuts) {
                 mNotificationGutsExposed = entry.row.getGuts();
-                bindGuts(entry.row);
+                bindGuts(entry.row, mGutsMenuItem);
             }
             inflateViews(entry, mStackScroller);
         }
@@ -1022,6 +1038,7 @@
             @Override
             public boolean onDismiss() {
                 AsyncTask.execute(new Runnable() {
+                    @Override
                     public void run() {
                         TaskStackBuilder.create(mContext)
                                 .addNextIntentWithParentStack(intent)
@@ -1035,10 +1052,18 @@
         }, false /* afterKeyguardGone */);
     }
 
-    private void bindGuts(final ExpandableNotificationRow row) {
+    protected void setNotificationSnoozed(StatusBarNotification sbn, long snoozeUntil) {
+        mNotificationListener.snoozeNotification(sbn.getKey(), snoozeUntil);
+    }
+
+    public SnoozeListener getSnoozeListener() {
+        return null;
+    }
+
+    private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
         row.inflateGuts();
+        row.setGutsView(item);
         final StatusBarNotification sbn = row.getStatusBarNotification();
-        PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
         row.setTag(sbn.getPackageName());
         final NotificationGuts guts = row.getGuts();
         guts.setClosedListener((NotificationGuts g) -> {
@@ -1048,43 +1073,53 @@
             mNotificationGutsExposed = null;
         });
 
-        final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        if (item.gutsContent instanceof SnoozeGutsContent) {
+            ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
+            ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
+        }
 
-        final String pkg = sbn.getPackageName();
-        final NotificationGuts.OnSettingsClickListener onSettingsClick =
-                (View v, int appUid) -> {
-                    MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
-                    guts.resetFalsingCheck();
-                    startAppNotificationSettingsActivity(pkg, appUid);
-                };
-        final View.OnClickListener onDoneClick =
-                (View v) -> {
-                    // If the user has security enabled, show challenge if the setting is changed.
-                    if (guts.hasImportanceChanged()
-                                && isLockscreenPublicMode(sbn.getUser().getIdentifier())
-                                && (mState == StatusBarState.KEYGUARD
-                                        || mState == StatusBarState.SHADE_LOCKED)) {
-                        OnDismissAction dismissAction = new OnDismissAction() {
-                            @Override
-                            public boolean onDismiss() {
-                                closeControls(row, guts, v);
-                                return true;
-                            }
-                        };
-                        onLockedNotificationImportanceChange(dismissAction);
-                    } else {
-                        closeControls(row, guts, v);
-                    }
-                };
-        guts.bindNotification(pmUser, iNotificationManager, sbn, onSettingsClick, onDoneClick,
-                mNonBlockablePkgs);
+        if (item.gutsContent instanceof NotificationInfo) {
+            final NotificationChannel channel = row.getEntry().channel;
+            PackageManager pmUser = getPackageManagerForUser(mContext,
+                    sbn.getUser().getIdentifier());
+            final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+            final String pkg = sbn.getPackageName();
+            NotificationInfo info = (NotificationInfo) item.gutsContent;
+            final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
+                    int appUid) -> {
+                MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
+                guts.resetFalsingCheck();
+                startAppNotificationSettingsActivity(pkg, appUid);
+            };
+            final View.OnClickListener onDoneClick = (View v) -> {
+                // If the user has security enabled, show challenge if the setting is changed.
+                if (info.hasImportanceChanged()
+                        && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+                        && (mState == StatusBarState.KEYGUARD
+                                || mState == StatusBarState.SHADE_LOCKED)) {
+                    OnDismissAction dismissAction = new OnDismissAction() {
+                        @Override
+                        public boolean onDismiss() {
+                            saveAndCloseNotificationMenu(info, row, guts, v);
+                            return true;
+                        }
+                    };
+                    onLockedNotificationImportanceChange(dismissAction);
+                } else {
+                    saveAndCloseNotificationMenu(info, row, guts, v);
+                }
+            };
+            info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick,
+                    onDoneClick,
+                    mNonBlockablePkgs);
+        }
     }
 
-    private void closeControls(
+    private void saveAndCloseNotificationMenu(NotificationInfo info,
             ExpandableNotificationRow row, NotificationGuts guts, View done) {
         guts.resetFalsingCheck();
-
+        info.saveImportance();
         int[] rowLocation = new int[2];
         int[] doneLocation = new int[2];
         row.getLocationOnScreen(rowLocation);
@@ -1100,7 +1135,8 @@
     protected SwipeHelper.LongPressListener getNotificationLongClicker() {
         return new SwipeHelper.LongPressListener() {
             @Override
-            public boolean onLongPress(View v, final int x, final int y) {
+            public boolean onLongPress(View v, final int x, final int y,
+                    MenuItem item) {
                 if (!(v instanceof ExpandableNotificationRow)) {
                     return false;
                 }
@@ -1110,10 +1146,10 @@
                 }
 
                 final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-                bindGuts(row);
+                bindGuts(row, item);
+                NotificationGuts guts = row.getGuts();
 
                 // Assume we are a status_bar_notification_row
-                final NotificationGuts guts = row.getGuts();
                 if (guts == null) {
                     // This view has no guts. Examples are the more card or the dismiss all view
                     return false;
@@ -1131,6 +1167,7 @@
                 guts.setVisibility(View.INVISIBLE);
                 // Post to ensure the the guts are properly laid out.
                 guts.post(new Runnable() {
+                    @Override
                     public void run() {
                         if (row.getWindowToken() == null) {
                             Log.e(TAG, "Trying to show notification guts, but not attached to "
@@ -1161,6 +1198,7 @@
                         row.closeRemoteInput();
                         mStackScroller.onHeightChanged(row, true /* needsAnimation */);
                         mNotificationGutsExposed = guts;
+                        mGutsMenuItem = item;
                     }
                 });
                 return true;
@@ -1472,6 +1510,7 @@
     }
 
     protected class H extends Handler {
+        @Override
         public void handleMessage(Message m) {
             switch (m.what) {
              case MSG_SHOW_RECENT_APPS:
@@ -1513,8 +1552,9 @@
                 entry.notification.getUser().getIdentifier());
 
         final StatusBarNotification sbn = entry.notification;
+        boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
         try {
-            entry.cacheContentViews(mContext, null);
+            entry.cacheContentViews(mContext, null, isLowPriority);
         } catch (RuntimeException e) {
             Log.e(TAG, "Unable to get notification remote views", e);
             return false;
@@ -1586,6 +1626,7 @@
 
         workAroundBadLayerDrawableOpacity(row);
         bindDismissRunnable(row);
+        row.setIsLowPriority(isLowPriority);
 
         // NB: the large icon is now handled entirely by the template
 
@@ -1741,6 +1782,7 @@
                 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                 mCurrentUserId);
         dismissKeyguardThenExecute(new OnDismissAction() {
+            @Override
             public boolean onDismiss() {
                 new Thread() {
                     @Override
@@ -1785,13 +1827,23 @@
         return false;
     }
 
+    public void wakeUpIfDozing(long time, PointF where) {
+    }
+
     private final class NotificationClicker implements View.OnClickListener {
+        private final int[] mTmpInt2 = new int[2];
+
+        @Override
         public void onClick(final View v) {
             if (!(v instanceof ExpandableNotificationRow)) {
                 Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
                 return;
             }
 
+            v.getLocationInWindow(mTmpInt2);
+            wakeUpIfDozing(SystemClock.uptimeMillis(),
+                    new PointF(mTmpInt2[0] + v.getWidth() / 2, mTmpInt2[1] + v.getHeight() / 2));
+
             final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
             final StatusBarNotification sbn = row.getStatusBarNotification();
             if (sbn == null) {
@@ -1825,6 +1877,7 @@
                     && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                             mCurrentUserId);
             dismissKeyguardThenExecute(new OnDismissAction() {
+                @Override
                 public boolean onDismiss() {
                     if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
                         // Release the HUN notification to the shade.
@@ -2263,7 +2316,8 @@
 
         boolean applyInPlace;
         try {
-            applyInPlace = entry.cacheContentViews(mContext, notification.getNotification());
+            applyInPlace = entry.cacheContentViews(mContext, notification.getNotification(),
+                    mNotificationData.isAmbient(key));
         } catch (RuntimeException e) {
             Log.e(TAG, "Unable to get notification remote views", e);
             applyInPlace = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 93c48f8..f56d29d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -22,6 +22,7 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -50,7 +51,9 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -118,7 +121,7 @@
     private int mNotificationColor;
     private ExpansionLogger mLogger;
     private String mLoggingKey;
-    private NotificationSettingsIconRow mSettingsIconRow;
+    private NotificationMenuRow mMenuRow;
     private NotificationGuts mGuts;
     private NotificationData.Entry mEntry;
     private StatusBarNotification mStatusBarNotification;
@@ -130,7 +133,7 @@
     private boolean mChildrenExpanded;
     private boolean mIsSummaryWithChildren;
     private NotificationChildrenContainer mChildrenContainer;
-    private ViewStub mSettingsIconRowStub;
+    private ViewStub mMenuRowStub;
     private ViewStub mGutsStub;
     private boolean mIsSystemChildExpanded;
     private boolean mIsPinned;
@@ -201,7 +204,9 @@
     private boolean mShowAmbient;
     private boolean mIsLastChild;
     private Runnable mOnDismissRunnable;
+    private boolean mIsLowPriority;
 
+    @Override
     public boolean isGroupExpansionChanging() {
         if (isChildInGroup()) {
             return mNotificationParent.isGroupExpansionChanging();
@@ -309,6 +314,18 @@
         mPublicLayout.updateExpandButtons(true);
         updateLimits();
         updateIconVisibilities();
+        updateShelfIconColor();
+    }
+
+    private void updateShelfIconColor() {
+        StatusBarIconView expandedIcon = mEntry.expandedIcon;
+        boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
+        boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
+                NotificationColorUtil.getInstance(mContext));
+        if (colorize) {
+            int color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded());
+            expandedIcon.setImageTintList(ColorStateList.valueOf(color));
+        }
     }
 
     private void updateLimits() {
@@ -371,8 +388,8 @@
 
     public void setAppName(String appName) {
         mAppName = appName;
-        if (mSettingsIconRow != null) {
-            mSettingsIconRow.setAppName(mAppName);
+        if (mMenuRow != null) {
+            mMenuRow.setAppName(mAppName);
         }
     }
 
@@ -403,6 +420,7 @@
         row.setIsChildInGroup(false, null);
     }
 
+    @Override
     public boolean isChildInGroup() {
         return mNotificationParent != null;
     }
@@ -440,7 +458,7 @@
 
     @Override
     protected boolean handleSlideBack() {
-        if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) {
+        if (mMenuRow != null && mMenuRow.isVisible()) {
             animateTranslateNotification(0 /* targetLeft */);
             return true;
         }
@@ -664,6 +682,13 @@
         mHeadsUpManager = headsUpManager;
     }
 
+    public void setGutsView(MenuItem item) {
+        if (mGuts != null) {
+            item.gutsContent.setInteractionListener(mGuts);
+            mGuts.setGutsContent(item.gutsContent);
+        }
+    }
+
     public void reInflateViews() {
         initDimens();
         if (mIsSummaryWithChildren) {
@@ -680,16 +705,16 @@
             mGuts.setVisibility(oldGuts.getVisibility());
             addView(mGuts, index);
         }
-        if (mSettingsIconRow != null) {
-            View oldSettings = mSettingsIconRow;
-            int settingsIndex = indexOfChild(oldSettings);
-            removeView(oldSettings);
-            mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate(
-                    R.layout.notification_settings_icon_row, this, false);
-            mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
-            mSettingsIconRow.setAppName(mAppName);
-            mSettingsIconRow.setVisibility(oldSettings.getVisibility());
-            addView(mSettingsIconRow, settingsIndex);
+        if (mMenuRow != null) {
+            View oldMenu = mMenuRow;
+            int menuIndex = indexOfChild(oldMenu);
+            removeView(oldMenu);
+            mMenuRow = (NotificationMenuRow) LayoutInflater.from(mContext).inflate(
+                    R.layout.notification_menu_row, this, false);
+            mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+            mMenuRow.setAppName(mAppName);
+            mMenuRow.setVisibility(oldMenu.getVisibility());
+            addView(mMenuRow, menuIndex);
 
         }
         for (NotificationContentView l : mLayouts) {
@@ -943,6 +968,14 @@
         return mPrivateLayout.getTranslationY();
     }
 
+    public void setIsLowPriority(boolean isLowPriority) {
+        mIsLowPriority = isLowPriority;
+        mPrivateLayout.setIsLowPriority(isLowPriority);
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setIsLowPriority(isLowPriority);
+        }
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
@@ -1010,21 +1043,19 @@
         super.onFinishInflate();
         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
-
         mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
 
         for (NotificationContentView l : mLayouts) {
             l.setExpandClickListener(mExpandClickListener);
             l.setContainingNotification(this);
         }
-
-        mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub);
-        mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+        mMenuRowStub = (ViewStub) findViewById(R.id.menu_row_stub);
+        mMenuRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
             @Override
             public void onInflate(ViewStub stub, View inflated) {
-                mSettingsIconRow = (NotificationSettingsIconRow) inflated;
-                mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
-                mSettingsIconRow.setAppName(mAppName);
+                mMenuRow = (NotificationMenuRow) inflated;
+                mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
+                mMenuRow.setAppName(mAppName);
             }
         });
         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
@@ -1043,6 +1074,7 @@
             @Override
             public void onInflate(ViewStub stub, View inflated) {
                 mChildrenContainer = (NotificationChildrenContainer) inflated;
+                mChildrenContainer.setIsLowPriority(mIsLowPriority);
                 mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
                 mChildrenContainer.onNotificationUpdated();
                 mTranslateableViews.add(mChildrenContainer);
@@ -1055,7 +1087,7 @@
             mTranslateableViews.add(getChildAt(i));
         }
         // Remove views that don't translate
-        mTranslateableViews.remove(mSettingsIconRowStub);
+        mTranslateableViews.remove(mMenuRowStub);
         mTranslateableViews.remove(mChildrenContainerStub);
         mTranslateableViews.remove(mGutsStub);
     }
@@ -1070,8 +1102,8 @@
             }
         }
         invalidateOutline();
-        if (mSettingsIconRow != null) {
-            mSettingsIconRow.resetState();
+        if (mMenuRow != null) {
+            mMenuRow.resetState(true /* notify */);
         }
     }
 
@@ -1098,8 +1130,8 @@
             }
         }
         invalidateOutline();
-        if (mSettingsIconRow != null) {
-            mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth());
+        if (mMenuRow != null) {
+            mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth());
         }
     }
 
@@ -1136,8 +1168,8 @@
 
             @Override
             public void onAnimationEnd(Animator anim) {
-                if (!cancelled && mSettingsIconRow != null && leftTarget == 0) {
-                    mSettingsIconRow.resetState();
+                if (!cancelled && mMenuRow != null && leftTarget == 0) {
+                    mMenuRow.resetState(true /* notify */);
                     mTranslateAnim = null;
                 }
             }
@@ -1147,17 +1179,17 @@
     }
 
     public float getSpaceForGear() {
-        if (mSettingsIconRow != null) {
-            return mSettingsIconRow.getSpaceForGear();
+        if (mMenuRow != null) {
+            return mMenuRow.getSpaceForMenu();
         }
         return 0;
     }
 
-    public NotificationSettingsIconRow getSettingsRow() {
-        if (mSettingsIconRow == null) {
-            mSettingsIconRowStub.inflate();
+    public NotificationMenuRow getSettingsRow() {
+        if (mMenuRow == null) {
+            mMenuRowStub.inflate();
         }
-        return mSettingsIconRow;
+        return mMenuRow;
     }
 
     public void inflateGuts() {
@@ -1242,6 +1274,7 @@
      */
     public void setUserExpanded(boolean userExpanded) {
         setUserExpanded(userExpanded, false /* allowChildExpansion */);
+        updateShelfIconColor();
     }
 
     /**
@@ -1301,6 +1334,7 @@
         if (expand != mIsSystemExpanded) {
             final boolean wasExpanded = isExpanded();
             mIsSystemExpanded = expand;
+            updateShelfIconColor();
             notifyHeightChanged(false /* needsAnimation */);
             logExpansionEvent(false, wasExpanded);
             if (mIsSummaryWithChildren) {
@@ -1378,6 +1412,7 @@
         }
     }
 
+    @Override
     public boolean isGroupExpanded() {
         return mGroupManager.isGroupExpanded(mStatusBarNotification);
     }
@@ -1431,8 +1466,8 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         updateMaxHeights();
-        if (mSettingsIconRow != null) {
-            mSettingsIconRow.updateVerticalLocation();
+        if (mMenuRow != null) {
+            mMenuRow.updateVerticalLocation();
         }
         updateContentShiftHeight();
     }
@@ -1479,6 +1514,7 @@
         mSensitiveHiddenInGeneral = hideSensitive;
     }
 
+    @Override
     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
         mHideSensitiveForIntrinsicHeight = hideSensitive;
         if (mIsSummaryWithChildren) {
@@ -1491,6 +1527,7 @@
         }
     }
 
+    @Override
     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
             long duration) {
         boolean oldShowingPublic = mShowingPublic;
@@ -1555,6 +1592,7 @@
         }
     }
 
+    @Override
     public boolean mustStayOnScreen() {
         return mIsHeadsUp;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 08fd93d..d599ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.UserInfoController;
 
 /**
  * Controls the indications and error messages shown on the Keyguard
@@ -83,6 +84,8 @@
     private int mChargingWattage;
     private String mMessageToShowOnScreenOn;
 
+    private KeyguardUpdateMonitorCallback mUpdateMonitor;
+
     private final DevicePolicyManager mDevicePolicyManager;
 
     public KeyguardIndicationController(Context context, ViewGroup indicationArea,
@@ -106,7 +109,7 @@
         mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
 
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitor);
+        KeyguardUpdateMonitor.getInstance(context).registerCallback(getKeyguardCallback());
         context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM,
                 new IntentFilter(Intent.ACTION_TIME_TICK), null,
                 Dependency.get(Dependency.TIME_TICK_HANDLER));
@@ -114,6 +117,23 @@
         updateDisclosure();
     }
 
+    /**
+     * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
+     * {@link KeyguardIndicationController}.
+     *
+     * <p>Subclasses may override this method to extend or change the callback behavior by extending
+     * the {@link BaseKeyguardCallback}.
+     *
+     * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the
+     * same instance.
+     */
+    protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
+        if (mUpdateMonitor == null) {
+            mUpdateMonitor = new BaseKeyguardCallback();
+        }
+        return mUpdateMonitor;
+    }
+
     private void updateDisclosure() {
         if (mDevicePolicyManager == null) {
             return;
@@ -152,6 +172,12 @@
     }
 
     /**
+     * Sets the active controller managing changes and callbacks to user information.
+     */
+    public void setUserInfoController(UserInfoController userInfoController) {
+    }
+
+    /**
      * Hides transient indication in {@param delayMs}.
      */
     public void hideTransientIndicationDelayed(long delayMs) {
@@ -264,8 +290,37 @@
         }
     }
 
-    KeyguardUpdateMonitorCallback mUpdateMonitor = new KeyguardUpdateMonitorCallback() {
-        public int mLastSuccessiveErrorMessage = -1;
+    public void setStatusBarKeyguardViewManager(
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+    }
+
+    BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mHandler.post(() -> {
+                if (mVisible) {
+                    updateIndication();
+                }
+            });
+        }
+    };
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
+                mTransientIndication = null;
+                updateIndication();
+            } else if (msg.what == MSG_CLEAR_FP_MSG) {
+                mLockIcon.setTransientFpError(false);
+                hideTransientIndication();
+            }
+        }
+    };
+
+    protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
+        private int mLastSuccessiveErrorMessage = -1;
 
         @Override
         public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
@@ -372,34 +427,4 @@
             }
         }
     };
-
-    BroadcastReceiver mTickReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mHandler.post(() -> {
-                if (mVisible) {
-                    updateIndication();
-                }
-            });
-        }
-    };
-
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
-                mTransientIndication = null;
-                updateIndication();
-            } else if (msg.what == MSG_CLEAR_FP_MSG) {
-                mLockIcon.setTransientFpError(false);
-                hideTransientIndication();
-            }
-        }
-    };
-
-    public void setStatusBarKeyguardViewManager(
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
-        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index b45cde8..a1299e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -129,6 +129,7 @@
     private boolean mHeadsUpAnimatingAway;
     private boolean mIconsVisible;
     private int mClipBottomAmount;
+    private boolean mIsLowPriority;
 
 
     public NotificationContentView(Context context, AttributeSet attrs) {
@@ -163,20 +164,31 @@
         if (mExpandedChild != null) {
             int size = Math.min(maxSize, mNotificationMaxHeight);
             ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
+            boolean useExactly = false;
             if (layoutParams.height >= 0) {
                 // An actual height is set
                 size = Math.min(maxSize, layoutParams.height);
+                useExactly = true;
             }
             int spec = size == Integer.MAX_VALUE
                     ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
-                    : MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+                    : MeasureSpec.makeMeasureSpec(size, useExactly
+                            ? MeasureSpec.EXACTLY
+                            : MeasureSpec.AT_MOST);
             mExpandedChild.measure(widthMeasureSpec, spec);
             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
         }
         if (mContractedChild != null) {
             int heightSpec;
             int size = Math.min(maxSize, mSmallHeight);
-            if (shouldContractedBeFixedSize()) {
+            ViewGroup.LayoutParams layoutParams = mContractedChild.getLayoutParams();
+            boolean useExactly = false;
+            if (layoutParams.height >= 0) {
+                // An actual height is set
+                size = Math.min(size, layoutParams.height);
+                useExactly = true;
+            }
+            if (shouldContractedBeFixedSize() || useExactly) {
                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
             } else {
                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
@@ -202,12 +214,15 @@
         if (mHeadsUpChild != null) {
             int size = Math.min(maxSize, mHeadsUpHeight);
             ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
+            boolean useExactly = false;
             if (layoutParams.height >= 0) {
                 // An actual height is set
                 size = Math.min(size, layoutParams.height);
+                useExactly = true;
             }
             mHeadsUpChild.measure(widthMeasureSpec,
-                    MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+                    MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+                            : MeasureSpec.AT_MOST));
             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
         }
         if (mSingleLineView != null) {
@@ -225,12 +240,15 @@
         if (mAmbientChild != null) {
             int size = Math.min(maxSize, mNotificationAmbientHeight);
             ViewGroup.LayoutParams layoutParams = mAmbientChild.getLayoutParams();
+            boolean useExactly = false;
             if (layoutParams.height >= 0) {
                 // An actual height is set
                 size = Math.min(size, layoutParams.height);
+                useExactly = true;
             }
             mAmbientChild.measure(widthMeasureSpec,
-                    MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+                    MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
+                            : MeasureSpec.AT_MOST));
             maxChildHeight = Math.max(maxChildHeight, mAmbientChild.getMeasuredHeight());
         }
         int ownHeight = Math.min(maxChildHeight, maxSize);
@@ -606,7 +624,7 @@
     }
 
     public int getMinHeight(boolean likeGroupExpanded) {
-        if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
+        if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded() || mIsLowPriority) {
             return mContractedChild.getHeight();
         } else {
             return mSingleLineView.getHeight();
@@ -868,7 +886,7 @@
                 height = mContentHeight;
             }
             int expandedVisualType = getVisualTypeForHeight(height);
-            int collapsedVisualType = mIsChildInGroup && !isGroupExpanded()
+            int collapsedVisualType = mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority
                     ? VISIBLE_TYPE_SINGLELINE
                     : getVisualTypeForHeight(mContainingNotification.getCollapsedHeight());
             return mTransformationStartVisibleType == collapsedVisualType
@@ -889,7 +907,7 @@
         if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
             return VISIBLE_TYPE_EXPANDED;
         }
-        if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
+        if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded() && !mIsLowPriority) {
             return VISIBLE_TYPE_SINGLELINE;
         }
 
@@ -974,19 +992,19 @@
         mStatusBarNotification = entry.notification;
         mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
         updateSingleLineView();
-        applyRemoteInput(entry);
         if (mContractedChild != null) {
-            mContractedWrapper.notifyContentUpdated(entry.notification);
+            mContractedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
         }
         if (mExpandedChild != null) {
-            mExpandedWrapper.notifyContentUpdated(entry.notification);
+            mExpandedWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
         }
         if (mHeadsUpChild != null) {
-            mHeadsUpWrapper.notifyContentUpdated(entry.notification);
+            mHeadsUpWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
         }
         if (mAmbientChild != null) {
-            mAmbientWrapper.notifyContentUpdated(entry.notification);
+            mAmbientWrapper.notifyContentUpdated(entry.notification, mIsLowPriority);
         }
+        applyRemoteInput(entry);
         updateShowingLegacyBackground();
         mForceSelectNextLayout = true;
         setDark(mDark, false /* animate */, 0 /* delay */);
@@ -1028,7 +1046,8 @@
         View bigContentView = mExpandedChild;
         if (bigContentView != null) {
             mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput,
-                    mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput);
+                    mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput,
+                    mExpandedWrapper);
         } else {
             mExpandedRemoteInput = null;
         }
@@ -1042,7 +1061,7 @@
         View headsUpContentView = mHeadsUpChild;
         if (headsUpContentView != null) {
             mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput,
-                    mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput);
+                    mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput, mHeadsUpWrapper);
         } else {
             mHeadsUpRemoteInput = null;
         }
@@ -1056,7 +1075,7 @@
 
     private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
             boolean hasRemoteInput, PendingIntent existingPendingIntent,
-            RemoteInputView cachedView) {
+            RemoteInputView cachedView, NotificationViewWrapper wrapper) {
         View actionContainerCandidate = view.findViewById(
                 com.android.internal.R.id.actions_container);
         if (actionContainerCandidate instanceof FrameLayout) {
@@ -1095,6 +1114,8 @@
                         mContext.getColor(R.color.remote_input_text_enabled),
                         mContext.getColor(R.color.remote_input_hint)));
 
+                existing.setWrapper(wrapper);
+
                 if (existingPendingIntent != null || existing.isActive()) {
                     // The current action could be gone, or the pending intent no longer valid.
                     // If we find a matching action in the new notification, focus, otherwise close.
@@ -1282,4 +1303,8 @@
             }
         }
     }
+
+    public void setIsLowPriority(boolean isLowPriority) {
+        mIsLowPriority = isLowPriority;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 458daf1..8c04a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.graphics.drawable.Icon;
@@ -55,6 +56,7 @@
         private static final int COLOR_INVALID = 1;
         public String key;
         public StatusBarNotification notification;
+        public NotificationChannel channel;
         public StatusBarIconView icon;
         public StatusBarIconView expandedIcon;
         public ExpandableNotificationRow row; // the outer expanded view
@@ -115,14 +117,16 @@
             return row.getPublicLayout().getContractedChild();
         }
 
-        public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
+        public boolean cacheContentViews(Context ctx, Notification updatedNotification,
+                boolean isLowPriority) {
             boolean applyInPlace = false;
             if (updatedNotification != null) {
                 final Notification.Builder updatedNotificationBuilder
                         = Notification.Builder.recoverBuilder(ctx, updatedNotification);
-                final RemoteViews newContentView = updatedNotificationBuilder.createContentView();
-                final RemoteViews newBigContentView =
-                        updatedNotificationBuilder.createBigContentView();
+                final RemoteViews newContentView = createContentView(updatedNotificationBuilder,
+                        isLowPriority);
+                final RemoteViews newBigContentView = createBigContentView(
+                        updatedNotificationBuilder, isLowPriority);
                 final RemoteViews newHeadsUpContentView =
                         updatedNotificationBuilder.createHeadsUpContentView();
                 final RemoteViews newPublicNotification
@@ -150,8 +154,8 @@
                 final Notification.Builder builder
                         = Notification.Builder.recoverBuilder(ctx, notification.getNotification());
 
-                cachedContentView = builder.createContentView();
-                cachedBigContentView = builder.createBigContentView();
+                cachedContentView = createContentView(builder, isLowPriority);
+                cachedBigContentView = createBigContentView(builder, isLowPriority);
                 cachedHeadsUpContentView = builder.createHeadsUpContentView();
                 cachedPublicContentView = builder.makePublicContentView();
                 cachedAmbientContentView = builder.makeAmbientNotification();
@@ -161,6 +165,28 @@
             return applyInPlace;
         }
 
+        private RemoteViews createBigContentView(Notification.Builder builder,
+                boolean isLowPriority) {
+            RemoteViews bigContentView = builder.createBigContentView();
+            if (bigContentView != null) {
+                return bigContentView;
+            }
+            if (isLowPriority) {
+                RemoteViews contentView = builder.createContentView();
+                Notification.Builder.makeHeaderExpanded(contentView);
+                return contentView;
+            }
+            return null;
+        }
+
+        private RemoteViews createContentView(Notification.Builder builder,
+                boolean isAmbient) {
+            if (isAmbient) {
+                return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
+            }
+            return builder.createContentView();
+        }
+
         // Returns true if the RemoteViews are the same.
         private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
             return (a == null && b == null) ||
@@ -254,8 +280,9 @@
             }
         }
 
-        public int getContrastedColor(Context context) {
-            int rawColor = notification.getNotification().color;
+        public int getContrastedColor(Context context, boolean ambient) {
+            int rawColor = ambient ? Notification.COLOR_DEFAULT :
+                    notification.getNotification().color;
             if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
                 return mCachedContrastColor;
             }
@@ -429,6 +456,14 @@
          return null;
     }
 
+    public NotificationChannel getChannel(String key) {
+        if (mRankingMap != null) {
+            mRankingMap.getRanking(key, mTmpRanking);
+            return mTmpRanking.getChannel();
+        }
+        return null;
+    }
+
     private void updateRankingAndSort(RankingMap ranking) {
         if (ranking != null) {
             mRankingMap = ranking;
@@ -442,6 +477,7 @@
                         entry.notification.setOverrideGroupKey(overrideGroupKey);
                         mGroupManager.onEntryUpdated(entry, oldSbn);
                     }
+                    entry.channel = getChannel(entry.key);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index c7adb60..f6056dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -40,6 +40,7 @@
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.widget.CompoundButton;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RadioButton;
@@ -53,6 +54,8 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.util.Set;
@@ -60,7 +63,8 @@
 /**
  * The guts of a notification revealed when performing a long press.
  */
-public class NotificationGuts extends LinearLayout {
+public class NotificationGuts extends FrameLayout
+        implements NotificationMenuRowProvider.GutsInteractionListener {
     private static final String TAG = "NotificationGuts";
     private static final long CLOSE_GUTS_DELAY = 8000;
 
@@ -69,28 +73,14 @@
     private int mClipBottomAmount;
     private int mActualHeight;
     private boolean mExposed;
-    private INotificationManager mINotificationManager;
-    private int mStartingUserImportance;
-    private StatusBarNotification mStatusBarNotification;
-
-    private ImageView mAutoButton;
-    private TextView mImportanceSummary;
-    private TextView mImportanceTitle;
-    private boolean mAuto;
-
-    private View mImportanceGroup;
-    private View mChannelDisabled;
-    private Switch mChannelEnabledSwitch;
-    private RadioButton mMinImportanceButton;
-    private RadioButton mLowImportanceButton;
-    private RadioButton mDefaultImportanceButton;
-    private RadioButton mHighImportanceButton;
 
     private Handler mHandler;
     private Runnable mFalsingCheck;
     private boolean mNeedsFalsingProtection;
     private OnGutsClosedListener mListener;
 
+    private GutsContent mGutsContent;
+
     public interface OnGutsClosedListener {
         public void onGutsClosed(NotificationGuts guts);
     }
@@ -107,11 +97,22 @@
                 }
             }
         };
-        final TypedArray ta =
-                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
+        final TypedArray ta = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Theme, 0, 0);
         ta.recycle();
     }
 
+    public NotificationGuts(Context context) {
+        this(context, null);
+
+    }
+
+    public void setGutsContent(GutsContent content) {
+        mGutsContent = content;
+        removeAllViews();
+        addView(mGutsContent.getContentView());
+    }
+
     public void resetFalsingCheck() {
         mHandler.removeCallbacks(mFalsingCheck);
         if (mNeedsFalsingProtection && mExposed) {
@@ -169,213 +170,23 @@
         void onClick(View v, int appUid);
     }
 
-    void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
-            final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick,
-            OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
-        mINotificationManager = iNotificationManager;
-        mStatusBarNotification = sbn;
-        final NotificationChannel channel = sbn.getNotificationChannel();
-        mStartingUserImportance = channel.getImportance();
-
-        final String pkg = sbn.getPackageName();
-        int appUid = -1;
-        String appname = pkg;
-        Drawable pkgicon = null;
-        try {
-            final ApplicationInfo info = pm.getApplicationInfo(pkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
-                appUid = info.uid;
-                appname = String.valueOf(pm.getApplicationLabel(info));
-                pkgicon = pm.getApplicationIcon(info);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // app is gone, just show package name and generic icon
-            pkgicon = pm.getDefaultActivityIcon();
-        }
-
-        // If this is the placeholder channel, don't use our channel-specific text.
-        String appNameText;
-        CharSequence channelNameText;
-        if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            appNameText = appname;
-            channelNameText = mContext.getString(R.string.notification_header_default_channel);
-        } else {
-            appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
-            channelNameText = channel.getName();
-        }
-        ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
-        ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
-
-        // Settings button.
-        final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
-        if (appUid >= 0 && onSettingsClick != null) {
-            final int appUidF = appUid;
-            settingsButton.setOnClickListener(
-                    (View view) -> { onSettingsClick.onClick(view, appUidF); });
-            settingsButton.setText(R.string.notification_more_settings);
-        } else {
-            settingsButton.setVisibility(View.GONE);
-        }
-
-        // Done button.
-        final TextView doneButton = (TextView) findViewById(R.id.done);
-        doneButton.setText(R.string.notification_done);
-        doneButton.setOnClickListener(onDoneClick);
-
-        boolean nonBlockable = false;
-        try {
-            final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
-            nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
-        } catch (PackageManager.NameNotFoundException e) {
-            // unlikely.
-        }
-        if (nonBlockablePkgs != null) {
-            nonBlockable |= nonBlockablePkgs.contains(pkg);
-        }
-
-        final View importanceButtons = findViewById(R.id.importance_buttons);
-        bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
-
-        // Importance Text (hardcoded to 4 importance levels)
-        final ViewGroup importanceTextGroup =
-                (ViewGroup) findViewById(R.id.importance_buttons_text);
-        final int size = importanceTextGroup.getChildCount();
-        for (int i = 0; i < size; i++) {
-            int importanceNameResId = 0;
-            int importanceDescResId = 0;
-            switch (i) {
-                case 0:
-                    importanceNameResId = R.string.high_importance;
-                    importanceDescResId = R.string.notification_importance_high;
-                    break;
-                case 1:
-                    importanceNameResId = R.string.default_importance;
-                    importanceDescResId = R.string.notification_importance_default;
-                    break;
-                case 2:
-                    importanceNameResId = R.string.low_importance;
-                    importanceDescResId = R.string.notification_importance_low;
-                    break;
-                case 3:
-                    importanceNameResId = R.string.min_importance;
-                    importanceDescResId = R.string.notification_importance_min;
-                    break;
-                default:
-                    Log.e(TAG, "Too many importance groups in this layout.");
-                    break;
-            }
-            final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
-            ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
-            ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
-        }
-
-        // Top-level importance group
-        mImportanceGroup = findViewById(R.id.importance);
-        mChannelDisabled = findViewById(R.id.channel_disabled);
-        updateImportanceGroup();
-    }
-
-    public boolean hasImportanceChanged() {
-        return mStartingUserImportance != getSelectedImportance();
-    }
-
-    private void saveImportance() {
-        int selectedImportance = getSelectedImportance();
-        if (selectedImportance == mStartingUserImportance) {
-            return;
-        }
-        final NotificationChannel channel = mStatusBarNotification.getNotificationChannel();
-        MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
-                selectedImportance - mStartingUserImportance);
-        channel.setImportance(selectedImportance);
-        try {
-            mINotificationManager.updateNotificationChannelForPackage(
-                    mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
-                    channel);
-        } catch (RemoteException e) {
-            // :(
-        }
-    }
-
-    private int getSelectedImportance() {
-        if (!mChannelEnabledSwitch.isChecked()) {
-            return NotificationManager.IMPORTANCE_NONE;
-        } else if (mMinImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_MIN;
-        } else if (mLowImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_LOW;
-        } else if (mDefaultImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_DEFAULT;
-        } else if (mHighImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_HIGH;
-        } else {
-            return NotificationManager.IMPORTANCE_UNSPECIFIED;
-        }
-    }
-
-    private void bindToggles(final View importanceButtons, final int importance,
-            final boolean nonBlockable) {
-        // Enabled Switch
-        mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
-        mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
-        mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
-
-        // Importance Buttons
-        mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
-        mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
-        mDefaultImportanceButton =
-                (RadioButton) importanceButtons.findViewById(R.id.default_importance);
-        mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
-
-        // Set to current importance setting
-        switch (importance) {
-            case NotificationManager.IMPORTANCE_UNSPECIFIED:
-            case NotificationManager.IMPORTANCE_NONE:
-                break;
-            case NotificationManager.IMPORTANCE_MIN:
-                mMinImportanceButton.setChecked(true);
-                break;
-            case NotificationManager.IMPORTANCE_LOW:
-                mLowImportanceButton.setChecked(true);
-                break;
-            case NotificationManager.IMPORTANCE_DEFAULT:
-                mDefaultImportanceButton.setChecked(true);
-                break;
-            case NotificationManager.IMPORTANCE_HIGH:
-            case NotificationManager.IMPORTANCE_MAX:
-                mHighImportanceButton.setChecked(true);
-                break;
-        }
-
-        // Callback when checked.
-        mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            resetFalsingCheck();
-            updateImportanceGroup();
-        });
-        ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
-                (buttonView, isChecked) -> { resetFalsingCheck(); });
-    }
-
-    private void updateImportanceGroup() {
-        final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
-        mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
-        mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
-    }
-
     public void closeControls(int x, int y, boolean saveImportance) {
-        if (saveImportance) {
-            saveImportance();
-        }
         if (getWindowToken() == null) {
             if (mListener != null) {
                 mListener.onGutsClosed(this);
             }
             return;
         }
+        if (mGutsContent == null || !mGutsContent.handleCloseControls()) {
+            animateClose(x, y);
+        }
+        setExposed(false, mNeedsFalsingProtection);
+        if (mListener != null) {
+            mListener.onGutsClosed(this);
+        }
+    }
+
+    private void animateClose(int x, int y) {
         if (x == -1 || y == -1) {
             x = (getLeft() + getRight()) / 2;
             y = (getTop() + getHeight() / 2);
@@ -395,10 +206,6 @@
             }
         });
         a.start();
-        setExposed(false, mNeedsFalsingProtection);
-        if (mListener != null) {
-            mListener.onGutsClosed(this);
-        }
     }
 
     public void setActualHeight(int actualHeight) {
@@ -443,4 +250,14 @@
     public boolean isExposed() {
         return mExposed;
     }
+
+    @Override
+    public void onInteraction(View view) {
+        resetFalsingCheck();
+    }
+
+    @Override
+    public void closeGuts(View view) {
+        closeControls(-1 /* x */, -1 /* y */, true /* notify */);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
new file mode 100644
index 0000000..0989846
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -0,0 +1,319 @@
+package com.android.systemui.statusbar;
+
+/*
+ * 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
+ */
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
+import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.util.Set;
+
+/**
+ * The guts of a notification revealed when performing a long press.
+ */
+public class NotificationInfo extends LinearLayout implements GutsContent {
+    private static final String TAG = "InfoGuts";
+
+    private INotificationManager mINotificationManager;
+    private int mStartingUserImportance;
+    private StatusBarNotification mStatusBarNotification;
+    private NotificationChannel mNotificationChannel;
+
+    private ImageView mAutoButton;
+    private TextView mImportanceSummary;
+    private TextView mImportanceTitle;
+    private boolean mAuto;
+
+    private View mImportanceGroup;
+    private View mChannelDisabled;
+    private Switch mChannelEnabledSwitch;
+    private RadioButton mMinImportanceButton;
+    private RadioButton mLowImportanceButton;
+    private RadioButton mDefaultImportanceButton;
+    private RadioButton mHighImportanceButton;
+
+    private GutsInteractionListener mGutsInteractionListener;
+
+    public NotificationInfo(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    interface OnSettingsClickListener {
+        void onClick(View v, int appUid);
+    }
+
+    void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
+            final StatusBarNotification sbn, final NotificationChannel channel,
+            OnSettingsClickListener onSettingsClick,
+            OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
+        mINotificationManager = iNotificationManager;
+        mNotificationChannel = channel;
+        mStatusBarNotification = sbn;
+        mStartingUserImportance = channel.getImportance();
+
+        final String pkg = sbn.getPackageName();
+        int appUid = -1;
+        String appname = pkg;
+        Drawable pkgicon = null;
+        try {
+            final ApplicationInfo info = pm.getApplicationInfo(pkg,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (info != null) {
+                appUid = info.uid;
+                appname = String.valueOf(pm.getApplicationLabel(info));
+                pkgicon = pm.getApplicationIcon(info);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // app is gone, just show package name and generic icon
+            pkgicon = pm.getDefaultActivityIcon();
+        }
+
+        // If this is the placeholder channel, don't use our channel-specific text.
+        String appNameText;
+        CharSequence channelNameText;
+        if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            appNameText = appname;
+            channelNameText = mContext.getString(R.string.notification_header_default_channel);
+        } else {
+            appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
+            channelNameText = channel.getName();
+        }
+        ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
+        ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
+
+        // Settings button.
+        final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
+        if (appUid >= 0 && onSettingsClick != null) {
+            final int appUidF = appUid;
+            settingsButton.setOnClickListener(
+                    (View view) -> {
+                        onSettingsClick.onClick(view, appUidF);
+                    });
+            settingsButton.setText(R.string.notification_more_settings);
+        } else {
+            settingsButton.setVisibility(View.GONE);
+        }
+
+        // Done button.
+        final TextView doneButton = (TextView) findViewById(R.id.done);
+        doneButton.setText(R.string.notification_done);
+        doneButton.setOnClickListener(onDoneClick);
+
+        boolean nonBlockable = false;
+        try {
+            final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+            nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
+        } catch (PackageManager.NameNotFoundException e) {
+            // unlikely.
+        }
+        if (nonBlockablePkgs != null) {
+            nonBlockable |= nonBlockablePkgs.contains(pkg);
+        }
+
+        final View importanceButtons = findViewById(R.id.importance_buttons);
+        bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
+
+        // Importance Text (hardcoded to 4 importance levels)
+        final ViewGroup importanceTextGroup = (ViewGroup) findViewById(
+                R.id.importance_buttons_text);
+        final int size = importanceTextGroup.getChildCount();
+        for (int i = 0; i < size; i++) {
+            int importanceNameResId = 0;
+            int importanceDescResId = 0;
+            switch (i) {
+                case 0:
+                    importanceNameResId = R.string.high_importance;
+                    importanceDescResId = R.string.notification_importance_high;
+                    break;
+                case 1:
+                    importanceNameResId = R.string.default_importance;
+                    importanceDescResId = R.string.notification_importance_default;
+                    break;
+                case 2:
+                    importanceNameResId = R.string.low_importance;
+                    importanceDescResId = R.string.notification_importance_low;
+                    break;
+                case 3:
+                    importanceNameResId = R.string.min_importance;
+                    importanceDescResId = R.string.notification_importance_min;
+                    break;
+                default:
+                    Log.e(TAG, "Too many importance groups in this layout.");
+                    break;
+            }
+            final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
+            ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
+            ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
+        }
+
+        // Top-level importance group
+        mImportanceGroup = findViewById(R.id.importance);
+        mChannelDisabled = findViewById(R.id.channel_disabled);
+        updateImportanceGroup();
+    }
+
+    public boolean hasImportanceChanged() {
+        return mStartingUserImportance != getSelectedImportance();
+    }
+
+    public void saveImportance() {
+        int selectedImportance = getSelectedImportance();
+        if (selectedImportance == mStartingUserImportance) {
+            return;
+        }
+        MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
+                selectedImportance - mStartingUserImportance);
+        mNotificationChannel.setImportance(selectedImportance);
+        try {
+            mINotificationManager.updateNotificationChannelForPackage(
+                    mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
+                    mNotificationChannel);
+        } catch (RemoteException e) {
+            // :(
+        }
+    }
+
+    private int getSelectedImportance() {
+        if (!mChannelEnabledSwitch.isChecked()) {
+            return NotificationManager.IMPORTANCE_NONE;
+        } else if (mMinImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_MIN;
+        } else if (mLowImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_LOW;
+        } else if (mDefaultImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_DEFAULT;
+        } else if (mHighImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_HIGH;
+        } else {
+            return NotificationManager.IMPORTANCE_UNSPECIFIED;
+        }
+    }
+
+    private void bindToggles(final View importanceButtons, final int importance,
+            final boolean nonBlockable) {
+        // Enabled Switch
+        mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
+        mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
+        mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
+
+        // Importance Buttons
+        mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
+        mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
+        mDefaultImportanceButton = (RadioButton) importanceButtons
+                .findViewById(R.id.default_importance);
+        mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
+
+        // Set to current importance setting
+        switch (importance) {
+            case NotificationManager.IMPORTANCE_UNSPECIFIED:
+            case NotificationManager.IMPORTANCE_NONE:
+                break;
+            case NotificationManager.IMPORTANCE_MIN:
+                mMinImportanceButton.setChecked(true);
+                break;
+            case NotificationManager.IMPORTANCE_LOW:
+                mLowImportanceButton.setChecked(true);
+                break;
+            case NotificationManager.IMPORTANCE_DEFAULT:
+                mDefaultImportanceButton.setChecked(true);
+                break;
+            case NotificationManager.IMPORTANCE_HIGH:
+            case NotificationManager.IMPORTANCE_MAX:
+                mHighImportanceButton.setChecked(true);
+                break;
+        }
+
+        // Callback when checked.
+        mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            mGutsInteractionListener.onInteraction(NotificationInfo.this);
+            updateImportanceGroup();
+        });
+        ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
+                (buttonView, isChecked) -> {
+                    mGutsInteractionListener.onInteraction(NotificationInfo.this);
+                });
+    }
+
+    private void updateImportanceGroup() {
+        final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+        mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
+        mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
+    }
+
+    public void closeControls() {
+        if (mGutsInteractionListener != null) {
+            mGutsInteractionListener.closeGuts(this);
+        }
+    }
+
+    @Override
+    public void setInteractionListener(GutsInteractionListener listener) {
+        mGutsInteractionListener = listener;
+    }
+
+    @Override
+    public View getContentView() {
+        return this;
+    }
+
+    @Override
+    public boolean handleCloseControls() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
new file mode 100644
index 0000000..fad63dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener;
+
+public class NotificationMenuRow extends FrameLayout
+        implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener {
+
+    private static final int ICON_ALPHA_ANIM_DURATION = 200;
+
+    private ExpandableNotificationRow mParent;
+    private OnMenuClickListener mListener;
+    private NotificationMenuRowProvider mMenuProvider;
+    private ArrayList<MenuItem> mMenuItems = new ArrayList<>();
+
+    private ValueAnimator mFadeAnimator;
+    private boolean mMenuFadedIn = false;
+    private boolean mAnimating = false;
+    private boolean mOnLeft = true;
+    private boolean mDismissing = false;
+    private boolean mSnapping = false;
+    private boolean mIconsPlaced = false;
+
+    private int[] mIconLocation = new int[2];
+    private int[] mParentLocation = new int[2];
+
+    private float mHorizSpaceForIcon;
+    private int mVertSpaceForIcons;
+
+    private int mIconPadding;
+    private int mIconTint;
+
+    private float mAlpha = 0f;
+
+    public NotificationMenuRow(Context context) {
+        this(context, null);
+    }
+
+    public NotificationMenuRow(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs);
+        PluginManager.getInstance(getContext()).addPluginListener(
+                NotificationMenuRowProvider.ACTION, this,
+                NotificationMenuRowProvider.VERSION, false /* Allow multiple */);
+        mMenuItems.addAll(getDefaultNotificationMenuItems());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        final Resources res = getResources();
+        mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
+        mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
+        mIconTint = res.getColor(R.color.notification_gear_color);
+        updateMenu(false /* notify */);
+    }
+
+    public static MenuItem getLongpressMenuItem(Context context) {
+        Resources res = context.getResources();
+        Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
+        String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+        NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
+                R.layout.notification_info, null, false);
+        MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
+        return settings;
+    }
+
+    public ArrayList<MenuItem> getDefaultNotificationMenuItems() {
+        ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+        Resources res = getResources();
+
+        Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze);
+        NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext)
+                .inflate(R.layout.notification_snooze, null, false);
+        String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
+        MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content);
+        items.add(snooze);
+
+        Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
+        String settingsDescription = res.getString(R.string.notification_menu_gear_description);
+        NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate(
+                R.layout.notification_info, null, false);
+        MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
+        items.add(settings);
+        return items;
+    }
+
+    private void updateMenu(boolean notify) {
+        removeAllViews();
+        mMenuItems.clear();
+        if (mMenuProvider != null) {
+            mMenuItems.addAll(mMenuProvider.getMenuItems(getContext()));
+        }
+        mMenuItems.addAll(getDefaultNotificationMenuItems());
+        for (int i = 0; i < mMenuItems.size(); i++) {
+            final View v = createMenuView(mMenuItems.get(i));
+            mMenuItems.get(i).menuView = v;
+        }
+        resetState(notify);
+    }
+
+    private View createMenuView(MenuItem item) {
+        AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext());
+        addView(iv);
+        iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+        iv.setImageDrawable(item.icon);
+        iv.setOnClickListener(this);
+        iv.setColorFilter(mIconTint);
+        iv.setAlpha(mAlpha);
+        FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams();
+        lp.width = (int) mHorizSpaceForIcon;
+        lp.height = (int) mHorizSpaceForIcon;
+        return iv;
+    }
+
+    public void resetState(boolean notify) {
+        setMenuAlpha(0f);
+        mIconsPlaced = false;
+        mMenuFadedIn = false;
+        mAnimating = false;
+        mSnapping = false;
+        mDismissing = false;
+        setMenuLocation(mOnLeft ? 1 : -1 /* on left */);
+        if (mListener != null && notify) {
+            mListener.onMenuReset(mParent);
+        }
+    }
+
+    public void setMenuClickListener(OnMenuClickListener listener) {
+        mListener = listener;
+    }
+
+    public void setNotificationRowParent(ExpandableNotificationRow parent) {
+        mParent = parent;
+        setMenuLocation(mOnLeft ? 1 : -1);
+    }
+
+    public void setAppName(String appName) {
+        Resources res = getResources();
+        final int count = mMenuItems.size();
+        for (int i = 0; i < count; i++) {
+            MenuItem item = mMenuItems.get(i);
+            String description = String.format(
+                    res.getString(R.string.notification_menu_accessibility),
+                    appName, item.menuDescription);
+            item.menuView.setContentDescription(description);
+        }
+    }
+
+    public ExpandableNotificationRow getNotificationParent() {
+        return mParent;
+    }
+
+    public void setMenuAlpha(float alpha) {
+        mAlpha = alpha;
+        if (alpha == 0) {
+            mMenuFadedIn = false; // Can fade in again once it's gone.
+            setVisibility(View.INVISIBLE);
+        } else {
+            setVisibility(View.VISIBLE);
+        }
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).setAlpha(mAlpha);
+        }
+    }
+
+    /**
+     * Returns whether the menu is displayed on the left side of the view or not.
+     */
+    public boolean isMenuOnLeft() {
+        return mOnLeft;
+    }
+
+    /**
+     * Returns the horizontal space in pixels required to display the menu.
+     */
+    public float getSpaceForMenu() {
+        return mHorizSpaceForIcon * getChildCount();
+    }
+
+    /**
+     * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is
+     * visible.
+     */
+    public boolean isVisible() {
+        return mAlpha > 0;
+    }
+
+    public void cancelFadeAnimator() {
+        if (mFadeAnimator != null) {
+            mFadeAnimator.cancel();
+        }
+    }
+
+    public void updateMenuAlpha(final float transX, final float size) {
+        if (mAnimating || !mMenuFadedIn) {
+            // Don't adjust when animating, or if the menu hasn't been shown yet.
+            return;
+        }
+
+        final float fadeThreshold = size * 0.3f;
+        final float absTrans = Math.abs(transX);
+        float desiredAlpha = 0;
+
+        if (absTrans == 0) {
+            desiredAlpha = 0;
+        } else if (absTrans <= fadeThreshold) {
+            desiredAlpha = 1;
+        } else {
+            desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
+        }
+        setMenuAlpha(desiredAlpha);
+    }
+
+    public void fadeInMenu(final boolean fromLeft, final float transX,
+            final float notiThreshold) {
+        if (mDismissing || mAnimating) {
+            return;
+        }
+        if (isMenuLocationChange(transX)) {
+            setMenuAlpha(0f);
+        }
+        setMenuLocation((int) transX);
+        mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);
+        mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                final float absTrans = Math.abs(transX);
+
+                boolean pastGear = (fromLeft && transX <= notiThreshold)
+                        || (!fromLeft && absTrans <= notiThreshold);
+                if (pastGear && !mMenuFadedIn) {
+                    setMenuAlpha((float) animation.getAnimatedValue());
+                }
+            }
+        });
+        mFadeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mAnimating = true;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                // TODO should animate back to 0f from current alpha
+                setMenuAlpha(0f);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimating = false;
+                mMenuFadedIn = mAlpha == 1;
+            }
+        });
+        mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
+        mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION);
+        mFadeAnimator.start();
+    }
+
+    public void updateVerticalLocation() {
+        if (mParent == null || mMenuItems.size() == 0) {
+            return;
+        }
+        int parentHeight = mParent.getCollapsedHeight();
+        float translationY;
+        if (parentHeight < mVertSpaceForIcons) {
+            translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
+        } else {
+            translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
+        }
+        setTranslationY(translationY);
+    }
+
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        mIconsPlaced = false;
+        setMenuLocation(mOnLeft ? 1 : -1);
+    }
+
+    public void setMenuLocation(int translation) {
+        boolean onLeft = translation > 0;
+        if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) {
+            // Do nothing
+            return;
+        }
+        final boolean isRtl = mParent.isLayoutRtl();
+        final int count = getChildCount();
+        final int width = getWidth();
+        for (int i = 0; i < count; i++) {
+            final View v = getChildAt(i);
+            final float left = isRtl
+                    ? -(width - mHorizSpaceForIcon * (i + 1))
+                    : i * mHorizSpaceForIcon;
+            final float right = isRtl
+                    ? -i * mHorizSpaceForIcon
+                    : width - (mHorizSpaceForIcon * (i + 1));
+            v.setTranslationX(onLeft ? left : right);
+        }
+        mOnLeft = onLeft;
+        mIconsPlaced = true;
+    }
+
+    public boolean isMenuLocationChange(float translation) {
+        boolean onLeft = translation > mIconPadding;
+        boolean onRight = translation < -mIconPadding;
+        if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
+            return true;
+        }
+        return false;
+    }
+
+    public void setDismissing() {
+        mDismissing = true;
+    }
+
+    public void setSnapping(boolean snapping) {
+        mSnapping = snapping;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (mListener == null) {
+            // Nothing to do
+            return;
+        }
+        v.getLocationOnScreen(mIconLocation);
+        mParent.getLocationOnScreen(mParentLocation);
+        final int centerX = (int) (mHorizSpaceForIcon / 2);
+        final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2;
+        final int x = mIconLocation[0] - mParentLocation[0] + centerX;
+        final int y = mIconLocation[1] - mParentLocation[1] + centerY;
+        final int index = indexOfChild(v);
+        mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
+    }
+
+    @Override
+    public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) {
+        mMenuProvider = plugin;
+        updateMenu(false /* notify */);
+    }
+
+    @Override
+    public void onPluginDisconnected(NotificationMenuRowProvider plugin) {
+        mMenuProvider = null;
+        updateMenu(false /* notify */);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
deleted file mode 100644
index 4315647..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-
-public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
-
-    private static final int GEAR_ALPHA_ANIM_DURATION = 200;
-
-    public interface SettingsIconRowListener {
-        /**
-         * Called when the gear behind a notification is touched.
-         */
-        public void onGearTouched(ExpandableNotificationRow row, int x, int y);
-
-        /**
-         * Called when a notification is slid back over the gear.
-         */
-        public void onSettingsIconRowReset(ExpandableNotificationRow row);
-    }
-
-    private ExpandableNotificationRow mParent;
-    private AlphaOptimizedImageView mGearIcon;
-    private float mHorizSpaceForGear;
-    private SettingsIconRowListener mListener;
-
-    private ValueAnimator mFadeAnimator;
-    private boolean mSettingsFadedIn = false;
-    private boolean mAnimating = false;
-    private boolean mOnLeft = true;
-    private boolean mDismissing = false;
-    private boolean mSnapping = false;
-    private boolean mIconPlaced = false;
-
-    private int[] mGearLocation = new int[2];
-    private int[] mParentLocation = new int[2];
-    private int mVertSpaceForGear;
-
-    public NotificationSettingsIconRow(Context context) {
-        this(context, null);
-    }
-
-    public NotificationSettingsIconRow(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mGearIcon = (AlphaOptimizedImageView) findViewById(R.id.gear_icon);
-        mGearIcon.setOnClickListener(this);
-        setOnClickListener(this);
-        mHorizSpaceForGear =
-                getResources().getDimensionPixelOffset(R.dimen.notification_gear_width);
-        mVertSpaceForGear = getResources().getDimensionPixelOffset(R.dimen.notification_min_height);
-        resetState();
-    }
-
-    public void resetState() {
-        setGearAlpha(0f);
-        mIconPlaced = false;
-        mSettingsFadedIn = false;
-        mAnimating = false;
-        mSnapping = false;
-        mDismissing = false;
-        setIconLocation(true /* on left */);
-        if (mListener != null) {
-            mListener.onSettingsIconRowReset(mParent);
-        }
-    }
-
-    public void setGearListener(SettingsIconRowListener listener) {
-        mListener = listener;
-    }
-
-    public void setNotificationRowParent(ExpandableNotificationRow parent) {
-        mParent = parent;
-        setIconLocation(mOnLeft);
-    }
-
-    public void setAppName(String appName) {
-        Resources res = getResources();
-        String description = String.format(res.getString(R.string.notification_gear_accessibility),
-                appName);
-        mGearIcon.setContentDescription(description);
-    }
-
-    public ExpandableNotificationRow getNotificationParent() {
-        return mParent;
-    }
-
-    public void setGearAlpha(float alpha) {
-        if (alpha == 0) {
-            mSettingsFadedIn = false; // Can fade in again once it's gone.
-            setVisibility(View.INVISIBLE);
-        } else {
-            setVisibility(View.VISIBLE);
-        }
-        mGearIcon.setAlpha(alpha);
-    }
-
-    /**
-     * Returns whether the icon is on the left side of the view or not.
-     */
-    public boolean isIconOnLeft() {
-        return mOnLeft;
-    }
-
-    /**
-     * Returns the horizontal space in pixels required to display the gear behind a notification.
-     */
-    public float getSpaceForGear() {
-        return mHorizSpaceForGear;
-    }
-
-    /**
-     * Indicates whether the gear is visible at 1 alpha. Does not indicate
-     * if entire view is visible.
-     */
-    public boolean isVisible() {
-        return mGearIcon.getAlpha() > 0;
-    }
-
-    public void cancelFadeAnimator() {
-        if (mFadeAnimator != null) {
-            mFadeAnimator.cancel();
-        }
-    }
-
-    public void updateSettingsIcons(final float transX, final float size) {
-        if (mAnimating || !mSettingsFadedIn) {
-            // Don't adjust when animating, or if the gear hasn't been shown yet.
-            return;
-        }
-
-        final float fadeThreshold = size * 0.3f;
-        final float absTrans = Math.abs(transX);
-        float desiredAlpha = 0;
-
-        if (absTrans == 0) {
-            desiredAlpha = 0;
-        } else if (absTrans <= fadeThreshold) {
-            desiredAlpha = 1;
-        } else {
-            desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
-        }
-        setGearAlpha(desiredAlpha);
-    }
-
-    public void fadeInSettings(final boolean fromLeft, final float transX,
-            final float notiThreshold) {
-        if (mDismissing || mAnimating) {
-            return;
-        }
-        if (isIconLocationChange(transX)) {
-            setGearAlpha(0f);
-        }
-        setIconLocation(transX > 0 /* fromLeft */);
-        mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
-        mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                final float absTrans = Math.abs(transX);
-
-                boolean pastGear = (fromLeft && transX <= notiThreshold)
-                        || (!fromLeft && absTrans <= notiThreshold);
-                if (pastGear && !mSettingsFadedIn) {
-                    setGearAlpha((float) animation.getAnimatedValue());
-                }
-            }
-        });
-        mFadeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mAnimating = true;
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                // TODO should animate back to 0f from current alpha
-                mGearIcon.setAlpha(0f);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimating = false;
-                mSettingsFadedIn = mGearIcon.getAlpha() == 1;
-            }
-        });
-        mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
-        mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
-        mFadeAnimator.start();
-    }
-
-    public void updateVerticalLocation() {
-        if (mParent == null) {
-            return;
-        }
-        int parentHeight = mParent.getCollapsedHeight();
-        if (parentHeight < mVertSpaceForGear) {
-            mGearIcon.setTranslationY((parentHeight / 2) - (mGearIcon.getHeight() / 2));
-        } else {
-            mGearIcon.setTranslationY((mVertSpaceForGear - mGearIcon.getHeight()) / 2);
-        }
-    }
-
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        setIconLocation(mOnLeft);
-    }
-
-    public void setIconLocation(boolean onLeft) {
-        if ((mIconPlaced && onLeft == mOnLeft) || mSnapping || mParent == null
-                || mGearIcon.getWidth() == 0) {
-            // Do nothing
-            return;
-        }
-        final boolean isRtl = mParent.isLayoutRtl();
-        final float left = isRtl
-                ? -(mParent.getWidth() - mHorizSpaceForGear)
-                : 0;
-        final float right = isRtl
-                ? 0
-                : mParent.getWidth() - mHorizSpaceForGear;
-        final float centerX = ((mHorizSpaceForGear - mGearIcon.getWidth()) / 2);
-        setTranslationX(onLeft ? left + centerX : right + centerX);
-        mOnLeft = onLeft;
-        mIconPlaced = true;
-    }
-
-    public boolean isIconLocationChange(float translation) {
-        boolean onLeft = translation > mGearIcon.getPaddingStart();
-        boolean onRight = translation < -mGearIcon.getPaddingStart();
-        if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
-            return true;
-        }
-        return false;
-    }
-
-    public void setDismissing() {
-        mDismissing = true;
-    }
-
-    public void setSnapping(boolean snapping) {
-        mSnapping = snapping;
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (v.getId() == R.id.gear_icon) {
-            if (mListener != null) {
-                mGearIcon.getLocationOnScreen(mGearLocation);
-                mParent.getLocationOnScreen(mParentLocation);
-
-                final int centerX = (int) (mHorizSpaceForGear / 2);
-                final int centerY =
-                        (int) (mGearIcon.getTranslationY() * 2 + mGearIcon.getHeight())/ 2;
-                final int x = mGearLocation[0] - mParentLocation[0] + centerX;
-                final int y = mGearLocation[1] - mParentLocation[1] + centerY;
-                mListener.onGearTouched(mParent, x, y);
-            }
-        } else {
-            // Do nothing when the background is touched.
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
new file mode 100644
index 0000000..670d73e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -0,0 +1,176 @@
+package com.android.systemui.statusbar;
+/*
+ * 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
+ */
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioGroup;
+import android.widget.RadioGroup.OnCheckedChangeListener;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.android.systemui.R;
+
+public class NotificationSnooze extends LinearLayout
+        implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener {
+
+    private GutsInteractionListener mGutsInteractionListener;
+    private SnoozeListener mSnoozeListener;
+    private StatusBarNotification mSbn;
+
+    private TextView mSelectedOption;
+    private TextView mUndo;
+    private ViewGroup mSnoozeOptionView;
+
+    private long mTimeToSnooze;
+
+    // Default is the first option in this list
+    private static final int[] SNOOZE_OPTIONS = {
+            R.string.snooze_option_15_min, R.string.snooze_option_30_min,
+            R.string.snooze_option_1_hour
+    };
+
+    public NotificationSnooze(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        // Create the different options based on list
+        createOptionViews();
+
+        // Snackbar
+        mSelectedOption = (TextView) findViewById(R.id.snooze_option_default);
+        mSelectedOption.setOnClickListener(this);
+        mUndo = (TextView) findViewById(R.id.undo);
+        mUndo.setOnClickListener(this);
+
+        // Default to first option in list
+        setTimeToSnooze(SNOOZE_OPTIONS[0]);
+    }
+
+    private void createOptionViews() {
+        mSnoozeOptionView = (ViewGroup) findViewById(R.id.snooze_options);
+        mSnoozeOptionView.setVisibility(View.GONE);
+        final Resources res = getResources();
+        final int textSize = res.getDimensionPixelSize(R.dimen.snooze_option_text_size);
+        final int p = res.getDimensionPixelSize(R.dimen.snooze_option_padding);
+        for (int i = 0; i < SNOOZE_OPTIONS.length; i++) {
+            TextView tv = new TextView(getContext());
+            tv.setTextColor(Color.WHITE);
+            tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+            tv.setPadding(p, p, p, p);
+            mSnoozeOptionView.addView(tv);
+            tv.setText(SNOOZE_OPTIONS[i]);
+            tv.setTag(SNOOZE_OPTIONS[i]);
+            tv.setOnClickListener(this);
+        }
+    }
+
+    private void showSnoozeOptions(boolean show) {
+        mSelectedOption.setVisibility(show ? View.GONE : View.VISIBLE);
+        mUndo.setVisibility(show ? View.GONE : View.VISIBLE);
+        mSnoozeOptionView.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
+    private void setTimeToSnooze(int optionId) {
+        long snoozeUntilMillis = Calendar.getInstance().getTimeInMillis();
+        switch (optionId) {
+            case R.string.snooze_option_15_min:
+                snoozeUntilMillis += TimeUnit.MINUTES.toMillis(15);
+                break;
+            case R.string.snooze_option_30_min:
+                snoozeUntilMillis += TimeUnit.MINUTES.toMillis(30);
+                break;
+            case R.string.snooze_option_1_hour:
+                snoozeUntilMillis += TimeUnit.MINUTES.toMillis(60);
+                break;
+        }
+        mTimeToSnooze = snoozeUntilMillis;
+        final Resources res = getResources();
+        String selectedString = String.format(
+                res.getString(R.string.snoozed_for_time), res.getString(optionId));
+        mSelectedOption.setText(selectedString);
+        showSnoozeOptions(false);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (mGutsInteractionListener != null) {
+            mGutsInteractionListener.onInteraction(this);
+        }
+        final int id = v.getId();
+        final Integer tag = (Integer) v.getTag();
+        if (tag != null) {
+            // From the option list
+            setTimeToSnooze(tag);
+        } else if (id == R.id.snooze_option_default) {
+            // Show more snooze options
+            showSnoozeOptions(true);
+        } else if (id == R.id.undo) {
+            mTimeToSnooze = -1;
+            mGutsInteractionListener.closeGuts(this);
+        }
+    }
+
+    @Override
+    public View getContentView() {
+        return this;
+    }
+
+    @Override
+    public void setStatusBarNotification(StatusBarNotification sbn) {
+        mSbn = sbn;
+    }
+
+    @Override
+    public void setInteractionListener(GutsInteractionListener listener) {
+        mGutsInteractionListener = listener;
+    }
+
+    @Override
+    public void setSnoozeListener(SnoozeListener listener) {
+        mSnoozeListener = listener;
+    }
+
+    @Override
+    public boolean handleCloseControls() {
+        // When snooze is closed (i.e. there was interaction outside of the notification)
+        // then we commit the snooze action.
+        if (mSnoozeListener != null && mTimeToSnooze != -1) {
+            mSnoozeListener.snoozeNotification(mSbn, mTimeToSnooze);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 1128101..c8e8973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,15 +16,18 @@
 
 package com.android.systemui.statusbar;
 
+import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
 import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -74,8 +77,10 @@
     private boolean mEthernetVisible = false;
     private int mEthernetIconId = 0;
     private int mLastEthernetIconId = -1;
+    private int mWifiBadgeId = -1;
     private boolean mWifiVisible = false;
     private int mWifiStrengthId = 0;
+    private int mLastWifiBadgeId = -1;
     private int mLastWifiStrengthId = -1;
     private boolean mIsAirplaneMode = false;
     private int mAirplaneIconId = 0;
@@ -259,6 +264,7 @@
             boolean activityIn, boolean activityOut, String description) {
         mWifiVisible = statusIcon.visible && !mBlockWifi;
         mWifiStrengthId = statusIcon.icon;
+        mWifiBadgeId = statusIcon.iconOverlay;
         mWifiDescription = statusIcon.contentDescription;
 
         apply();
@@ -399,6 +405,7 @@
             mWifi.setImageDrawable(null);
             mWifiDark.setImageDrawable(null);
             mLastWifiStrengthId = -1;
+            mLastWifiBadgeId = -1;
         }
 
         for (PhoneState state : mPhoneStates) {
@@ -464,10 +471,16 @@
                     (mEthernetVisible ? "VISIBLE" : "GONE")));
 
         if (mWifiVisible) {
-            if (mWifiStrengthId != mLastWifiStrengthId) {
-                setIconForView(mWifi, mWifiStrengthId);
-                setIconForView(mWifiDark, mWifiStrengthId);
+            if (mWifiStrengthId != mLastWifiStrengthId || mWifiBadgeId != mLastWifiBadgeId) {
+                if (mWifiBadgeId == -1) {
+                    setIconForView(mWifi, mWifiStrengthId);
+                    setIconForView(mWifiDark, mWifiStrengthId);
+                } else {
+                    setBadgedWifiIconForView(mWifi, mWifiStrengthId, mWifiBadgeId);
+                    setBadgedWifiIconForView(mWifiDark, mWifiStrengthId, mWifiBadgeId);
+                }
                 mLastWifiStrengthId = mWifiStrengthId;
+                mLastWifiBadgeId = mWifiBadgeId;
             }
             mWifiGroup.setContentDescription(mWifiDescription);
             mWifiGroup.setVisibility(View.VISIBLE);
@@ -529,6 +542,10 @@
         // Using the imageView's context to retrieve the Drawable so that theme is preserved.
         Drawable icon = imageView.getContext().getDrawable(iconId);
 
+        setScaledIcon(imageView, icon);
+    }
+
+    private void setScaledIcon(ImageView imageView, Drawable icon) {
         if (mIconScaleFactor == 1.f) {
             imageView.setImageDrawable(icon);
         } else {
@@ -536,6 +553,33 @@
         }
     }
 
+    /**
+     * Creates and sets a LayerDrawable from the given ids on the given view.
+     *
+     * <p>This method will also scale the icon by {@link #mIconScaleFactor} if appropriate.
+     */
+    private void setBadgedWifiIconForView(ImageView imageView, @DrawableRes int wifiPieId,
+            @DrawableRes int badgeId) {
+        // Using the imageView's context to retrieve the Drawable so that theme is preserved.;
+        LayerDrawable icon = new LayerDrawable(new Drawable[] {
+                imageView.getContext().getDrawable(wifiPieId),
+                imageView.getContext().getDrawable(badgeId)});
+
+        // The LayerDrawable shares an underlying state so we must mutate the object to change the
+        // color between the light and dark themes.
+        icon.mutate().setTint(getColorAttr(imageView.getContext(), R.attr.singleToneColor));
+
+        setScaledIcon(imageView, icon);
+    }
+
+    /** Returns the given color attribute value, or white if not defined. */
+    @ColorInt private static int getColorAttr(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
+        @ColorInt int colorAccent = ta.getColor(0, Color.WHITE);
+        ta.recycle();
+        return colorAccent;
+    }
+
     public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
         boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity
                 || !mTintArea.equals(tintArea);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
index dd7c4c7..063252f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
@@ -22,7 +22,7 @@
  * A view that can be transformed to and from.
  */
 public interface TransformableView {
-    int TRANSFORMING_VIEW_HEADER = 0;
+    int TRANSFORMING_VIEW_ICON = 0;
     int TRANSFORMING_VIEW_TITLE = 1;
     int TRANSFORMING_VIEW_TEXT = 2;
     int TRANSFORMING_VIEW_IMAGE = 3;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index cd6c31f..1c89e32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -23,6 +23,7 @@
 import android.util.ArraySet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.Interpolator;
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -298,5 +299,14 @@
                 TransformState otherState) {
             return false;
         }
+
+        /**
+         * Get a custom interpolator for this animation
+         * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY
+         * @param isFrom true if this transformation from the other view
+         */
+        public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
+            return null;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
new file mode 100644
index 0000000..de4c312
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
@@ -0,0 +1,79 @@
+/*
+ * 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.systemui.statusbar.notification;
+
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.CrossFadeHelper;
+import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+
+import static com.android.systemui.statusbar.TransformableView.TRANSFORMING_VIEW_TITLE;
+import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
+
+/**
+ * A custom transformation that modifies the interpolator
+ */
+public abstract class CustomInterpolatorTransformation
+        extends ViewTransformationHelper.CustomTransformation {
+
+    private final int mViewType;
+
+    public CustomInterpolatorTransformation(int viewType) {
+        mViewType = viewType;
+    }
+
+    @Override
+    public boolean transformTo(TransformState ownState, TransformableView notification,
+            float transformationAmount) {
+        if (!hasCustomTransformation()) {
+            return false;
+        }
+        TransformState otherState = notification.getCurrentState(mViewType);
+        if (otherState == null) {
+            return false;
+        }
+        View view = ownState.getTransformedView();
+        CrossFadeHelper.fadeOut(view, transformationAmount);
+        ownState.transformViewFullyTo(otherState, this, transformationAmount);
+        otherState.recycle();
+        return true;
+    }
+
+    protected boolean hasCustomTransformation() {
+        return true;
+    }
+
+    @Override
+    public boolean transformFrom(TransformState ownState,
+            TransformableView notification, float transformationAmount) {
+        if (!hasCustomTransformation()) {
+            return false;
+        }
+        TransformState otherState = notification.getCurrentState(mViewType);
+        if (otherState == null) {
+            return false;
+        }
+        View view = ownState.getTransformedView();
+        CrossFadeHelper.fadeIn(view, transformationAmount);
+        ownState.transformViewFullyFrom(otherState, this, transformationAmount);
+        otherState.recycle();
+        return true;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
deleted file mode 100644
index 9501f90..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.util.Pools;
-import android.view.NotificationHeaderView;
-import android.view.View;
-
-import com.android.systemui.statusbar.CrossFadeHelper;
-
-/**
- * A transform state of a text view.
-*/
-public class HeaderTransformState extends TransformState {
-
-    private static Pools.SimplePool<HeaderTransformState> sInstancePool
-            = new Pools.SimplePool<>(40);
-    private View mExpandButton;
-    private View mWorkProfileIcon;
-    private TransformState mWorkProfileState;
-
-    @Override
-    public void initFrom(View view) {
-        super.initFrom(view);
-        if (view instanceof NotificationHeaderView) {
-            NotificationHeaderView header = (NotificationHeaderView) view;
-            mExpandButton = header.getExpandButton();
-            mWorkProfileState = TransformState.obtain();
-            mWorkProfileIcon = header.getWorkProfileIcon();
-            mWorkProfileState.initFrom(mWorkProfileIcon);
-        }
-    }
-
-    @Override
-    public boolean transformViewTo(TransformState otherState, float transformationAmount) {
-        // if the transforming notification has a header, we have ensured that it looks the same
-        // but the expand button, so lets fade just that one and transform the work profile icon.
-        if (!(mTransformedView instanceof NotificationHeaderView)) {
-            return false;
-        }
-        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
-        int childCount = header.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View headerChild = header.getChildAt(i);
-            if (headerChild.getVisibility() == View.GONE) {
-                continue;
-            }
-            if (headerChild != mExpandButton) {
-                headerChild.setVisibility(View.INVISIBLE);
-            } else {
-                CrossFadeHelper.fadeOut(mExpandButton, transformationAmount);
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void transformViewFrom(TransformState otherState, float transformationAmount) {
-        // if the transforming notification has a header, we have ensured that it looks the same
-        // but the expand button, so lets fade just that one and transform the work profile icon.
-        if (!(mTransformedView instanceof NotificationHeaderView)) {
-            return;
-        }
-        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
-        header.setVisibility(View.VISIBLE);
-        header.setAlpha(1.0f);
-        int childCount = header.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View headerChild = header.getChildAt(i);
-            if (headerChild.getVisibility() == View.GONE) {
-                continue;
-            }
-            if (headerChild == mExpandButton) {
-                CrossFadeHelper.fadeIn(mExpandButton, transformationAmount);
-            } else {
-                headerChild.setVisibility(View.VISIBLE);
-                if (headerChild == mWorkProfileIcon) {
-                    mWorkProfileState.transformViewFullyFrom(
-                            ((HeaderTransformState) otherState).mWorkProfileState,
-                            transformationAmount);
-                }
-            }
-        }
-        return;
-    }
-
-    public static HeaderTransformState obtain() {
-        HeaderTransformState instance = sInstancePool.acquire();
-        if (instance != null) {
-            return instance;
-        }
-        return new HeaderTransformState();
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        sInstancePool.release(this);
-    }
-
-    @Override
-    protected void reset() {
-        super.reset();
-        mExpandButton = null;
-        mWorkProfileState = null;
-        if (mWorkProfileState != null) {
-            mWorkProfileState.recycle();
-            mWorkProfileState = null;
-        }
-    }
-
-    @Override
-    public void setVisible(boolean visible, boolean force) {
-        super.setVisible(visible, force);
-        if (!(mTransformedView instanceof NotificationHeaderView)) {
-            return;
-        }
-        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
-        int childCount = header.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View headerChild = header.getChildAt(i);
-            if (!force && headerChild.getVisibility() == View.GONE) {
-                continue;
-            }
-            headerChild.animate().cancel();
-            if (headerChild.getVisibility() != View.GONE) {
-                headerChild.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-            }
-            if (headerChild == mExpandButton) {
-                headerChild.setAlpha(visible ? 1.0f : 0.0f);
-            }
-            if (headerChild == mWorkProfileIcon) {
-                headerChild.setTranslationX(0);
-                headerChild.setTranslationY(0);
-            }
-        }
-    }
-
-    @Override
-    public void prepareFadeIn() {
-        super.prepareFadeIn();
-        if (!(mTransformedView instanceof NotificationHeaderView)) {
-            return;
-        }
-        NotificationHeaderView header = (NotificationHeaderView) mTransformedView;
-        int childCount = header.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View headerChild = header.getChildAt(i);
-            if (headerChild.getVisibility() == View.GONE) {
-                continue;
-            }
-            headerChild.animate().cancel();
-            headerChild.setVisibility(View.VISIBLE);
-            headerChild.setAlpha(1.0f);
-            if (headerChild == mWorkProfileIcon) {
-                headerChild.setTranslationX(0);
-                headerChild.setTranslationY(0);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
index 6084770..78b967a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigPictureTemplateViewWrapper.java
@@ -36,8 +36,8 @@
     }
 
     @Override
-    public void notifyContentUpdated(StatusBarNotification notification) {
-        super.notifyContentUpdated(notification);
+    public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+        super.notifyContentUpdated(notification, isLowPriority);
         updateImageTag(notification);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
index 3f49125..39db243 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationBigTextTemplateViewWrapper.java
@@ -41,11 +41,11 @@
     }
 
     @Override
-    public void notifyContentUpdated(StatusBarNotification notification) {
+    public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
         // Reinspect the notification. Before the super call, because the super call also updates
         // the transformation types and we need to have our values set by then.
         resolveViews(notification);
-        super.notifyContentUpdated(notification);
+        super.notifyContentUpdated(notification, isLowPriority);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 85e87dd..3b18886 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -40,9 +40,6 @@
 
     private final ViewInvertHelper mInvertHelper;
     private final Paint mGreyPaint = new Paint();
-    private int mBackgroundColor = 0;
-    private static final int CUSTOM_BACKGROUND_TAG = R.id.custom_background_color;
-    private boolean mShouldInvertDark;
     private boolean mShowingLegacyBackground;
 
     protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
@@ -106,32 +103,6 @@
     }
 
     @Override
-    public void notifyContentUpdated(StatusBarNotification notification) {
-        super.notifyContentUpdated(notification);
-        Drawable background = mView.getBackground();
-        mBackgroundColor = 0;
-        if (background instanceof ColorDrawable) {
-            mBackgroundColor = ((ColorDrawable) background).getColor();
-            mView.setBackground(null);
-            mView.setTag(CUSTOM_BACKGROUND_TAG, mBackgroundColor);
-        } else if (mView.getTag(CUSTOM_BACKGROUND_TAG) != null) {
-            mBackgroundColor = (int) mView.getTag(CUSTOM_BACKGROUND_TAG);
-        }
-        mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor);
-    }
-
-    private boolean isColorLight(int backgroundColor) {
-        return Color.alpha(backgroundColor) == 0
-                || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
-    }
-
-    @Override
-    public int getCustomBackgroundColor() {
-        // Parent notifications should always use the normal background color
-        return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
-    }
-
-    @Override
     public void setShowingLegacyBackground(boolean showing) {
         super.setShowingLegacyBackground(showing);
         mShowingLegacyBackground = showing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 3e4c758..8eab2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -31,8 +31,12 @@
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.widget.ImageView;
+import android.widget.TextView;
 
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -42,17 +46,21 @@
 
 import java.util.Stack;
 
+import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
+
 /**
  * Wraps a notification header view.
  */
 public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
 
+    private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
+            = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
     private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
             0, PorterDuff.Mode.SRC_ATOP);
     private final int mIconDarkAlpha;
     private final int mIconDarkColor = 0xffffffff;
-    protected final ViewInvertHelper mInvertHelper;
 
+    protected final ViewInvertHelper mInvertHelper;
     protected final ViewTransformationHelper mTransformationHelper;
 
     protected int mColor;
@@ -60,19 +68,50 @@
 
     private ImageView mExpandButton;
     private NotificationHeaderView mNotificationHeader;
+    private TextView mHeaderText;
+    private ImageView mWorkProfileImage;
+    private boolean mIsLowPriority;
 
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(view, row);
         mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
         mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
         mTransformationHelper = new ViewTransformationHelper();
+
+        // we want to avoid that the header clashes with the other text when transforming
+        // low-priority
+        mTransformationHelper.setCustomTransformation(
+                new CustomInterpolatorTransformation(TRANSFORMING_VIEW_TITLE) {
+
+                    @Override
+                    public Interpolator getCustomInterpolator(int interpolationType,
+                            boolean isFrom) {
+                        boolean isLowPriority = mView instanceof NotificationHeaderView;
+                        if (interpolationType == TRANSFORM_Y) {
+                            if (isLowPriority && !isFrom
+                                    || !isLowPriority && isFrom) {
+                                return Interpolators.LINEAR_OUT_SLOW_IN;
+                            } else {
+                                return LOW_PRIORITY_HEADER_CLOSE;
+                            }
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    protected boolean hasCustomTransformation() {
+                        return mIsLowPriority;
+                    }
+                }, TRANSFORMING_VIEW_TITLE);
         resolveHeaderViews();
         updateInvertHelper();
     }
 
     protected void resolveHeaderViews() {
         mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+        mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
         mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
+        mWorkProfileImage = (ImageView) mView.findViewById(com.android.internal.R.id.profile_badge);
         mColor = resolveColor(mExpandButton);
         mNotificationHeader = (NotificationHeaderView) mView.findViewById(
                 com.android.internal.R.id.notification_header);
@@ -89,9 +128,9 @@
     }
 
     @Override
-    public void notifyContentUpdated(StatusBarNotification notification) {
-        super.notifyContentUpdated(notification);
-
+    public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
+        super.notifyContentUpdated(notification, isLowPriority);
+        mIsLowPriority = isLowPriority;
         ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
 
         // Reinspect the notification.
@@ -100,6 +139,11 @@
         updateTransformedTypes();
         addRemainingTransformTypes();
         updateCropToPaddingForImageViews();
+        mIcon.setTag(ImageTransformState.ICON_TAG, notification.getNotification().getSmallIcon());
+        // The work profile image is always the same lets just set the icon tag for it not to
+        // animate
+        mWorkProfileImage.setTag(ImageTransformState.ICON_TAG,
+                notification.getNotification().getSmallIcon());
 
         // We need to reset all views that are no longer transforming in case a view was previously
         // transformed, but now we decided to transform its container instead.
@@ -154,8 +198,11 @@
 
     protected void updateTransformedTypes() {
         mTransformationHelper.reset();
-        mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER,
-                mNotificationHeader);
+        mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
+        if (mIsLowPriority) {
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
+                    mHeaderText);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index 4ce330c..04ee6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -40,11 +40,11 @@
     }
 
     @Override
-    public void notifyContentUpdated(StatusBarNotification notification) {
+    public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
         // Reinspect the notification. Before the super call, because the super call also updates
         // the transformation types and we need to have our values set by then.
         resolveViews(notification);
-        super.notifyContentUpdated(notification);
+        super.notifyContentUpdated(notification, isLowPriority);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index ff2febf..defeab2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -22,7 +22,11 @@
 
 import android.content.Context;
 import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
 import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
 
 /**
  * Wraps a notification containing a messaging template
@@ -30,6 +34,7 @@
 public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
     private View mContractedMessage;
+    private ArrayList<View> mHistoricMessages = new ArrayList<View>();
 
     protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
@@ -44,21 +49,33 @@
                 && ((MessagingLinearLayout) container).getChildCount() > 0) {
             MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
 
-            // Only consider the first child - transforming to a position other than the first
-            // looks bad because we have to move across other messages that are fading in.
-            View child = messagingContainer.getChildAt(0);
-            if (child.getId() == messagingContainer.getContractedChildId()) {
-                mContractedMessage = child;
+            int childCount = messagingContainer.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = messagingContainer.getChildAt(i);
+
+                if (child.getVisibility() == View.GONE
+                        && child instanceof TextView
+                        && !TextUtils.isEmpty(((TextView) child).getText())) {
+                    mHistoricMessages.add(child);
+                }
+
+                // Only consider the first visible child - transforming to a position other than the
+                // first looks bad because we have to move across other messages that are fading in.
+                if (child.getId() == messagingContainer.getContractedChildId()) {
+                    mContractedMessage = child;
+                } else if (child.getVisibility() == View.VISIBLE) {
+                    break;
+                }
             }
         }
     }
 
     @Override
-    public void notifyContentUpdated(StatusBarNotification notification) {
+    public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
         // Reinspect the notification. Before the super call, because the super call also updates
         // the transformation types and we need to have our values set by then.
         resolveViews();
-        super.notifyContentUpdated(notification);
+        super.notifyContentUpdated(notification, isLowPriority);
     }
 
     @Override
@@ -70,4 +87,11 @@
                     mContractedMessage);
         }
     }
+
+    @Override
+    public void setRemoteInputVisible(boolean visible) {
+        for (int i = 0; i < mHistoricMessages.size(); i++) {
+            mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index b984c0b..e9956ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -133,11 +133,11 @@
     }
 
     @Override
-    public void notifyContentUpdated(StatusBarNotification notification) {
+    public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
         // Reinspect the notification. Before the super call, because the super call also updates
         // the transformation types and we need to have our values set by then.
         resolveTemplateViews(notification);
-        super.notifyContentUpdated(notification);
+        super.notifyContentUpdated(notification, isLowPriority);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 16348dfe..836482a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -19,12 +19,17 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.ColorMatrix;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.service.notification.StatusBarNotification;
+import android.support.v4.graphics.ColorUtils;
 import android.view.NotificationHeaderView;
 import android.view.View;
 
 import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
@@ -40,6 +45,8 @@
     protected final View mView;
     protected final ExpandableNotificationRow mRow;
     protected boolean mDark;
+    private int mBackgroundColor = 0;
+    protected boolean mShouldInvertDark;
     protected boolean mDarkInitialized = false;
 
     public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
@@ -80,11 +87,24 @@
 
     /**
      * Notifies this wrapper that the content of the view might have changed.
-     * @param notification
+     * @param notification the notification this is wrapped around
+     * @param isLowPriority is this notification low priority
      */
-    public void notifyContentUpdated(StatusBarNotification notification) {
+    public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
         mDarkInitialized = false;
-    };
+        Drawable background = mView.getBackground();
+        mBackgroundColor = 0;
+        if (background instanceof ColorDrawable) {
+            mBackgroundColor = ((ColorDrawable) background).getColor();
+            mView.setBackground(null);
+        }
+        mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor);
+    }
+
+    private boolean isColorLight(int backgroundColor) {
+        return Color.alpha(backgroundColor) == 0
+                || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
+    }
 
 
     protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
@@ -155,7 +175,8 @@
     }
 
     public int getCustomBackgroundColor() {
-        return 0;
+        // Parent notifications should always use the normal background color
+        return mRow.isSummaryWithChildren() ? 0 : mBackgroundColor;
     }
 
     public void setShowingLegacyBackground(boolean showing) {
@@ -163,4 +184,7 @@
 
     public void setContentHeight(int contentHeight, int minHeightHint) {
     }
+
+    public void setRemoteInputVisible(boolean visible) {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 770ec95..d15ab10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -18,10 +18,10 @@
 
 import android.util.ArraySet;
 import android.util.Pools;
-import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.animation.Interpolator;
 import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
@@ -38,10 +38,11 @@
 */
 public class TransformState {
 
+    public static final int TRANSFORM_X = 0x1;
+    public static final int TRANSFORM_Y = 0x10;
+    public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
+
     private static final float UNDEFINED = -1f;
-    private static final int TRANSOFORM_X = 0x1;
-    private static final int TRANSOFORM_Y = 0x10;
-    private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y;
     private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
     private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
     private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
@@ -80,25 +81,31 @@
     }
 
     public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
-        transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount);
+        transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
+    }
+
+    public void transformViewFullyFrom(TransformState otherState,
+            ViewTransformationHelper.CustomTransformation customTransformation,
+            float transformationAmount) {
+        transformViewFrom(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
     }
 
     public void transformViewVerticalFrom(TransformState otherState,
             ViewTransformationHelper.CustomTransformation customTransformation,
             float transformationAmount) {
-        transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
+        transformViewFrom(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
     }
 
     public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
-        transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount);
+        transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
     }
 
     private void transformViewFrom(TransformState otherState, int transformationFlags,
             ViewTransformationHelper.CustomTransformation customTransformation,
             float transformationAmount) {
         final View transformedView = mTransformedView;
-        boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
-        boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+        boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
+        boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
         boolean transformScale = transformScale();
         // lets animate the positions correctly
         if (transformationAmount == 0.0f
@@ -153,14 +160,30 @@
         float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
                 transformationAmount);
         if (transformX) {
+            float interpolation = interpolatedValue;
+            if (customTransformation != null) {
+                Interpolator customInterpolator =
+                        customTransformation.getCustomInterpolator(TRANSFORM_X, true /* isFrom */);
+                if (customInterpolator != null) {
+                    interpolation = customInterpolator.getInterpolation(transformationAmount);
+                }
+            }
             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
                     0.0f,
-                    interpolatedValue));
+                    interpolation));
         }
         if (transformY) {
+            float interpolation = interpolatedValue;
+            if (customTransformation != null) {
+                Interpolator customInterpolator =
+                        customTransformation.getCustomInterpolator(TRANSFORM_Y, true /* isFrom */);
+                if (customInterpolator != null) {
+                    interpolation = customInterpolator.getInterpolation(transformationAmount);
+                }
+            }
             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
                     0.0f,
-                    interpolatedValue));
+                    interpolation));
         }
         if (transformScale) {
             float transformationStartScaleX = getTransformationStartScaleX();
@@ -207,17 +230,23 @@
     }
 
     public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
-        transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount);
+        transformViewTo(otherState, TRANSFORM_ALL, null, transformationAmount);
+    }
+
+    public void transformViewFullyTo(TransformState otherState,
+            ViewTransformationHelper.CustomTransformation customTransformation,
+            float transformationAmount) {
+        transformViewTo(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
     }
 
     public void transformViewVerticalTo(TransformState otherState,
             ViewTransformationHelper.CustomTransformation customTransformation,
             float transformationAmount) {
-        transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
+        transformViewTo(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
     }
 
     public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
-        transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount);
+        transformViewTo(otherState, TRANSFORM_Y, null, transformationAmount);
     }
 
     private void transformViewTo(TransformState otherState, int transformationFlags,
@@ -226,8 +255,8 @@
         // lets animate the positions correctly
 
         final View transformedView = mTransformedView;
-        boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
-        boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+        boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
+        boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
         boolean transformScale = transformScale();
         // lets animate the positions correctly
         if (transformationAmount == 0.0f) {
@@ -264,23 +293,37 @@
         int[] ownPosition = getLaidOutLocationOnScreen();
         if (transformX) {
             float endX = otherStablePosition[0] - ownPosition[0];
-            if (customTransformation != null
-                    && customTransformation.customTransformTarget(this, otherState)) {
-                endX = mTransformationEndX;
+            float interpolation = interpolatedValue;
+            if (customTransformation != null) {
+                if (customTransformation.customTransformTarget(this, otherState)) {
+                    endX = mTransformationEndX;
+                }
+                Interpolator customInterpolator =
+                        customTransformation.getCustomInterpolator(TRANSFORM_X, false /* isFrom */);
+                if (customInterpolator != null) {
+                    interpolation = customInterpolator.getInterpolation(transformationAmount);
+                }
             }
             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
                     endX,
-                    interpolatedValue));
+                    interpolation));
         }
         if (transformY) {
             float endY = otherStablePosition[1] - ownPosition[1];
-            if (customTransformation != null
-                    && customTransformation.customTransformTarget(this, otherState)) {
-                endY = mTransformationEndY;
+            float interpolation = interpolatedValue;
+            if (customTransformation != null) {
+                if (customTransformation.customTransformTarget(this, otherState)) {
+                    endY = mTransformationEndY;
+                }
+                Interpolator customInterpolator =
+                        customTransformation.getCustomInterpolator(TRANSFORM_Y, false /* isFrom */);
+                if (customInterpolator != null) {
+                    interpolation = customInterpolator.getInterpolation(transformationAmount);
+                }
             }
             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
                     endY,
-                    interpolatedValue));
+                    interpolation));
         }
         if (transformScale) {
             View otherView = otherState.getTransformedView();
@@ -402,11 +445,6 @@
             result.initFrom(view);
             return result;
         }
-        if (view instanceof NotificationHeaderView) {
-            HeaderTransformState result = HeaderTransformState.obtain();
-            result.initFrom(view);
-            return result;
-        }
         if (view instanceof ImageView) {
             ImageTransformState result = ImageTransformState.obtain();
             result.initFrom(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 695b500..ef42b2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -29,11 +29,12 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.policy.AccessibilityController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 
 /**
  * Manages the different states and animations of the unlock icon.
  */
-public class LockIcon extends KeyguardAffordanceView {
+public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChangedListener {
 
     private static final int FP_DRAW_OFF_TIMEOUT = 800;
 
@@ -49,6 +50,7 @@
     private boolean mDeviceInteractive;
     private boolean mScreenOn;
     private boolean mLastScreenOn;
+    private Drawable mUserAvatarIcon;
     private TrustDrawable mTrustDrawable;
     private final UnlockMethodCache mUnlockMethodCache;
     private AccessibilityController mAccessibilityController;
@@ -80,6 +82,12 @@
         mTrustDrawable.stop();
     }
 
+    @Override
+    public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+        mUserAvatarIcon = picture;
+        update();
+    }
+
     public void setTransientFpError(boolean transientFpError) {
         mTransientFpError = transientFpError;
         update();
@@ -126,27 +134,33 @@
         boolean trustHidden = anyFingerprintIcon;
         if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive
                 || mScreenOn != mLastScreenOn || force) {
-            boolean isAnim = true;
-            int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
+            int iconAnimRes =
+                getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
                     mDeviceInteractive, mLastScreenOn, mScreenOn);
-            if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
+            boolean isAnim = iconAnimRes != -1;
+            if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
                 anyFingerprintIcon = true;
                 useAdditionalPadding = true;
                 trustHidden = true;
-            } else if (iconRes == R.drawable.trusted_state_to_error_animation) {
+            } else if (iconAnimRes == R.drawable.trusted_state_to_error_animation) {
                 anyFingerprintIcon = true;
                 useAdditionalPadding = false;
                 trustHidden = true;
-            } else if (iconRes == R.drawable.error_to_trustedstate_animation) {
+            } else if (iconAnimRes == R.drawable.error_to_trustedstate_animation) {
                 anyFingerprintIcon = true;
                 useAdditionalPadding = false;
                 trustHidden = false;
             }
-            if (iconRes == -1) {
-                iconRes = getIconForState(state, mScreenOn, mDeviceInteractive);
-                isAnim = false;
+
+            Drawable icon;
+            if (isAnim) {
+                // Load the animation resource.
+                icon = mContext.getDrawable(iconAnimRes);
+            } else {
+                // Load the static icon resource based on the current state.
+                icon = getIconForState(state, mScreenOn, mDeviceInteractive);
             }
-            Drawable icon = mContext.getDrawable(iconRes);
+
             final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
                     ? (AnimatedVectorDrawable) icon
                     : null;
@@ -175,7 +189,7 @@
                 animation.start();
             }
 
-            if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
+            if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
                 removeCallbacks(mDrawOffTimeout);
                 postDelayed(mDrawOffTimeout, FP_DRAW_OFF_TIMEOUT);
             } else {
@@ -225,25 +239,38 @@
         mAccessibilityController = accessibilityController;
     }
 
-    private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
+    private Drawable getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
+        int iconRes;
         switch (state) {
             case STATE_LOCKED:
-                return R.drawable.ic_lock_24dp;
+                iconRes = R.drawable.ic_lock_24dp;
+                break;
             case STATE_LOCK_OPEN:
-                return R.drawable.ic_lock_open_24dp;
+                if (mUnlockMethodCache.isTrustManaged() && mUnlockMethodCache.isTrusted()
+                    && mUserAvatarIcon != null) {
+                    return mUserAvatarIcon;
+                } else {
+                    iconRes = R.drawable.ic_lock_open_24dp;
+                }
+                break;
             case STATE_FACE_UNLOCK:
-                return com.android.internal.R.drawable.ic_account_circle;
+                iconRes = com.android.internal.R.drawable.ic_account_circle;
+                break;
             case STATE_FINGERPRINT:
                 // If screen is off and device asleep, use the draw on animation so the first frame
                 // gets drawn.
-                return screenOn && deviceInteractive
+                iconRes = screenOn && deviceInteractive
                         ? R.drawable.ic_fingerprint
                         : R.drawable.lockscreen_fingerprint_draw_on_animation;
+                break;
             case STATE_FINGERPRINT_ERROR:
-                return R.drawable.ic_fingerprint_error;
+                iconRes = R.drawable.ic_fingerprint_error;
+                break;
             default:
                 throw new IllegalArgumentException();
         }
+
+        return mContext.getDrawable(iconRes);
     }
 
     private int getAnimationResForTransition(int oldState, int newState,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 3c46d26..d40326a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -546,8 +546,7 @@
 
     private boolean onLongPressRecents() {
         if (mRecents == null || !ActivityManager.supportsMultiWindow()
-                || !mDivider.getView().getSnapAlgorithm()
-                .isSplitScreenFeasible()) {
+                || !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()) {
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index f04a9ee..9b4867e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -17,6 +17,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.view.Display;
@@ -47,6 +48,8 @@
     private static final String TAG = "NavBarInflater";
 
     public static final String NAV_BAR_VIEWS = "sysui_nav_bar";
+    public static final String NAV_BAR_LEFT = "sysui_nav_bar_left";
+    public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right";
 
     public static final String MENU_IME = "menu_ime";
     public static final String BACK = "back";
@@ -55,6 +58,8 @@
     public static final String NAVSPACE = "space";
     public static final String CLIPBOARD = "clipboard";
     public static final String KEY = "key";
+    public static final String LEFT = "left";
+    public static final String RIGHT = "right";
 
     public static final String GRAVITY_SEPARATOR = ";";
     public static final String BUTTON_SEPARATOR = ",";
@@ -130,7 +135,8 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+        TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS, NAV_BAR_LEFT,
+                NAV_BAR_RIGHT);
         PluginManager.getInstance(getContext()).addPluginListener(NavBarButtonProvider.ACTION, this,
                 NavBarButtonProvider.VERSION, true /* Allow multiple */);
     }
@@ -148,6 +154,9 @@
                 clearViews();
                 inflateLayout(newValue);
             }
+        } else if (NAV_BAR_LEFT.equals(key) || NAV_BAR_RIGHT.equals(key)) {
+            clearViews();
+            inflateLayout(mCurrentLayout);
         }
     }
 
@@ -268,6 +277,13 @@
             boolean landscape) {
         View v = null;
         String button = extractButton(buttonSpec);
+        if (LEFT.equals(button)) {
+            buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_LEFT, NAVSPACE);
+            button = extractButton(buttonSpec);
+        } else if (RIGHT.equals(button)) {
+            buttonSpec = TunerService.get(mContext).getValue(NAV_BAR_RIGHT, MENU_IME);
+            button = extractButton(buttonSpec);
+        }
         // Let plugins go first so they can override a standard view if they want.
         for (NavBarButtonProvider provider : mPlugins) {
             v = provider.createView(buttonSpec, parent);
@@ -291,7 +307,14 @@
             v = inflater.inflate(R.layout.custom_key, parent, false);
             ((KeyButtonView) v).setCode(code);
             if (uri != null) {
-                ((KeyButtonView) v).loadAsync(uri);
+                if (uri.contains(":")) {
+                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
+                } else if (uri.contains("/")) {
+                    int index = uri.indexOf('/');
+                    String pkg = uri.substring(0, index);
+                    int id = Integer.parseInt(uri.substring(index + 1));
+                    ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
+                }
             }
         }
         return v;
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 345dcbd..32b9969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -154,18 +154,6 @@
                 NotificationShelf.SHOW_AMBIENT_ICONS);
 
         applyNotificationIconsTint();
-        ArrayList<NotificationData.Entry> activeNotifications
-                = notificationData.getActiveNotifications();
-        for (int i = 0; i < activeNotifications.size(); i++) {
-            NotificationData.Entry entry = activeNotifications.get(i);
-            boolean isPreL = Boolean.TRUE.equals(entry.expandedIcon.getTag(R.id.icon_is_pre_L));
-            boolean colorize = !isPreL
-                    || NotificationUtils.isGrayscale(entry.expandedIcon, mNotificationColorUtil);
-            if (colorize) {
-                int color = entry.getContrastedColor(mContext);
-                entry.expandedIcon.setImageTintList(ColorStateList.valueOf(color));
-            }
-        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 3291d59..7d82477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -141,6 +141,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.ActivityStarter;
 import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -205,7 +206,7 @@
 
 public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
         DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
-        OnHeadsUpChangedListener, VisualStabilityManager.Callback {
+        OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener {
     static final String TAG = "PhoneStatusBar";
     public static final boolean DEBUG = BaseStatusBar.DEBUG;
     public static final boolean SPEW = false;
@@ -801,7 +802,8 @@
                 (KeyguardStatusView) mStatusBarWindow.findViewById(R.id.keyguard_status_view);
         mKeyguardBottomArea =
                 (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
-        mKeyguardIndicationController = new KeyguardIndicationController(mContext,
+        mKeyguardIndicationController =
+                SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
                 (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
                 mKeyguardBottomArea.getLockIcon());
         mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
@@ -1179,6 +1181,8 @@
                 mFingerprintUnlockController);
         mKeyguardIndicationController.setStatusBarKeyguardViewManager(
                 mStatusBarKeyguardViewManager);
+        mKeyguardIndicationController.setUserInfoController(
+                Dependency.get(UserInfoController.class));
         mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mIconPolicy.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
@@ -1388,7 +1392,7 @@
             newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
 
             StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
-                    sbn.getOpPkg(), sbn.getNotificationChannel(),
+                    sbn.getOpPkg(),
                     sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
                     newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
 
@@ -3580,7 +3584,8 @@
     public void postQSRunnableDismissingKeyguard(final Runnable runnable) {
         mHandler.post(() -> {
             mLeaveOpenOnKeyguardHide = true;
-            executeRunnableDismissingKeyguard(runnable, null, false, false, false);
+            executeRunnableDismissingKeyguard(() -> mHandler.post(runnable), null, false, false,
+                    false);
         });
     }
 
@@ -4604,12 +4609,13 @@
         return !mNotificationData.getActiveNotifications().isEmpty();
     }
 
-    public void wakeUpIfDozing(long time, MotionEvent event) {
+    @Override
+    public void wakeUpIfDozing(long time, PointF where) {
         if (mDozing && mDozeScrimController.isPulsing()) {
             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
             pm.wakeUp(time, "com.android.systemui:NODOZE");
             mWakeUpComingFromTouch = true;
-            mWakeUpTouchLocation = new PointF(event.getX(), event.getY());
+            mWakeUpTouchLocation = where;
             mNotificationPanel.setTouchDisabled(false);
             mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
             mFalsingManager.onScreenOnFromTouch();
@@ -4853,4 +4859,14 @@
         }
 
     }
+
+    @Override
+    public SnoozeListener getSnoozeListener() {
+        return this;
+    }
+
+    @Override
+    public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil) {
+        setNotificationSnoozed(sbn, snoozeUntil);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 9e93802..5536209 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -157,11 +157,15 @@
         // Set the light/dark theming on the header status UI to match the current theme.
         SignalClusterView cluster = (SignalClusterView) findViewById(R.id.signal_cluster);
         int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
-        float intensity = colorForeground / (float) Color.WHITE;
+        float intensity = colorForeground == Color.WHITE ? 0 : 1;
         cluster.setIconTint(colorForeground, intensity, new Rect(0, 0, 0, 0));
         BatteryMeterView battery = (BatteryMeterView) findViewById(R.id.battery);
         int colorSecondary = Utils.getColorAttr(getContext(), android.R.attr.textColorSecondary);
         battery.setRawColors(colorForeground, colorSecondary);
+
+        mNextAlarmController = Dependency.get(NextAlarmController.class);
+        mUserInfoController = Dependency.get(UserInfoController.class);
+        mActivityStarter = Dependency.get(ActivityStarter.class);
     }
 
     @Override
@@ -260,14 +264,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mNextAlarmController = Dependency.get(NextAlarmController.class);
-        mUserInfoController = Dependency.get(UserInfoController.class);
-        mActivityStarter = Dependency.get(ActivityStarter.class);
-    }
-
-    @Override
     @VisibleForTesting
     public void onDetachedFromWindow() {
         setListening(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 1b73a3f..ccd6357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -29,6 +29,7 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
 import android.media.session.MediaSessionLegacyHelper;
 import android.net.Uri;
 import android.os.Bundle;
@@ -223,7 +224,8 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
                 if (mService.isDozing()) {
-                    MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, true);
+                    MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+                            event, AudioManager.USE_DEFAULT_STREAM_TYPE, true);
                     return true;
                 }
                 break;
@@ -263,12 +265,9 @@
         if (mNotificationPanel.isFullyExpanded()
                 && mStackScrollLayout.getVisibility() == View.VISIBLE
                 && mService.getBarState() == StatusBarState.KEYGUARD
-                && !mService.isBouncerShowing()) {
+                && !mService.isBouncerShowing()
+                && !mService.isDozing()) {
             intercept = mDragDownHelper.onInterceptTouchEvent(ev);
-            // wake up on a touch down event, if dozing
-            if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                mService.wakeUpIfDozing(ev.getEventTime(), ev);
-            }
         }
         if (!intercept) {
             super.onInterceptTouchEvent(ev);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index 0fc300d..528fefe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -91,9 +91,11 @@
 
     @Override
     public void onUserSwitched(int newUserId) {
-        stopListening();
-        startListening(newUserId);
-        notifyUserChanged();
+        mContentResolver.unregisterContentObserver(mSettingsObserver);
+        mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
+                mSettingsObserver, 0);
+        mContentResolver.registerContentObserver(mUserSetupUri, true,
+                mSettingsObserver, newUserId);
         notifyUserChanged();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 45cfbdc..882902e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -116,18 +116,18 @@
         mOnClickListener = onClickListener;
     }
 
-    public void loadAsync(String uri) {
-        new AsyncTask<String, Void, Drawable>() {
+    public void loadAsync(Icon icon) {
+        new AsyncTask<Icon, Void, Drawable>() {
             @Override
-            protected Drawable doInBackground(String... params) {
-                return Icon.createWithContentUri(params[0]).loadDrawable(mContext);
+            protected Drawable doInBackground(Icon... params) {
+                return params[0].loadDrawable(mContext);
             }
 
             @Override
             protected void onPostExecute(Drawable drawable) {
                 setImageDrawable(drawable);
             }
-        }.execute(uri);
+        }.execute(icon);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index a22fc6b..a3a9d71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.telephony.SubscriptionInfo;
+import android.view.View;
+
 import com.android.settingslib.net.DataUsageController;
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.systemui.DemoMode;
@@ -68,15 +70,29 @@
 
     public static class IconState {
         public final boolean visible;
+
         public final int icon;
+
+        /**
+         * Optional iconOverlay resource id.
+         *
+         * <p>Set to -1 if not present.
+         */
+        public final int iconOverlay;
+
         public final String contentDescription;
 
-        public IconState(boolean visible, int icon, String contentDescription) {
+        public IconState(boolean visible, int icon, int iconOverlay, String contentDescription) {
             this.visible = visible;
             this.icon = icon;
+            this.iconOverlay = iconOverlay;
             this.contentDescription = contentDescription;
         }
 
+        public IconState(boolean visible, int icon, String contentDescription) {
+            this(visible, icon, -1 /* iconOverlay */, contentDescription);
+        }
+
         public IconState(boolean visible, int icon, int contentDescription,
                 Context context) {
             this(visible, icon, context.getString(contentDescription));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index edf2c8a..5e13f59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -88,6 +89,7 @@
     private final DataSaverController mDataSaverController;
     private final CurrentUserTracker mUserTracker;
     private Config mConfig;
+    private final NetworkScoreManager mNetworkScoreManager;
 
     // Subcontrollers.
     @VisibleForTesting
@@ -145,9 +147,12 @@
     public NetworkControllerImpl(Context context, Looper bgLooper,
             DeviceProvisionedController deviceProvisionedController) {
         this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
+                context.getSystemService(NetworkScoreManager.class),
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
-                SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
+                SubscriptionManager.from(context),
+                Config.readConfig(context),
+                bgLooper,
                 new CallbackHandler(),
                 new AccessPointControllerImpl(context, bgLooper),
                 new DataUsageController(context),
@@ -158,8 +163,12 @@
 
     @VisibleForTesting
     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
-            TelephonyManager telephonyManager, WifiManager wifiManager,
-            SubscriptionManager subManager, Config config, Looper bgLooper,
+            NetworkScoreManager networkScoreManager,
+            TelephonyManager telephonyManager,
+            WifiManager wifiManager,
+            SubscriptionManager subManager,
+            Config config,
+            Looper bgLooper,
             CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
             DataUsageController dataUsageController,
@@ -182,6 +191,7 @@
 
         // wifi
         mWifiManager = wifiManager;
+        mNetworkScoreManager = networkScoreManager;
 
         mLocale = mContext.getResources().getConfiguration().locale;
         mAccessPoints = accessPointController;
@@ -195,7 +205,7 @@
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
-                mCallbackHandler, this);
+                mCallbackHandler, this, mNetworkScoreManager);
 
         mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
 
@@ -799,6 +809,8 @@
                 MobileSignalController controller = mMobileSignalControllers
                         .values().toArray(new MobileSignalController[0])[slot];
                 controller.getState().dataSim = datatype != null;
+                controller.getState().isDefault = datatype != null;
+                controller.getState().dataConnected = datatype != null;
                 if (datatype != null) {
                     controller.getState().iconGroup =
                             datatype.equals("1x") ? TelephonyIcons.ONE_X :
@@ -811,6 +823,7 @@
                             datatype.equals("lte") ? TelephonyIcons.LTE :
                             datatype.equals("lte+") ? TelephonyIcons.LTE_PLUS :
                             datatype.equals("roam") ? TelephonyIcons.ROAMING :
+                            datatype.equals("dis") ? TelephonyIcons.DATA_DISABLED :
                             TelephonyIcons.UNKNOWN;
                 }
                 int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
@@ -836,9 +849,11 @@
     private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
         SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
                 null, 0, 0, "");
-        mMobileSignalControllers.put(id, new MobileSignalController(mContext,
+        MobileSignalController controller = new MobileSignalController(mContext,
                 mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
-                mSubDefaults, mReceiverHandler.getLooper()));
+                mSubDefaults, mReceiverHandler.getLooper());
+        mMobileSignalControllers.put(id, controller);
+        controller.getState().userSetup = true;
         return info;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 44ec283..784f25e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.stack.ScrollContainer;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
@@ -90,6 +91,7 @@
     private int mRevealR;
 
     private boolean mResetting;
+    private NotificationViewWrapper mWrapper;
 
     public RemoteInputView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -210,11 +212,17 @@
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         setVisibility(INVISIBLE);
+                        if (mWrapper != null) {
+                            mWrapper.setRemoteInputVisible(false);
+                        }
                     }
                 });
                 reveal.start();
             } else {
                 setVisibility(INVISIBLE);
+                if (mWrapper != null) {
+                    mWrapper.setRemoteInputVisible(false);
+                }
             }
         }
         MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_CLOSE,
@@ -267,6 +275,9 @@
                 mEntry.notification.getPackageName());
 
         setVisibility(VISIBLE);
+        if (mWrapper != null) {
+            mWrapper.setRemoteInputVisible(true);
+        }
         mController.addRemoteInput(mEntry, mToken);
         mEditText.setInnerFocusable(true);
         mEditText.mShowImeOnInputConnection = true;
@@ -283,6 +294,10 @@
             // Update came in after we sent the reply, time to reset.
             reset();
         }
+
+        if (isActive() && mWrapper != null) {
+            mWrapper.setRemoteInputVisible(true);
+        }
     }
 
     private void reset() {
@@ -452,6 +467,10 @@
         super.dispatchFinishTemporaryDetach();
     }
 
+    public void setWrapper(NotificationViewWrapper wrapper) {
+        mWrapper = wrapper;
+    }
+
     /**
      * An EditText that changes appearance based on whether it's focusable and becomes
      * un-focusable whenever the user navigates away from it or it becomes invisible.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index b59cf68..ed8c7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -224,7 +224,7 @@
     static final int ICON_CARRIER_NETWORK_CHANGE =
             R.drawable.stat_sys_signal_carrier_network_change_animation;
 
-    static final int ICON_DATA_DISABLED = R.drawable.ic_qs_data_disabled;
+    static final int ICON_DATA_DISABLED = R.drawable.stat_sys_data_disabled;
 
     static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte;
     static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index b1bc2f0..42c20ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -15,18 +15,27 @@
  */
 package com.android.systemui.statusbar.policy;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.NetworkCapabilities;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.ScoredNetwork;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.WifiNetworkScoreCache.CacheListener;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
+import com.android.settingslib.Utils;
 import com.android.settingslib.wifi.WifiStatusTracker;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -34,17 +43,24 @@
 import com.android.systemui.R;
 
 import java.util.Objects;
+import java.util.List;
 
 
 public class WifiSignalController extends
         SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
+
     private final WifiManager mWifiManager;
     private final AsyncChannel mWifiChannel;
     private final boolean mHasMobileData;
+    private final NetworkScoreManager mNetworkScoreManager;
+    private final WifiNetworkScoreCache mScoreCache;
     private final WifiStatusTracker mWifiTracker;
 
+    private boolean mScoringEnabled = false;
+
     public WifiSignalController(Context context, boolean hasMobileData,
-            CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
+            CallbackHandler callbackHandler, NetworkControllerImpl networkController,
+            NetworkScoreManager networkScoreManager) {
         super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
                 callbackHandler, networkController);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
@@ -68,6 +84,44 @@
                 WifiIcons.QS_WIFI_NO_NETWORK,
                 AccessibilityContentDescriptions.WIFI_NO_CONNECTION
                 );
+
+        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(handler) {
+            @Override
+            public void networkCacheUpdated(List<ScoredNetwork> networks) {
+                mCurrentState.badgeEnum = getWifiBadgeEnum();
+                notifyListenersIfNecessary();
+            }
+        });
+
+        // Setup scoring
+        mNetworkScoreManager = networkScoreManager;
+        ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mScoringEnabled =
+                        Settings.Global.getInt(
+                                mContext.getContentResolver(),
+                                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1;
+                if (!mScoringEnabled) {
+                    mScoreCache.clearScores();
+                }
+            }
+        };
+        ContentResolver cr = mContext.getContentResolver();
+        cr.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED),
+                false /* notifyForDescendants */,
+                observer);
+        observer.onChange(false /* selfChange */);
+        registerScoreCache();
+    }
+
+    private void registerScoreCache() {
+        Log.d(mTag, "Registered score cache");
+        mNetworkScoreManager.registerNetworkScoreCache(
+                NetworkKey.TYPE_WIFI,
+                mScoreCache,
+                NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
     }
 
     @Override
@@ -88,27 +142,74 @@
                     ("," + mContext.getString(R.string.accessibility_quick_settings_no_internet));
         }
 
-        IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
-        IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
-                contentDescription);
+        IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(),
+                Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
+        IconState qsIcon = new IconState(
+                mCurrentState.connected, getQsCurrentIconId(),
+                Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
         callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
                 ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
                 wifiDesc);
     }
 
+    @Override
+    public int getCurrentIconId() {
+        if (mCurrentState.badgeEnum != ScoredNetwork.BADGING_NONE) {
+            return Utils.WIFI_PIE_FOR_BADGING[mCurrentState.level];
+        }
+        return super.getCurrentIconId();
+    }
+
     /**
      * Extract wifi state directly from broadcasts about changes in wifi state.
      */
     public void handleBroadcast(Intent intent) {
+        // Update the WifiStatusTracker with the new information and update the score cache.
+        NetworkKey previousNetworkKey = mWifiTracker.networkKey;
         mWifiTracker.handleBroadcast(intent);
+        updateScoreCacheIfNecessary(previousNetworkKey);
+
         mCurrentState.enabled = mWifiTracker.enabled;
         mCurrentState.connected = mWifiTracker.connected;
         mCurrentState.ssid = mWifiTracker.ssid;
         mCurrentState.rssi = mWifiTracker.rssi;
         mCurrentState.level = mWifiTracker.level;
+        mCurrentState.badgeEnum = getWifiBadgeEnum();
         notifyListenersIfNecessary();
     }
 
+    /**
+     * Clears old scores out of the cache and requests new scores if the network key has changed.
+     *
+     * <p>New scores are requested asynchronously.
+     */
+    private void updateScoreCacheIfNecessary(NetworkKey previousNetworkKey) {
+        if (mWifiTracker.networkKey == null) {
+            return;
+        }
+        if ((previousNetworkKey == null) || !mWifiTracker.networkKey.equals(previousNetworkKey)) {
+            mScoreCache.clearScores();
+            mNetworkScoreManager.requestScores(new NetworkKey[]{mWifiTracker.networkKey});
+        }
+    }
+
+    /**
+     * Returns the wifi badge enum for the current {@link #mWifiTracker} state.
+     *
+     * <p>{@link #updateScoreCacheIfNecessary} should be called prior to this method.
+     */
+    private int getWifiBadgeEnum() {
+        if (!mScoringEnabled || mWifiTracker.networkKey == null) {
+            return ScoredNetwork.BADGING_NONE;
+        }
+        ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey);
+
+        if (score != null) {
+            return score.calculateBadge(mWifiTracker.rssi);
+        }
+        return ScoredNetwork.BADGING_NONE;
+    }
+
     @VisibleForTesting
     void setActivity(int wifiActivity) {
         mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
@@ -149,12 +250,14 @@
 
     static class WifiState extends SignalController.State {
         String ssid;
+        int badgeEnum;
 
         @Override
         public void copyFrom(State s) {
             super.copyFrom(s);
             WifiState state = (WifiState) s;
             ssid = state.ssid;
+            badgeEnum = state.badgeEnum;
         }
 
         @Override
@@ -166,7 +269,8 @@
         @Override
         public boolean equals(Object o) {
             return super.equals(o)
-                    && Objects.equals(((WifiState) o).ssid, ssid);
+                    && Objects.equals(((WifiState) o).ssid, ssid)
+                    && (((WifiState) o).badgeEnum == badgeEnum);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 1a2d778..e6a3add 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -78,6 +78,7 @@
     private NotificationHeaderUtil mHeaderUtil;
     private ViewState mHeaderViewState;
     private int mClipBottomAmount;
+    private boolean mIsLowPriority;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -246,10 +247,13 @@
         return mChildren.size();
     }
 
-    public void recreateNotificationHeader(OnClickListener listener, StatusBarNotification notification) {
+    public void recreateNotificationHeader(OnClickListener listener,
+            StatusBarNotification notification) {
         final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
                 mNotificationParent.getStatusBarNotification().getNotification());
-        final RemoteViews header = builder.makeNotificationHeader();
+        final RemoteViews header = mIsLowPriority
+                ? builder.makeLowPriorityContentView(true /* useRegularSubtext */)
+                : builder.makeNotificationHeader();
         if (mNotificationHeader == null) {
             mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
             final View expandButton = mNotificationHeader.findViewById(
@@ -262,7 +266,7 @@
             invalidate();
         } else {
             header.reapply(getContext(), mNotificationHeader);
-            mNotificationHeaderWrapper.notifyContentUpdated(notification);
+            mNotificationHeaderWrapper.notifyContentUpdated(notification, mIsLowPriority);
         }
         updateChildrenHeaderAppearance();
     }
@@ -367,6 +371,9 @@
      * @return the intrinsic size of this children container, i.e the natural fully expanded state
      */
     public int getIntrinsicHeight() {
+        if (mIsLowPriority && !mChildrenExpanded) {
+            return mNotificationHeader.getHeight();
+        }
         int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
         return getIntrinsicHeight(maxAllowedVisibleChildren);
     }
@@ -480,7 +487,7 @@
             childState.clipTopAmount = 0;
             childState.alpha = 0;
             if (i < firstOverflowIndex) {
-                childState.alpha = 1;
+                childState.alpha = mIsLowPriority && !mChildrenExpanded ? expandFactor : 1.0f;
             } else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
                 childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
                 childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
@@ -828,6 +835,9 @@
     }
 
     private int getMinHeight(int maxAllowedVisibleChildren) {
+        if (mIsLowPriority && !mChildrenExpanded) {
+            return mNotificationHeader.getHeight();
+        }
         int minExpandHeight = mNotificationHeaderMargin;
         int visibleChildren = 0;
         boolean firstChild = true;
@@ -922,4 +932,8 @@
         mClipBottomAmount = clipBottomAmount;
         updateChildrenClipping();
     }
+
+    public void setIsLowPriority(boolean isLowPriority) {
+        mIsLowPriority = isLowPriority;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 395e8f2..b5ec398 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -63,14 +63,15 @@
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationSettingsIconRow;
-import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
+import com.android.systemui.statusbar.NotificationMenuRow;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
@@ -94,7 +95,8 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        SettingsIconRowListener, ScrollContainer, VisibilityLocationProvider {
+        NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer,
+        VisibilityLocationProvider {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -224,7 +226,7 @@
     private int mMaxScrollAfterExpand;
     private SwipeHelper.LongPressListener mLongPressListener;
 
-    private NotificationSettingsIconRow mCurrIconRow;
+    private NotificationMenuRow mCurrIconRow;
     private View mTranslatingParentView;
     private View mGearExposedView;
 
@@ -399,16 +401,20 @@
     }
 
     @Override
-    public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
-        if (mLongPressListener != null) {
+    public void onMenuClicked(View view, int x, int y, MenuItem item) {
+        if (mLongPressListener == null) {
+            return;
+        }
+        if (view instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
             MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
                     row.getStatusBarNotification().getPackageName());
-            mLongPressListener.onLongPress(row, x, y);
         }
+        mLongPressListener.onLongPress(view, x, y, item);
     }
 
     @Override
-    public void onSettingsIconRowReset(ExpandableNotificationRow row) {
+    public void onMenuReset(View row) {
         if (mTranslatingParentView != null && row == mTranslatingParentView) {
             mSwipeHelper.setSnappedToGear(false);
             mGearExposedView = null;
@@ -425,7 +431,7 @@
         if (DEBUG) {
             int y = mTopPadding;
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-            y = (int) getLayoutHeight();
+            y = getLayoutHeight();
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
             y = getHeight() - getEmptyBottomMargin();
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -911,7 +917,7 @@
             mDragAnimPendingChildren.remove(animView);
         }
         if (mCurrIconRow != null && targetLeft == 0) {
-            mCurrIconRow.resetState();
+            mCurrIconRow.resetState(true /* notify */);
             mCurrIconRow = null;
         }
     }
@@ -4121,7 +4127,7 @@
             if (currView instanceof ExpandableNotificationRow) {
                 // Set the listener for the current row's gear
                 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
-                mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
+                mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this);
             }
         }
 
@@ -4133,9 +4139,9 @@
                 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
 
                 // If the gear is visible and the movement is towards it it's not a location change.
-                boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
+                boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft();
                 boolean locationChange = isTowardsGear(translation, onLeft)
-                        ? false : mCurrIconRow.isIconLocationChange(translation);
+                        ? false : mCurrIconRow.isMenuLocationChange(translation);
                 if (locationChange) {
                     // Don't consider it "snapped" if location has changed.
                     setSnappedToGear(false);
@@ -4146,8 +4152,8 @@
                         mCheckForDrag = null;
                     } else {
                         // Check scheduled, reset alpha and update location; check will fade it in
-                        mCurrIconRow.setGearAlpha(0f);
-                        mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
+                        mCurrIconRow.setMenuAlpha(0f);
+                        mCurrIconRow.setMenuLocation((int) translation);
                     }
                 }
             }
@@ -4198,14 +4204,14 @@
                 return false; // Let SwipeHelper handle it.
             }
 
-            boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
+            boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft());
             boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
             final double timeForGesture = ev.getEventTime() - ev.getDownTime();
             final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
                 && timeForGesture >= SWIPE_GEAR_TIMING;
 
             if (mGearSnappedTo && mCurrIconRow.isVisible()) {
-                if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
+                if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
                     boolean coveringGear =
                             Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
                     if (gestureTowardsGear || coveringGear) {
@@ -4249,7 +4255,7 @@
 
         private void snapToGear(View animView, float velocity) {
             final float snapBackThreshold = getSpaceForGear(animView);
-            final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
+            final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold
                     : -snapBackThreshold;
             mGearExposedView = mTranslatingParentView;
             if (animView instanceof ExpandableNotificationRow) {
@@ -4280,7 +4286,7 @@
             final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
             final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
             final float translation = getTranslation(animView);
-            return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
+            return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft()
                     ? translation > snapBackThreshold
                     : translation < -snapBackThreshold);
         }
@@ -4349,7 +4355,7 @@
          * Indicates the the gear has been snapped to.
          */
         private void setSnappedToGear(boolean snapped) {
-            mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
+            mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false;
             mGearSnappedTo = snapped && mCurrIconRow != null;
         }
 
@@ -4389,11 +4395,11 @@
                 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
                 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
                 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
-                        || mCurrIconRow.isIconLocationChange(translation)))
+                        || mCurrIconRow.isMenuLocationChange(translation)))
                         && absTransX >= bounceBackToGearWidth * 0.4
                         && absTransX < notiThreshold) {
                     // Fade in the gear
-                    mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
+                    mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation,
                             notiThreshold);
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
new file mode 100644
index 0000000..c9c780a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/BetterListPreference.java
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.tuner;
+
+import android.content.Context;
+import android.support.v7.preference.ListPreference;
+import android.util.AttributeSet;
+
+public class BetterListPreference extends ListPreference {
+
+    private CharSequence mSummary;
+
+    public BetterListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void setSummary(CharSequence summary) {
+        super.setSummary(summary);
+        mSummary = summary;
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return mSummary;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
index ad42459..9593c45 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/NavBarTuner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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
@@ -14,590 +14,227 @@
 
 package com.android.systemui.tuner;
 
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BACK;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.BUTTON_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.CLIPBOARD;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.GRAVITY_SEPARATOR;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.HOME;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_END;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_CODE_START;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.KEY_IMAGE_DELIM;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.MENU_IME;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAVSPACE;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_LEFT;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_RIGHT;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.RECENT;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_END;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.SIZE_MOD_START;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractButton;
-import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractSize;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractImage;
+import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.extractKeycode;
 
-public class NavBarTuner extends Fragment implements TunerService.Tunable {
+import android.annotation.Nullable;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.DropDownPreference;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+import android.support.v7.preference.Preference.OnPreferenceClickListener;
+import android.support.v7.preference.PreferenceCategory;
+import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.widget.EditText;
 
-    private static final int SAVE = Menu.FIRST + 1;
-    private static final int RESET = Menu.FIRST + 2;
-    private static final int READ_REQUEST = 42;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.NavigationBarInflaterView;
+import com.android.systemui.tuner.TunerService.Tunable;
 
-    private static final float PREVIEW_SCALE = .95f;
-    private static final float PREVIEW_SCALE_LANDSCAPE = .75f;
+import java.util.ArrayList;
 
-    private NavBarAdapter mNavBarAdapter;
-    private PreviewNavInflater mPreview;
+public class NavBarTuner extends PreferenceFragment {
+
+    private static final String LAYOUT = "layout";
+    private static final String LEFT = "left";
+    private static final String RIGHT = "right";
+
+    private static final String TYPE = "type";
+    private static final String KEYCODE = "keycode";
+    private static final String ICON = "icon";
+
+    private static final int[] ICONS = new int[]{
+            R.drawable.ic_qs_circle,
+            R.drawable.ic_add,
+            R.drawable.ic_remove,
+            R.drawable.ic_left,
+            R.drawable.ic_right,
+            R.drawable.ic_menu,
+    };
+
+    private final ArrayList<Tunable> mTunables = new ArrayList<>();
+    private Handler mHandler;
 
     @Override
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        final View view = inflater.inflate(R.layout.nav_bar_tuner, container, false);
-        inflatePreview((ViewGroup) view.findViewById(R.id.nav_preview_frame));
-        return view;
-    }
-
-    private void inflatePreview(ViewGroup view) {
-        Display display = getActivity().getWindowManager().getDefaultDisplay();
-        boolean isRotated = display.getRotation() == Surface.ROTATION_90
-                || display.getRotation() == Surface.ROTATION_270;
-
-        Configuration config = new Configuration(getContext().getResources().getConfiguration());
-        boolean isPhoneLandscape = isRotated && (config.smallestScreenWidthDp < 600);
-        final float scale = isPhoneLandscape ? PREVIEW_SCALE_LANDSCAPE : PREVIEW_SCALE;
-        config.densityDpi = (int) (config.densityDpi * scale);
-
-        mPreview = (PreviewNavInflater) LayoutInflater.from(getContext().createConfigurationContext(
-                config)).inflate(R.layout.nav_bar_tuner_inflater, view, false);
-        final ViewGroup.LayoutParams layoutParams = mPreview.getLayoutParams();
-        layoutParams.width = (int) ((isPhoneLandscape ? display.getHeight() : display.getWidth())
-                * scale);
-        // Not sure why, but the height dimen is not being scaled with the dp, set it manually
-        // for now.
-        layoutParams.height = (int) (layoutParams.height * scale);
-        if (isPhoneLandscape) {
-            int width = layoutParams.width;
-            layoutParams.width = layoutParams.height;
-            layoutParams.height = width;
-        }
-        view.addView(mPreview);
-
-        if (isRotated) {
-            mPreview.findViewById(R.id.rot0).setVisibility(View.GONE);
-            final View rot90 = mPreview.findViewById(R.id.rot90);
-        } else {
-            mPreview.findViewById(R.id.rot90).setVisibility(View.GONE);
-            final View rot0 = mPreview.findViewById(R.id.rot0);
-        }
-    }
-
-    private void notifyChanged() {
-        mPreview.onTuningChanged(NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        mHandler = new Handler();
+        super.onCreate(savedInstanceState);
     }
 
     @Override
-    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        RecyclerView recyclerView = (RecyclerView) view.findViewById(android.R.id.list);
-        final Context context = getContext();
-        recyclerView.setLayoutManager(new LinearLayoutManager(context));
-        mNavBarAdapter = new NavBarAdapter(context);
-        recyclerView.setAdapter(mNavBarAdapter);
-        recyclerView.addItemDecoration(new Dividers(context));
-        final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mNavBarAdapter.mCallbacks);
-        mNavBarAdapter.setTouchHelper(itemTouchHelper);
-        itemTouchHelper.attachToRecyclerView(recyclerView);
-
-        TunerService.get(getContext()).addTunable(this, NAV_BAR_VIEWS);
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
     }
 
     @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        TunerService.get(getContext()).removeTunable(this);
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        addPreferencesFromResource(R.xml.nav_bar_tuner);
+        bindLayout((ListPreference) findPreference(LAYOUT));
+        bindButton((PreferenceCategory) findPreference(LEFT),
+                NAV_BAR_LEFT, NAVSPACE);
+        bindButton((PreferenceCategory) findPreference(RIGHT),
+                NAV_BAR_RIGHT, MENU_IME);
     }
 
     @Override
-    public void onTuningChanged(String key, String navLayout) {
-        if (!NAV_BAR_VIEWS.equals(key)) return;
-        Context context = getContext();
-        if (navLayout == null) {
-            navLayout = context.getString(R.string.config_navBarLayout);
-        }
-        String[] views = navLayout.split(GRAVITY_SEPARATOR);
-        String[] groups = new String[] { NavBarAdapter.START, NavBarAdapter.CENTER,
-                NavBarAdapter.END};
-        CharSequence[] groupLabels = new String[] { getString(R.string.start),
-                getString(R.string.center), getString(R.string.end) };
-        mNavBarAdapter.clear();
-        for (int i = 0; i < 3; i++) {
-            mNavBarAdapter.addButton(groups[i], groupLabels[i]);
-            for (String button : views[i].split(BUTTON_SEPARATOR)) {
-                mNavBarAdapter.addButton(button, getLabel(button, context));
+    public void onDestroy() {
+        super.onDestroy();
+        mTunables.forEach(t -> TunerService.get(getContext()).removeTunable(t));
+    }
+
+    private void addTunable(Tunable tunable, String... keys) {
+        mTunables.add(tunable);
+        TunerService.get(getContext()).addTunable(tunable, keys);
+    }
+
+    private void bindLayout(ListPreference preference) {
+        addTunable((key, newValue) -> mHandler.post(() -> {
+            String val = newValue;
+            if (val == null) {
+                val = "default";
             }
-        }
-        mNavBarAdapter.addButton(NavBarAdapter.ADD, getString(R.string.add_button));
-        setHasOptionsMenu(true);
+            preference.setValue(val);
+        }), NAV_BAR_VIEWS);
+        preference.setOnPreferenceChangeListener((preference1, newValue) -> {
+            String val = (String) newValue;
+            if ("default".equals(val)) val = null;
+            TunerService.get(getContext()).setValue(NAV_BAR_VIEWS, val);
+            return true;
+        });
     }
 
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        super.onCreateOptionsMenu(menu, inflater);
-        // TODO: Show save button conditionally, only when there are changes.
-        menu.add(Menu.NONE, SAVE, Menu.NONE, getString(R.string.save))
-                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-        menu.add(Menu.NONE, RESET, Menu.NONE, getString(R.string.reset));
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (item.getItemId() == SAVE) {
-            if (!mNavBarAdapter.hasHomeButton()) {
-                new AlertDialog.Builder(getContext())
-                        .setTitle(R.string.no_home_title)
-                        .setMessage(R.string.no_home_message)
-                        .setPositiveButton(android.R.string.ok, null)
-                        .show();
+    private void bindButton(PreferenceCategory parent, String setting, String def) {
+        String k = parent.getKey();
+        DropDownPreference type = (DropDownPreference) findPreference(TYPE + "_" + k);
+        Preference keycode = findPreference(KEYCODE + "_" + k);
+        ListPreference icon = (ListPreference) findPreference(ICON + "_" + k);
+        setupIcons(icon);
+        addTunable((key, newValue) -> mHandler.post(() -> {
+            String val = newValue;
+            if (val == null) {
+                val = def;
+            }
+            String button = extractButton(val);
+            if (button.startsWith(KEY)) {
+                type.setValue(KEY);
+                String uri = extractImage(button);
+                int code = extractKeycode(button);
+                icon.setValue(uri);
+                updateSummary(icon);
+                keycode.setSummary(code + "");
+                keycode.setVisible(true);
+                icon.setVisible(true);
             } else {
-                Settings.Secure.putString(getContext().getContentResolver(),
-                        NAV_BAR_VIEWS, mNavBarAdapter.getNavString());
+                type.setValue(button);
+                keycode.setVisible(false);
+                icon.setVisible(false);
             }
-            return true;
-        } else if (item.getItemId() == RESET) {
-            Settings.Secure.putString(getContext().getContentResolver(),
-                    NAV_BAR_VIEWS, null);
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    private static CharSequence getLabel(String button, Context context) {
-        if (button.startsWith(HOME)) {
-            return context.getString(R.string.accessibility_home);
-        } else if (button.startsWith(BACK)) {
-            return context.getString(R.string.accessibility_back);
-        } else if (button.startsWith(RECENT)) {
-            return context.getString(R.string.accessibility_recent);
-        } else if (button.startsWith(NAVSPACE)) {
-            return context.getString(R.string.space);
-        } else if (button.startsWith(MENU_IME)) {
-            return context.getString(R.string.menu_ime);
-        } else if (button.startsWith(CLIPBOARD)) {
-            return context.getString(R.string.clipboard);
-        } else if (button.startsWith(KEY)) {
-            return context.getString(R.string.keycode);
-        }
-        return button;
-    }
-
-    private static class Holder extends RecyclerView.ViewHolder {
-        private TextView title;
-
-        public Holder(View itemView) {
-            super(itemView);
-            title = (TextView) itemView.findViewById(android.R.id.title);
-        }
-    }
-
-    private static class Dividers extends RecyclerView.ItemDecoration {
-        private final Drawable mDivider;
-
-        public Dividers(Context context) {
-            TypedValue value = new TypedValue();
-            context.getTheme().resolveAttribute(android.R.attr.listDivider, value, true);
-            mDivider = context.getDrawable(value.resourceId);
-        }
-
-        @Override
-        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
-            super.onDraw(c, parent, state);
-            final int left = parent.getPaddingLeft();
-            final int right = parent.getWidth() - parent.getPaddingRight();
-
-            final int childCount = parent.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = parent.getChildAt(i);
-                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
-                        .getLayoutParams();
-                final int top = child.getBottom() + params.bottomMargin;
-                final int bottom = top + mDivider.getIntrinsicHeight();
-                mDivider.setBounds(left, top, right, bottom);
-                mDivider.draw(c);
-            }
-        }
-    }
-
-    private void selectImage() {
-        startActivityForResult(KeycodeSelectionHelper.getSelectImageIntent(), READ_REQUEST);
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == READ_REQUEST && resultCode == Activity.RESULT_OK && data != null) {
-            final Uri uri = data.getData();
-            final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            getContext().getContentResolver().takePersistableUriPermission(uri, takeFlags);
-            mNavBarAdapter.onImageSelected(uri);
-        } else {
-            super.onActivityResult(requestCode, resultCode, data);
-        }
-    }
-
-    private class NavBarAdapter extends RecyclerView.Adapter<Holder>
-            implements View.OnClickListener {
-
-        private static final String START = "start";
-        private static final String CENTER = "center";
-        private static final String END = "end";
-        private static final String ADD = "add";
-
-        private static final int ADD_ID = 0;
-        private static final int BUTTON_ID = 1;
-        private static final int CATEGORY_ID = 2;
-
-        private List<String> mButtons = new ArrayList<>();
-        private List<CharSequence> mLabels = new ArrayList<>();
-        private int mCategoryLayout;
-        private int mButtonLayout;
-        private ItemTouchHelper mTouchHelper;
-
-        // Stored keycode while we wait for image selection on a KEY.
-        private int mKeycode;
-
-        public NavBarAdapter(Context context) {
-            TypedArray attrs = context.getTheme().obtainStyledAttributes(null,
-                    android.R.styleable.Preference, android.R.attr.preferenceStyle, 0);
-            mButtonLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
-            attrs = context.getTheme().obtainStyledAttributes(null,
-                    android.R.styleable.Preference, android.R.attr.preferenceCategoryStyle, 0);
-            mCategoryLayout = attrs.getResourceId(android.R.styleable.Preference_layout, 0);
-        }
-
-        public void setTouchHelper(ItemTouchHelper itemTouchHelper) {
-            mTouchHelper = itemTouchHelper;
-        }
-
-        public void clear() {
-            mButtons.clear();
-            mLabels.clear();
-            notifyDataSetChanged();
-        }
-
-        public void addButton(String button, CharSequence label) {
-            mButtons.add(button);
-            mLabels.add(label);
-            notifyItemInserted(mLabels.size() - 1);
-            notifyChanged();
-        }
-
-        public boolean hasHomeButton() {
-            final int N = mButtons.size();
-            for (int i = 0; i < N; i++) {
-                if (mButtons.get(i).startsWith(HOME)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        public String getNavString() {
-            StringBuilder builder = new StringBuilder();
-            for (int i = 1; i < mButtons.size() - 1; i++) {
-                String button = mButtons.get(i);
-                if (button.equals(CENTER) || button.equals(END)) {
-                    if (builder.length() == 0 || builder.toString().endsWith(GRAVITY_SEPARATOR)) {
-                        // No start or center buttons, fill with a space.
-                        builder.append(NAVSPACE);
-                    }
-                    builder.append(GRAVITY_SEPARATOR);
-                    continue;
-                } else if (builder.length() != 0 && !builder.toString().endsWith(
-                        GRAVITY_SEPARATOR)) {
-                    builder.append(BUTTON_SEPARATOR);
-                }
-                builder.append(button);
-            }
-            if (builder.toString().endsWith(GRAVITY_SEPARATOR)) {
-                // No end buttons, fill with space.
-                builder.append(NAVSPACE);
-            }
-            return builder.toString();
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            String button = mButtons.get(position);
-            if (button.equals(START) || button.equals(CENTER) || button.equals(END)) {
-                return CATEGORY_ID;
-            }
-            if (button.equals(ADD)) {
-                return ADD_ID;
-            }
-            return BUTTON_ID;
-        }
-
-        @Override
-        public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
-            final Context context = parent.getContext();
-            final LayoutInflater inflater = LayoutInflater.from(context);
-            final View view = inflater.inflate(getLayoutId(viewType), parent, false);
-            if (viewType == BUTTON_ID) {
-                inflater.inflate(R.layout.nav_control_widget,
-                        (ViewGroup) view.findViewById(android.R.id.widget_frame));
-            }
-            return new Holder(view);
-        }
-
-        private int getLayoutId(int viewType) {
-            if (viewType == CATEGORY_ID) {
-                return mCategoryLayout;
-            }
-            return mButtonLayout;
-        }
-
-        @Override
-        public void onBindViewHolder(Holder holder, int position) {
-            holder.title.setText(mLabels.get(position));
-            if (holder.getItemViewType() == BUTTON_ID) {
-                bindButton(holder, position);
-            } else if (holder.getItemViewType() == ADD_ID) {
-                bindAdd(holder);
-            }
-        }
-
-        private void bindAdd(Holder holder) {
-            TypedValue value = new TypedValue();
-            final Context context = holder.itemView.getContext();
-            context.getTheme().resolveAttribute(android.R.attr.colorAccent, value, true);
-            final ImageView icon = (ImageView) holder.itemView.findViewById(android.R.id.icon);
-            icon.setImageResource(R.drawable.ic_add);
-            icon.setImageTintList(ColorStateList.valueOf(context.getColor(value.resourceId)));
-            holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
-            holder.itemView.setClickable(true);
-            holder.itemView.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    showAddDialog(v.getContext());
-                }
+        }), setting);
+        OnPreferenceChangeListener listener = (preference, newValue) -> {
+            mHandler.post(() -> {
+                setValue(setting, type, keycode, icon);
+                updateSummary(icon);
             });
-        }
-
-        private void bindButton(final Holder holder, int position) {
-            holder.itemView.findViewById(android.R.id.icon_frame).setVisibility(View.GONE);
-            holder.itemView.findViewById(android.R.id.summary).setVisibility(View.GONE);
-            bindClick(holder.itemView.findViewById(R.id.close), holder);
-            bindClick(holder.itemView.findViewById(R.id.width), holder);
-            holder.itemView.findViewById(R.id.drag).setOnTouchListener(new View.OnTouchListener() {
-                @Override
-                public boolean onTouch(View v, MotionEvent event) {
-                    mTouchHelper.startDrag(holder);
-                    return true;
-                }
-            });
-        }
-
-        private void showAddDialog(final Context context) {
-            final String[] options = new String[] {
-                    BACK, HOME, RECENT, MENU_IME, NAVSPACE, CLIPBOARD, KEY,
-            };
-            final CharSequence[] labels = new CharSequence[options.length];
-            for (int i = 0; i < options.length; i++) {
-                labels[i] = getLabel(options[i], context);
-            }
-            new AlertDialog.Builder(context)
-                    .setTitle(R.string.select_button)
-                    .setItems(labels, new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (KEY.equals(options[which])) {
-                                showKeyDialogs(context);
-                            } else {
-                                int index = mButtons.size() - 1;
-                                showAddedMessage(context, options[which]);
-                                mButtons.add(index, options[which]);
-                                mLabels.add(index, labels[which]);
-
-                                notifyItemInserted(index);
-                                notifyChanged();
-                            }
-                        }
-                    }).setNegativeButton(android.R.string.cancel, null)
-                    .show();
-        }
-
-        private void onImageSelected(Uri uri) {
-            int index = mButtons.size() - 1;
-            mButtons.add(index, KEY + KEY_CODE_START + mKeycode + KEY_IMAGE_DELIM + uri.toString()
-                    + KEY_CODE_END);
-            mLabels.add(index, getLabel(KEY, getContext()));
-
-            notifyItemInserted(index);
-            notifyChanged();
-        }
-
-        private void showKeyDialogs(final Context context) {
-            final KeycodeSelectionHelper.OnSelectionComplete listener =
-                    new KeycodeSelectionHelper.OnSelectionComplete() {
-                        @Override
-                        public void onSelectionComplete(int code) {
-                            mKeycode = code;
-                            selectImage();
-                        }
-                    };
-            new AlertDialog.Builder(context)
-                    .setTitle(R.string.keycode)
-                    .setMessage(R.string.keycode_description)
-                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            KeycodeSelectionHelper.showKeycodeSelect(context, listener);
-                        }
-                    }).show();
-        }
-
-        private void showAddedMessage(Context context, String button) {
-            if (CLIPBOARD.equals(button)) {
-                new AlertDialog.Builder(context)
-                        .setTitle(R.string.clipboard)
-                        .setMessage(R.string.clipboard_description)
-                        .setPositiveButton(android.R.string.ok, null)
-                        .show();
-            }
-        }
-
-        private void bindClick(View view, Holder holder) {
-            view.setOnClickListener(this);
-            view.setTag(holder);
-        }
-
-        @Override
-        public void onClick(View v) {
-            Holder holder = (Holder) v.getTag();
-            if (v.getId() == R.id.width) {
-                showWidthDialog(holder, v.getContext());
-            } else if (v.getId() == R.id.close) {
-                int position = holder.getAdapterPosition();
-                mButtons.remove(position);
-                mLabels.remove(position);
-                notifyItemRemoved(position);
-                notifyChanged();
-            }
-        }
-
-        private void showWidthDialog(final Holder holder, Context context) {
-            final String buttonSpec = mButtons.get(holder.getAdapterPosition());
-            float amount = extractSize(buttonSpec);
-            final AlertDialog dialog = new AlertDialog.Builder(context)
-                    .setTitle(R.string.adjust_button_width)
-                    .setView(R.layout.nav_width_view)
-                    .setNegativeButton(android.R.string.cancel, null).create();
-            dialog.setButton(DialogInterface.BUTTON_POSITIVE,
-                    context.getString(android.R.string.ok),
-                    new DialogInterface.OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface d, int which) {
-                            final String button = extractButton(buttonSpec);
-                            SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
-                            if (seekBar.getProgress() == 75) {
-                                mButtons.set(holder.getAdapterPosition(), button);
-                            } else {
-                                float amount = (seekBar.getProgress() + 25) / 100f;
-                                mButtons.set(holder.getAdapterPosition(), button
-                                        + SIZE_MOD_START + amount + SIZE_MOD_END);
-                            }
-                            notifyChanged();
-                        }
-                    });
-            dialog.show();
-            SeekBar seekBar = (SeekBar) dialog.findViewById(R.id.seekbar);
-            // Range is .25 - 1.75.
-            seekBar.setMax(150);
-            seekBar.setProgress((int) ((amount - .25f) * 100));
-        }
-
-        @Override
-        public int getItemCount() {
-            return mButtons.size();
-        }
-
-        private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
-            @Override
-            public boolean isLongPressDragEnabled() {
-                return false;
-            }
-
-            @Override
-            public boolean isItemViewSwipeEnabled() {
-                return false;
-            }
-
-            @Override
-            public int getMovementFlags(RecyclerView recyclerView,
-                    RecyclerView.ViewHolder viewHolder) {
-                if (viewHolder.getItemViewType() != BUTTON_ID) {
-                    return makeMovementFlags(0, 0);
-                }
-                int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
-                return makeMovementFlags(dragFlags, 0);
-            }
-
-            @Override
-            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
-                    RecyclerView.ViewHolder target) {
-                int from = viewHolder.getAdapterPosition();
-                int to = target.getAdapterPosition();
-                if (to == 0) {
-                    // Can't go above the top.
-                    return false;
-                }
-                move(from, to, mButtons);
-                move(from, to, mLabels);
-                notifyChanged();
-                notifyItemMoved(from, to);
-                return true;
-            }
-
-            private <T> void move(int from, int to, List<T> list) {
-                list.add(from > to ? to : to + 1, list.get(from));
-                list.remove(from > to ? from + 1 : from);
-            }
-
-            @Override
-            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
-                // Don't care.
-            }
+            return true;
         };
+        type.setOnPreferenceChangeListener(listener);
+        icon.setOnPreferenceChangeListener(listener);
+        keycode.setOnPreferenceClickListener(preference -> {
+            EditText editText = new EditText(getContext());
+            new AlertDialog.Builder(getContext())
+                    .setTitle(preference.getTitle())
+                    .setView(editText)
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+                        int code = KeyEvent.KEYCODE_ENTER;
+                        try {
+                            code = Integer.parseInt(editText.getText().toString());
+                        } catch (Exception e) {
+                        }
+                        keycode.setSummary(code + "");
+                        setValue(setting, type, keycode, icon);
+                    }).show();
+            return true;
+        });
+    }
+
+    private void updateSummary(ListPreference icon) {
+        try {
+            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
+                    getContext().getResources().getDisplayMetrics());
+            String pkg = icon.getValue().split("/")[0];
+            int id = Integer.parseInt(icon.getValue().split("/")[1]);
+            SpannableStringBuilder builder = new SpannableStringBuilder();
+            Drawable d = Icon.createWithResource(pkg, id)
+                    .loadDrawable(getContext());
+            d.setTint(Color.BLACK);
+            d.setBounds(0, 0, size, size);
+            ImageSpan span = new ImageSpan(d);
+            builder.append("  ", span, 0);
+            icon.setSummary(builder);
+        } catch (Exception e) {
+            Log.d("NavButton", "Problem with summary", e);
+            icon.setSummary(null);
+        }
+    }
+
+    private void setValue(String setting, DropDownPreference type, Preference keycode,
+            ListPreference icon) {
+        String button = type.getValue();
+        if (KEY.equals(button)) {
+            String uri = icon.getValue();
+            int code = KeyEvent.KEYCODE_ENTER;
+            try {
+                code = Integer.parseInt(keycode.getSummary().toString());
+            } catch (Exception e) {
+            }
+            button = button + KEY_CODE_START + code + KEY_IMAGE_DELIM + uri + KEY_CODE_END;
+        }
+        TunerService.get(getContext()).setValue(setting, button);
+    }
+
+    private void setupIcons(ListPreference icon) {
+        CharSequence[] labels = new CharSequence[ICONS.length];
+        CharSequence[] values = new CharSequence[ICONS.length];
+        int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14,
+                getContext().getResources().getDisplayMetrics());
+        for (int i = 0; i < ICONS.length; i++) {
+            SpannableStringBuilder builder = new SpannableStringBuilder();
+            Drawable d = Icon.createWithResource(getContext().getPackageName(), ICONS[i])
+                    .loadDrawable(getContext());
+            d.setTint(Color.BLACK);
+            d.setBounds(0, 0, size, size);
+            ImageSpan span = new ImageSpan(d);
+            builder.append("  ", span, 0);
+            labels[i] = builder;
+            values[i] = getContext().getPackageName() + "/" + ICONS[i];
+        }
+        icon.setEntries(labels);
+        icon.setEntryValues(values);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index f6b8891..266f053 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -23,6 +23,7 @@
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.support.v14.preference.PreferenceFragment;
 import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.PreferenceCategory;
@@ -30,9 +31,9 @@
 import android.support.v7.preference.PreferenceViewHolder;
 import android.view.View;
 
+import com.android.systemui.R;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.PluginPrefs;
-import com.android.systemui.R;
 
 import java.util.List;
 import java.util.Set;
@@ -147,6 +148,12 @@
                                     result.activityInfo.name)));
                 }
             });
+            holder.itemView.setOnLongClickListener(v -> {
+                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                intent.setData(Uri.fromParts("package", mComponent.getPackageName(), null));
+                getContext().startActivity(intent);
+                return true;
+            });
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
deleted file mode 100644
index a068172..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/ThemePreference.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import android.app.AlertDialog;
-import android.app.UiModeManager;
-import android.content.Context;
-import android.os.SystemProperties;
-import android.support.v7.preference.ListPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.android.systemui.R;
-
-import libcore.util.Objects;
-
-import com.google.android.collect.Lists;
-
-import java.io.File;
-import java.util.ArrayList;
-
-public class ThemePreference extends ListPreference {
-
-    public ThemePreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public void onAttached() {
-        super.onAttached();
-        String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
-        if (TextUtils.isEmpty(def)) {
-            def = getContext().getString(R.string.default_theme);
-        }
-        String[] fileList = new File("/vendor/overlay").list();
-        ArrayList<String> options = fileList != null
-                ? Lists.newArrayList(fileList) : new ArrayList<>();
-        if (!options.contains(def)) {
-            options.add(0, def);
-        }
-        String[] list = options.toArray(new String[options.size()]);
-        setVisible(options.size() > 1);
-        setEntries(list);
-        setEntryValues(list);
-        updateValue();
-    }
-
-    private void updateValue() {
-        setValue(getContext().getSystemService(UiModeManager.class).getTheme());
-    }
-
-    @Override
-    protected void notifyChanged() {
-        super.notifyChanged();
-        if (!Objects.equal(getValue(),
-                getContext().getSystemService(UiModeManager.class).getTheme())) {
-            new AlertDialog.Builder(getContext())
-                    .setTitle(R.string.change_theme_reboot)
-                    .setPositiveButton(com.android.internal.R.string.global_action_restart, (d, i)
-                            -> getContext().getSystemService(UiModeManager.class)
-                            .setTheme(getValue()))
-                    .setNegativeButton(android.R.string.cancel, (d, i) -> updateValue())
-                    .show();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 565ac08..fb94061 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -33,6 +33,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -126,6 +127,12 @@
         return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser);
     }
 
+    public String getValue(String setting, String def) {
+        String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
+        if (ret == null) return def;
+        return ret;
+    }
+
     public void setValue(String setting, int value) {
          Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d23ebc1..d057d863 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -127,6 +127,7 @@
 
     private boolean mShowing;
     private boolean mExpanded;
+    private boolean mShowA11yStream;
 
     private int mActiveStream;
     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -244,7 +245,6 @@
             if (!AudioSystem.isSingleVolume(mContext)) {
                 addRow(AudioManager.STREAM_RING,
                         R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
-
                 addRow(AudioManager.STREAM_ALARM,
                         R.drawable.ic_volume_alarm, R.drawable.ic_volume_alarm_mute, false);
                 addRow(AudioManager.STREAM_VOICE_CALL,
@@ -253,6 +253,8 @@
                         R.drawable.ic_volume_bt_sco, R.drawable.ic_volume_bt_sco, false);
                 addRow(AudioManager.STREAM_SYSTEM,
                         R.drawable.ic_volume_system, R.drawable.ic_volume_system_mute, false);
+                addRow(AudioManager.STREAM_ACCESSIBILITY, R.drawable.ic_volume_accessibility,
+                        R.drawable.ic_volume_accessibility, true);
             }
         } else {
             addExistingRows();
@@ -307,10 +309,24 @@
     }
 
     private void addRow(int stream, int iconRes, int iconMuteRes, boolean important) {
+        addRow(stream, iconRes, iconMuteRes, important, false);
+    }
+
+    private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
+            boolean dynamic) {
         VolumeRow row = new VolumeRow();
         initRow(row, stream, iconRes, iconMuteRes, important);
-        mDialogRowsView.addView(row.view);
-        mRows.add(row);
+        int rowSize;
+        int viewSize;
+        if (mShowA11yStream && dynamic && (rowSize = mRows.size()) > 1
+                && (viewSize = mDialogRowsView.getChildCount()) > 1) {
+            // A11y Stream should be the last in the list
+            mDialogRowsView.addView(row.view, viewSize - 2);
+            mRows.add(rowSize - 2, row);
+        } else {
+            mDialogRowsView.addView(row.view);
+            mRows.add(row);
+        }
     }
 
     private void addExistingRows() {
@@ -592,6 +608,9 @@
     }
 
     private boolean shouldBeVisibleH(VolumeRow row, boolean isActive) {
+        if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
+            return mShowA11yStream;
+        }
         return mExpanded && row.view.getVisibility() == View.VISIBLE
                 || (mExpanded && (row.important || isActive))
                 || !mExpanded && isActive;
@@ -644,7 +663,8 @@
             if (!ss.dynamic) continue;
             mDynamic.put(stream, true);
             if (findRow(stream) == null) {
-                addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true);
+                addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,
+                        true);
             }
         }
 
@@ -1009,6 +1029,14 @@
         public void onShowSafetyWarning(int flags) {
             showSafetyWarningH(flags);
         }
+
+        @Override
+        public void onAccessibilityModeChanged(Boolean showA11yStream) {
+            boolean show = showA11yStream == null ? false : showA11yStream;
+            mShowA11yStream = show;
+            updateRowsH(getActiveRow());
+
+        }
     };
 
     private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 0e5ff43..276b7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -75,13 +75,13 @@
         STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco);
         STREAMS.put(AudioSystem.STREAM_DTMF, R.string.stream_dtmf);
         STREAMS.put(AudioSystem.STREAM_MUSIC, R.string.stream_music);
+        STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
         STREAMS.put(AudioSystem.STREAM_NOTIFICATION, R.string.stream_notification);
         STREAMS.put(AudioSystem.STREAM_RING, R.string.stream_ring);
         STREAMS.put(AudioSystem.STREAM_SYSTEM, R.string.stream_system);
         STREAMS.put(AudioSystem.STREAM_SYSTEM_ENFORCED, R.string.stream_system_enforced);
         STREAMS.put(AudioSystem.STREAM_TTS, R.string.stream_tts);
         STREAMS.put(AudioSystem.STREAM_VOICE_CALL, R.string.stream_voice_call);
-        STREAMS.put(AudioSystem.STREAM_ACCESSIBILITY, R.string.stream_accessibility);
     }
 
     private final HandlerThread mWorkerThread;
@@ -98,6 +98,7 @@
     private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
     private final Vibrator mVibrator;
     private final boolean mHasVibrator;
+    private boolean mShowA11yStream;
 
     private boolean mDestroyed;
     private VolumePolicy mVolumePolicy;
@@ -204,6 +205,7 @@
         pw.print("  mHasVibrator: "); pw.println(mHasVibrator);
         pw.print("  mRemoteStreams: "); pw.println(mMediaSessionsCallbacksW.mRemoteStreams
                 .values());
+        pw.print("  mShowA11yStream: "); pw.println(mShowA11yStream);
         pw.println();
         mMediaSessions.dump(pw);
     }
@@ -301,6 +303,10 @@
         mCallbacks.onShowSafetyWarning(flags);
     }
 
+    private void onAccessibilityModeChanged(Boolean showA11yStream) {
+        mCallbacks.onAccessibilityModeChanged(showA11yStream);
+    }
+
     private boolean checkRoutedToBluetoothW(int stream) {
         boolean changed = false;
         if (stream == AudioManager.STREAM_MUSIC) {
@@ -570,13 +576,16 @@
             switch (mode) {
                 case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME:
                     // "legacy" mode
+                    mShowA11yStream = false;
                     break;
                 case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME:
+                    mShowA11yStream = true;
                     break;
                 default:
                     Log.e(TAG, "Invalid accessibility mode " + mode);
                     break;
             }
+            mWorker.obtainMessage(W.ACCESSIBILITY_MODE_CHANGED, mShowA11yStream).sendToTarget();
         }
     }
 
@@ -595,6 +604,7 @@
         private static final int NOTIFY_VISIBLE = 12;
         private static final int USER_ACTIVITY = 13;
         private static final int SHOW_SAFETY_WARNING = 14;
+        private static final int ACCESSIBILITY_MODE_CHANGED = 15;
 
         W(Looper looper) {
             super(looper);
@@ -617,6 +627,7 @@
                 case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
                 case USER_ACTIVITY: onUserActivityW(); break;
                 case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
+                case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
             }
         }
     }
@@ -743,6 +754,19 @@
                 });
             }
         }
+
+        @Override
+        public void onAccessibilityModeChanged(Boolean showA11yStream) {
+            boolean show = showA11yStream == null ? false : showA11yStream;
+            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+                entry.getValue().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        entry.getKey().onAccessibilityModeChanged(show);
+                    }
+                });
+            }
+        }
     }
 
 
@@ -1004,6 +1028,7 @@
                         .append('[').append(ss.levelMin).append("..").append(ss.levelMax)
                         .append(']');
                 if (ss.muted) sb.append(" [MUTED]");
+                if (ss.dynamic) sb.append(" [DYNAMIC]");
             }
             sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal);
             sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal);
@@ -1037,6 +1062,7 @@
         void onShowSilentHint();
         void onScreenOff();
         void onShowSafetyWarning(int flags);
+        void onAccessibilityModeChanged(Boolean showA11yStream);
     }
 
     public interface UserActivityListener {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 6516369..90e9321 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -30,6 +30,9 @@
     <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 973f1f2..fb4b6bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -15,6 +15,7 @@
 package com.android.systemui;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -25,6 +26,8 @@
 
 import org.junit.Test;
 
+import java.io.PrintWriter;
+
 public class DependencyTest extends SysuiTestCase {
 
     @Test
@@ -46,8 +49,8 @@
         Dumpable d = mock(Dumpable.class);
         injectTestDependency("test", d);
         Dependency.get("test");
-        mDependency.dump(null, null, null);
-        verify(d).dump(eq(null), eq(null), eq(null));
+        mDependency.dump(null, mock(PrintWriter.class), null);
+        verify(d).dump(eq(null), any(), eq(null));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 5fe5174..f258e5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -43,6 +43,7 @@
     public void SysuiSetup() throws Exception {
         System.setProperty("dexmaker.share_classloader", "true");
         mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
+        SystemUIFactory.createFromConfig(mContext);
         mDependency = new TestDependency();
         mDependency.mContext = mContext;
         mDependency.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index e3ee851..5345031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -58,6 +58,7 @@
 
     @Before
     public void addLeakCheckDependencies() {
+        injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper());
         injectMockDependency(UserSwitcherController.class);
         injectLeakCheckedDependencies(BluetoothController.class, LocationController.class,
                 RotationLockController.class, NetworkController.class, ZenModeController.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
index c65f7150..166fcb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationGutsTest.java
@@ -63,7 +63,7 @@
     private static final String TEST_CHANNEL = "test_channel";
     private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
 
-    private NotificationGuts mNotificationGuts;
+    private NotificationInfo mNotificationInfo;
     private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
     private final PackageManager mMockPackageManager = mock(PackageManager.class);
     private NotificationChannel mNotificationChannel;
@@ -76,7 +76,7 @@
         // Inflate the layout
         final LayoutInflater layoutInflater =
                 LayoutInflater.from(InstrumentationRegistry.getTargetContext());
-        mNotificationGuts = (NotificationGuts) layoutInflater.inflate(R.layout.notification_guts,
+        mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
                 null);
 
         // PackageManager must return a packageInfo and applicationInfo.
@@ -91,7 +91,6 @@
         // mMockStatusBarNotification with a test channel.
         mNotificationChannel = new NotificationChannel(
                 TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
-        when(mMockStatusBarNotification.getNotificationChannel()).thenReturn(mNotificationChannel);
         when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
     }
 
@@ -99,18 +98,18 @@
     @UiThreadTest
     public void testBindNotification_SetsTextApplicationName() throws Exception {
         when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
-        final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+        final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname);
         assertTrue(textView.getText().toString().contains("App Name"));
     }
 
     @Test
     @UiThreadTest
     public void testBindNotification_SetsTextChannelName() throws Exception {
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
-        final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+        final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(TEST_CHANNEL_NAME, textView.getText());
     }
 
@@ -118,12 +117,12 @@
     @UiThreadTest
     public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, (View v, int appUid) -> { latch.countDown(); },
-                null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel,
+                (View v, int appUid) -> { latch.countDown(); }, null, null);
 
-        final TextView settingsButton =
-                (TextView) mNotificationGuts.findViewById(R.id.more_settings);
+        final TextView settingsButton = 
+                (TextView) mNotificationInfo.findViewById(R.id.more_settings);
         settingsButton.performClick();
         // Verify that listener was triggered.
         assertEquals(0, latch.getCount());
@@ -133,12 +132,12 @@
     @UiThreadTest
     public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null,
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null,
                 (View v) -> { latch.countDown(); },
                 null);
 
-        final TextView doneButton = (TextView) mNotificationGuts.findViewById(R.id.done);
+        final TextView doneButton = (TextView) mNotificationInfo.findViewById(R.id.done);
         doneButton.performClick();
         // Verify that listener was triggered.
         assertEquals(0, latch.getCount());
@@ -147,39 +146,39 @@
     @Test
     @UiThreadTest
     public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
-        assertFalse(mNotificationGuts.hasImportanceChanged());
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
+        assertFalse(mNotificationInfo.hasImportanceChanged());
     }
 
     @Test
     @UiThreadTest
     public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
         // Find the high button and check it.
-        RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+        RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
         highButton.setChecked(true);
-        assertTrue(mNotificationGuts.hasImportanceChanged());
+        assertTrue(mNotificationInfo.hasImportanceChanged());
     }
 
     @Test
     @UiThreadTest
     public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+        RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
         assertTrue(highButton.isChecked());
     }
 
     @Test
     @UiThreadTest
     public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
     }
@@ -188,10 +187,10 @@
     @UiThreadTest
     public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+        RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
         highButton.setChecked(true);
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
@@ -200,10 +199,10 @@
     @Test
     @UiThreadTest
     public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        mNotificationGuts.closeControls(-1, -1, true);
+        mNotificationInfo.closeControls();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
     }
@@ -212,10 +211,10 @@
     @UiThreadTest
     public void testCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        mNotificationGuts.closeControls(-1, -1, true);
+        mNotificationInfo.closeControls();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
     }
@@ -224,12 +223,12 @@
     @UiThreadTest
     public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+        RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
         highButton.setChecked(true);
-        mNotificationGuts.closeControls(-1, -1, true);
+        mNotificationInfo.closeControls();
         verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
                 eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
         assertEquals(NotificationManager.IMPORTANCE_HIGH, mNotificationChannel.getImportance());
@@ -239,12 +238,12 @@
     @UiThreadTest
     public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
+        RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
         highButton.setChecked(true);
-        mNotificationGuts.closeControls(-1, -1, false);
+        mNotificationInfo.closeControls();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), anyInt(), any());
     }
@@ -253,10 +252,10 @@
     @UiThreadTest
     public void testEnabledSwitchOnByDefault() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+        Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         assertTrue(enabledSwitch.isChecked());
     }
 
@@ -264,10 +263,10 @@
     @UiThreadTest
     public void testEnabledSwitchVisibleByDefault() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+        Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
     }
 
@@ -275,10 +274,11 @@
     @UiThreadTest
     public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null,
+                Collections.singleton(TEST_PACKAGE_NAME));
 
-        Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+        Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
     }
 
@@ -286,12 +286,13 @@
     @UiThreadTest
     public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null,
+                Collections.singleton(TEST_PACKAGE_NAME));
 
-        Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
+        Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
         enabledSwitch.setChecked(false);
-        mNotificationGuts.closeControls(-1, -1, true);
+        mNotificationInfo.closeControls();
         verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
                 eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
     }
@@ -300,14 +301,14 @@
     @UiThreadTest
     public void testEnabledSwitchOverridesOtherButtons() throws Exception {
         mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
-                mMockStatusBarNotification, null, null, null);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                mMockStatusBarNotification, mNotificationChannel, null, null, null);
 
-        Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
-        RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
+        Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
+        RadioButton lowButton = (RadioButton) mNotificationInfo.findViewById(R.id.low_importance);
         lowButton.setChecked(true);
         enabledSwitch.setChecked(false);
-        mNotificationGuts.closeControls(-1, -1, true);
+        mNotificationInfo.closeControls();
         assertEquals(NotificationManager.IMPORTANCE_NONE, mNotificationChannel.getImportance());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 9fcb5f7..525a361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -40,6 +40,7 @@
         mContext.putComponent(PhoneStatusBar.class, mock(PhoneStatusBar.class));
         mContext.putComponent(Recents.class, mock(Recents.class));
         mContext.putComponent(Divider.class, mock(Divider.class));
+        mContext.addMockSystemService(Context.WINDOW_SERVICE, mock(WindowManager.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index 7b56ea3..c969cc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -18,7 +18,6 @@
 import android.os.HandlerThread;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.SubscriptionInfo;
-import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 23c635c..6aa021e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
 import android.net.wifi.WifiManager;
 import android.os.Looper;
 import android.telephony.PhoneStateListener;
@@ -79,6 +80,7 @@
     protected Config mConfig;
     protected CallbackHandler mCallbackHandler;
     protected SubscriptionDefaults mMockSubDefaults;
+    protected NetworkScoreManager mMockNetworkScoreManager;
 
     protected int mSubId;
 
@@ -105,6 +107,8 @@
         mMockCm = mock(ConnectivityManager.class);
         mMockSubDefaults = mock(SubscriptionDefaults.class);
         mNetCapabilities = new NetworkCapabilities();
+        mMockNetworkScoreManager = mock(NetworkScoreManager.class);
+
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
         when(mMockCm.getDefaultNetworkCapabilitiesForUser(0)).thenReturn(
                 new NetworkCapabilities[] { mNetCapabilities });
@@ -115,7 +119,8 @@
         mConfig = new Config();
         mConfig.hspaDataDistinguishable = true;
         mCallbackHandler = mock(CallbackHandler.class);
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+                mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class));
@@ -157,8 +162,8 @@
     protected NetworkControllerImpl setUpNoMobileData() {
       when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
       NetworkControllerImpl networkControllerNoMobile
-              = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                        mConfig, mContext.getMainLooper(), mCallbackHandler,
+              = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager, mMockTm,
+                        mMockWm, mMockSm, mConfig, mContext.getMainLooper(), mCallbackHandler,
                         mock(AccessPointControllerImpl.class),
                         mock(DataUsageController.class), mMockSubDefaults,
                         mock(DeviceProvisionedController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 1f7ec1a..1ec0418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -100,7 +100,8 @@
     public void test4gDataIcon() {
         // Switch to showing 4g icon and re-initialize the NetworkController.
         mConfig.show4gForLte = true;
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+                mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
                 mock(DataUsageController.class), mMockSubDefaults,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 1a61d80..00e5926 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -53,7 +53,8 @@
         // Turn off mobile network support.
         Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+                mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class));
@@ -107,7 +108,8 @@
         // Turn off mobile network support.
         Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockNetworkScoreManager,
+                mMockTm, mMockWm, mMockSm,
                 mConfig, Looper.getMainLooper(), mCallbackHandler,
                 mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
                 mMockSubDefaults, mock(DeviceProvisionedController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index ed32f65..06a5122 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,24 +1,50 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
 import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -27,6 +53,15 @@
     private static final int MIN_RSSI = -100;
     private static final int MAX_RSSI = -55;
 
+    private static final int LATCH_TIMEOUT = 2000;
+    private static final String TEST_SSID = "\"Test SSID\"";
+    private static final String TEST_BSSID = "00:00:00:00:00:00";
+
+    private final List<NetworkKey> mRequestedKeys = new ArrayList<>();
+    private CountDownLatch mRequestScoresLatch;
+
+    private SettingOverrider mSettingsOverrider;
+
     @Test
     public void testWifiIcon() {
         String testSsid = "Test SSID";
@@ -47,6 +82,77 @@
     }
 
     @Test
+    public void testBadgedWifiIcon() throws Exception {
+        int testLevel = 1;
+        RssiCurve mockBadgeCurve = mock(RssiCurve.class);
+        Bundle attr = new Bundle();
+        attr.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve);
+        ScoredNetwork score =
+                new ScoredNetwork(
+                        new NetworkKey(new WifiKey(TEST_SSID, TEST_BSSID)),
+                        null,
+                        false /* meteredHint */,
+                        attr);
+
+        // Enable scoring
+        mSettingsOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this)
+                .addSetting("global", Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, "1")
+                .build();
+
+        setupNetworkScoreManager();
+        mRequestScoresLatch = new CountDownLatch(1);
+        setWifiEnabled(true);
+        setWifiState(true, TEST_SSID, TEST_BSSID);
+        mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) ScoredNetwork.BADGING_SD);
+
+        ArgumentCaptor<WifiNetworkScoreCache> scoreCacheCaptor =
+                ArgumentCaptor.forClass(WifiNetworkScoreCache.class);
+        verify(mMockNetworkScoreManager).registerNetworkScoreCache(
+                anyInt(),
+                scoreCacheCaptor.capture(),
+                Matchers.anyInt());
+        scoreCacheCaptor.getValue().updateScores(Arrays.asList(score));
+
+        setWifiLevel(testLevel);
+        NetworkController.SignalCallback mockCallback =
+                mock(NetworkController.SignalCallback.class);
+        mNetworkController.addCallback(mockCallback);
+
+        ArgumentCaptor<IconState> iconState = ArgumentCaptor.forClass(IconState.class);
+        Mockito.verify(mockCallback).setWifiIndicators(
+                anyBoolean(), iconState.capture(), any(), anyBoolean(), anyBoolean(), any());
+
+        assertEquals("Badged Wifi Resource is set",
+                Utils.WIFI_PIE_FOR_BADGING[testLevel],
+                iconState.getValue().icon);
+        assertEquals("SD Badge is set",
+                Utils.getWifiBadgeResource(ScoredNetwork.BADGING_SD),
+                iconState.getValue().iconOverlay);
+
+        mSettingsOverrider.release();
+    }
+
+    private void setupNetworkScoreManager() {
+        // Capture requested keys and count down latch if present
+        doAnswer(
+                new Answer<Boolean>() {
+                    @Override
+                    public Boolean answer(InvocationOnMock input) {
+                        if (mRequestScoresLatch != null) {
+                            mRequestScoresLatch.countDown();
+                        }
+                        NetworkKey[] keys = (NetworkKey[]) input.getArguments()[0];
+                        for (NetworkKey key : keys) {
+                            mRequestedKeys.add(key);
+                        }
+                        return true;
+                    }
+                }).when(mMockNetworkScoreManager).requestScores(Matchers.<NetworkKey[]>any());
+    }
+
+    @Test
     public void testQsWifiIcon() {
         String testSsid = "Test SSID";
 
@@ -97,7 +203,7 @@
     @Test
     public void testRoamingIconDuringWifi() {
         // Setup normal connection
-        String testSsid = "Test SSID";
+        String testSsid = "\"Test SSID\"";
         int testLevel = 2;
         setWifiEnabled(true);
         setWifiState(true, testSsid);
@@ -137,12 +243,19 @@
     }
 
     protected void setWifiState(boolean connected, String ssid) {
+        setWifiState(connected, ssid, null);
+    }
+
+    protected void setWifiState(boolean connected, String ssid, String bssid) {
         Intent i = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         NetworkInfo networkInfo = Mockito.mock(NetworkInfo.class);
         Mockito.when(networkInfo.isConnected()).thenReturn(connected);
 
         WifiInfo wifiInfo = Mockito.mock(WifiInfo.class);
         Mockito.when(wifiInfo.getSSID()).thenReturn(ssid);
+        if (bssid != null) {
+            Mockito.when(wifiInfo.getBSSID()).thenReturn(bssid);
+        }
 
         i.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
         i.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo);
@@ -166,8 +279,8 @@
         ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
 
         Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
-                enabledArg.capture(), any(), iconArg.capture(), anyBoolean(), anyBoolean(),
-                descArg.capture());
+                enabledArg.capture(), any(), iconArg.capture(), anyBoolean(),
+                anyBoolean(),  descArg.capture());
         IconState iconState = iconArg.getValue();
         assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue());
         assertEquals("WiFi connected, in quick settings", connected, iconState.visible);
@@ -179,7 +292,8 @@
         ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
 
         Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
-                anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(), any());
+                anyBoolean(), iconArg.capture(), any(), anyBoolean(), anyBoolean(),
+                any());
         IconState iconState = iconArg.getValue();
         assertEquals("WiFi visible, in status bar", visible, iconState.visible);
         assertEquals("WiFi signal, in status bar", icon, iconState.icon);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 88bc99f..952f851 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3311,6 +3311,41 @@
     // OS: 8.0
     MANAGE_EXTERNAL_SOURCES = 808;
 
+    // ACTION: Logged when terms activity finishes.
+    // TIME: Indicates time taken by terms activity to finish in MS.
+    PROVISIONING_TERMS_ACTIVITY_TIME_MS = 809;
+
+    // Indicates number of terms displayed on the terms screen.
+    PROVISIONING_TERMS_COUNT = 810;
+
+    // Indicates number of terms read on the terms screen.
+    PROVISIONING_TERMS_READ = 811;
+
+    // Logs that the user has edited the picture-in-picture settings.
+    // CATEGORY: SETTINGS
+    SETTINGS_MANAGE_PICTURE_IN_PICTURE = 812;
+
+    // ACTION: Allow "Enable picture-in-picture on hide" for an app
+    APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW = 813;
+
+    // ACTION: Deny "Enable picture-in-picture on hide" for an app
+    APP_PICTURE_IN_PICTURE_ON_HIDE_DENY = 814;
+
+    // OPEN: Settings > Language & input > Text-to-speech output -> Speech rate & pitch
+    // CATEGORY: SETTINGS
+    // OS: 8.0
+    TTS_SLIDERS = 815;
+
+    // ACTION: Settings -> Display -> Theme
+    ACTION_THEME = 816;
+
+    // OPEN: SUW Welcome Screen -> Vision Settings -> Select to Speak
+    // ACTION: Select to Speak configuration is chosen
+    //  SUBTYPE: 0 is off, 1 is on
+    // CATEGORY: SETTINGS
+    // OS: N
+    SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK = 817;
+
     // ---- End O Constants, all O constants go above this line ----
 
     // Add new aosp constants above this line.
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
index 87eaf29..47ac1ce 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java
@@ -19,10 +19,6 @@
 import static android.Manifest.permission.MANAGE_AUTO_FILL;
 import static android.content.Context.AUTO_FILL_MANAGER_SERVICE;
 import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
-
-import static com.android.server.autofill.AutoFillUI.MSG_SHOW_ALL_NOTIFICATIONS;
-import static com.android.server.autofill.AutoFillUI.SHOW_ALL_NOTIFICATIONS_DELAY_MS;
 
 import android.Manifest;
 import android.app.AppGlobals;
@@ -32,11 +28,13 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -51,9 +49,12 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.view.autofill.AutoFillId;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
 
@@ -70,13 +71,12 @@
 public final class AutoFillManagerService extends SystemService {
 
     private static final String TAG = "AutoFillManagerService";
-    static final boolean DEBUG = true; // TODO: change to false once stable
+    static final boolean DEBUG = true; // TODO(b/33197203): change to false once stable
 
     private static final long SERVICE_BINDING_LIFETIME_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
 
-    private static final int ARG_NOT_USED = 0;
-
     protected static final int MSG_UNBIND = 1;
+    protected static final int MSG_SHOW_AUTO_FILL = 2;
 
     private final AutoFillManagerServiceStub mServiceStub;
     private final AutoFillUI mUi;
@@ -85,23 +85,27 @@
 
     private final Object mLock = new Object();
 
-    private final Handler mHandler = new Handler() {
+    private final HandlerCaller.Callback mHandlerCallback = new HandlerCaller.Callback() {
+
         @Override
-        public void handleMessage(Message msg) {
+        public void executeMessage(Message msg) {
             switch (msg.what) {
-                case MSG_UNBIND:
+                case MSG_UNBIND: {
                     removeStaleServiceForUser(msg.arg1);
                     return;
-                case MSG_SHOW_ALL_NOTIFICATIONS:
-                    mUi.showAllNotifications();
+                } case MSG_SHOW_AUTO_FILL: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    showAutoFillInput(msg.arg1, (AutoFillId) args.arg1, (Rect) args.arg2);
                     return;
-                default:
+                } default: {
                     Slog.w(TAG, "Invalid message: " + msg);
+                }
             }
         }
-
     };
 
+    private HandlerCaller mHandlerCaller;
+
     /**
      * Cache of {@link AutoFillManagerServiceImpl} per user id.
      * <p>
@@ -122,6 +126,8 @@
     public AutoFillManagerService(Context context) {
         super(context);
 
+        mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(), mHandlerCallback, true);
+
         mContext = context;
         mUi = new AutoFillUI(context, this, mLock);
         mResolver = context.getContentResolver();
@@ -139,14 +145,6 @@
         if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             new SettingsObserver(BackgroundThread.getHandler());
         }
-        if (phase == PHASE_BOOT_COMPLETED) {
-            // TODO: if sent right away, the notification is not displayed. Since the notification
-            // mechanism is a temporary approach anyways, just delay it..
-            if (DEBUG)
-                Slog.d(TAG, "Showing notifications in " + SHOW_ALL_NOTIFICATIONS_DELAY_MS + "ms");
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ALL_NOTIFICATIONS),
-                    SHOW_ALL_NOTIFICATIONS_DELAY_MS);
-        }
     }
 
     private AutoFillManagerServiceImpl newServiceForUser(int userId) {
@@ -200,7 +198,7 @@
         }
         // Keep service connection alive for a while, in case user needs to interact with it
         // (for example, to save the data that was inputted in)
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UNBIND, userId, ARG_NOT_USED),
+        mHandlerCaller.sendMessageDelayed(mHandlerCaller.obtainMessageI(MSG_UNBIND, userId),
                 SERVICE_BINDING_LIFETIME_MS);
         return service;
     }
@@ -245,18 +243,41 @@
 
     }
 
+
+    private void requestAutoFillLocked(IBinder activityToken, int userId, Bundle extras,
+            int flags) {
+        final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
+        if (service != null) {
+            service.requestAutoFill(activityToken, extras, flags);
+        }
+    }
+
+    private void showAutoFillInput(int userId, AutoFillId id, Rect rect) {
+        if (DEBUG) Slog.d(TAG, "handler.showAutoFillInput(): id=" + id + ", rect=" + rect);
+
+        synchronized (mLock) {
+            requestAutoFillLocked(null, userId, null, AUTO_FILL_FLAG_TYPE_FILL);
+        }
+    }
+
     final class AutoFillManagerServiceStub extends IAutoFillManagerService.Stub {
 
         @Override
+        public void showAutoFillInput(AutoFillId id, Rect boundaries) {
+            if (DEBUG) Slog.d(TAG, "showAutoFillInput(): id=" + id + ", boundaries=" + boundaries);
+
+            // TODO(b/33197203): fail if it's not called by same uid as the top activity
+            mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW_AUTO_FILL,
+                    UserHandle.getCallingUserId(), id, boundaries));
+        }
+
+        @Override
         public void requestAutoFill(IBinder activityToken, int userId, Bundle extras, int flags) {
             if (DEBUG) Slog.d(TAG, "requestAutoFill: flags=" + flags + ", userId=" + userId);
             mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG);
 
             synchronized (mLock) {
-                final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId);
-                if (service != null) {
-                    service.requestAutoFill(activityToken, extras, flags);
-                }
+                requestAutoFillLocked(activityToken, userId, extras, flags);
             }
         }
 
@@ -291,7 +312,6 @@
             (new AutoFillManagerServiceShellCommand(this)).exec(
                     this, in, out, err, args, callback, resultReceiver);
         }
-
     }
 
     private final class SettingsObserver extends ContentObserver {
@@ -307,7 +327,6 @@
             if (DEBUG) Slog.d(TAG, "settings (" + uri + " changed for " + userId);
             synchronized (mLock) {
                 removeCachedServiceForUserLocked(userId);
-                mUi.updateNotification(userId);
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
index ae21b07..77e7b31 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java
@@ -16,7 +16,11 @@
 
 package com.android.server.autofill;
 
-import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_ERROR;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_REQUESTED;
+import static android.service.autofill.AutoFillService.FLAG_AUTHENTICATION_SUCCESS;
 
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -31,12 +35,16 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
-import android.icu.text.DateFormat;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintService;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.autofill.AutoFillService;
@@ -45,6 +53,7 @@
 import android.service.autofill.IAutoFillServerCallback;
 import android.service.autofill.IAutoFillService;
 import android.service.voice.VoiceInteractionSession;
+import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -59,7 +68,6 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -73,7 +81,7 @@
     private static final String TAG = "AutoFillManagerServiceImpl";
 
     /** Used do assign ids to new ServerCallback instances. */
-    private static int sServerCallbackCounter = 0;
+    private static int sSessionIdCounter = 0;
 
     private final int mUserId;
     private final int mUid;
@@ -106,13 +114,15 @@
     };
 
     /**
-     * Cache of pending ServerCallbacks, keyed by {@link ServerCallback#id}.
+     * Cache of pending {@link Session}s, keyed by {@link Session#mId}.
      *
-     * <p>They're kept until the AutoFillService handles a request, or an error occurs.
+     * <p>They're kept until the {@link AutoFillService} finished handling a request, an error
+     * occurs, or the session times out.
      */
-    // TODO(b/33197203): need to make sure service is bound while callback is pending
+    // TODO(b/33197203): need to make sure service is bound while callback is pending and/or
+    // use WeakReference
     @GuardedBy("mLock")
-    private static final SparseArray<ServerCallback> mServerCallbacks = new SparseArray<>();
+    private static final SparseArray<Session> mSessions = new SparseArray<>();
 
     private final ServiceConnection mConnection = new ServiceConnection() {
         @Override
@@ -146,10 +156,9 @@
         }
     };
 
-
     /**
      * Receiver of assist data from the app's {@link Activity}, uses the {@code resultData} as
-     * the {@link ServerCallback#id}.
+     * the {@link Session#mId}.
      */
     private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
         @Override
@@ -163,25 +172,26 @@
             }
             final AssistStructure structure = resultData
                     .getParcelable(VoiceInteractionSession.KEY_STRUCTURE);
-            final Bundle data = resultData.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
             final int flags = resultData.getInt(VoiceInteractionSession.KEY_FLAGS, 0);
 
-            final ServerCallback serverCallback;
+            final Session session;
             synchronized (mLock) {
-                serverCallback = mServerCallbacks.get(resultCode);
-                if (serverCallback == null) {
+                session = mSessions.get(resultCode);
+                if (session == null) {
                     Slog.w(TAG, "no server callback for id " + resultCode);
                     return;
                 }
-                serverCallback.appCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
+                session.mAppCallback = IAutoFillAppCallback.Stub.asInterface(appBinder);
             }
-            mService.autoFill(structure, serverCallback, serverCallback.extras, flags);
+            mService.autoFill(structure, session.mServerCallback, session.mExtras, flags);
         }
     };
 
     @GuardedBy("mLock")
     private IAutoFillService mService;
+    @GuardedBy("mLock")
     private boolean mBound;
+    @GuardedBy("mLock")
     private boolean mValid;
 
     // Estimated time when the service will be evicted from the cache.
@@ -275,8 +285,8 @@
             activityToken = topActivities.get(0);
         }
 
-        final String historyItem =
-                DateFormat.getDateTimeInstance().format(new Date()) + " - " + activityToken;
+        final String historyItem = TimeUtils.formatForLogging(System.currentTimeMillis())
+                + " - " + activityToken;
         synchronized (mLock) {
             mRequestHistory.add(historyItem);
             requestAutoFillLocked(activityToken, extras, flags, true);
@@ -295,9 +305,9 @@
             return;
         }
 
-        final int callbackId = ++sServerCallbackCounter;
-        final ServerCallback serverCallback = new ServerCallback(callbackId, extras);
-        mServerCallbacks.put(callbackId, serverCallback);
+        final int sessionId = ++sSessionIdCounter;
+        final Session session = new Session(sessionId, extras);
+        mSessions.put(sessionId, session);
 
         /*
          * TODO(b/33197203): apply security checks below:
@@ -308,7 +318,7 @@
          */
         try {
             // TODO(b/33197203): add MetricsLogger call
-            if (!mAm.requestAutoFillData(mAssistReceiver, null, callbackId, activityToken, flags)) {
+            if (!mAm.requestAutoFillData(mAssistReceiver, null, sessionId, activityToken, flags)) {
                 // TODO(b/33197203): might need a way to warn user (perhaps a new method on
                 // AutoFillService).
                 Slog.w(TAG, "failed to request auto-fill data for " + activityToken);
@@ -348,39 +358,74 @@
     /**
      * Called by {@link AutoFillUI} to fill an activity after the user selected a dataset.
      */
-    void autoFillApp(int callbackId, Dataset dataset) {
+    void autoFillApp(int sessionId, Dataset dataset) {
         // TODO(b/33197203): add MetricsLogger call
 
         if (dataset == null) {
-            Slog.w(TAG, "autoFillApp(): no dataset for callback id " + callbackId);
+            Slog.w(TAG, "autoFillApp(): no dataset for callback id " + sessionId);
             return;
         }
 
-        final ServerCallback serverCallback;
+
+        final Session session;
         synchronized (mLock) {
-            serverCallback = mServerCallbacks.get(callbackId);
-            if (serverCallback == null) {
-                Slog.w(TAG, "autoFillApp(): no server callback with id " + callbackId);
+            session = mSessions.get(sessionId);
+            if (session == null) {
+                Slog.w(TAG, "autoFillApp(): no session with id " + sessionId);
                 return;
             }
-            if (serverCallback.appCallback == null) {
-                Slog.w(TAG, "autoFillApp(): no app callback for server callback " + callbackId);
+            if (session.mAppCallback == null) {
+                Slog.w(TAG, "autoFillApp(): no app callback for session " + sessionId);
                 return;
             }
+
             // TODO(b/33197203): use a handler?
-            try {
-                if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
-                serverCallback.appCallback.autoFill(dataset);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Error auto-filling activity: " + e);
-            }
-            removeServerCallbackLocked(callbackId);
+            session.autoFill(dataset);
         }
     }
 
-    void removeServerCallbackLocked(int id) {
-        if (DEBUG) Slog.d(TAG, "Removing " + id + " from server callbacks");
-        mServerCallbacks.remove(id);
+    void removeSessionLocked(int id) {
+        if (DEBUG) Slog.d(TAG, "Removing session " + id);
+        mSessions.remove(id);
+
+        // TODO(b/33197203): notify mService so it can invalidate the FillCallback / SaveCallback?
+    }
+
+    /**
+     * Notifies the result of a {@link FillResponse} authentication request to the service.
+     *
+     * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after user
+     * used the fingerprint sensors to authenticate.
+     */
+    void notifyResponseAuthenticationResult(Bundle extras, int flags) {
+        if (DEBUG) Slog.d(TAG, "notifyResponseAuthenticationResult(): flags=" + flags
+                + ", extras=" + bundleToString(extras));
+
+        synchronized (mLock) {
+            try {
+                mService.authenticateFillResponse(extras, flags);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Error sending authentication result back to service: " + e);
+            }
+        }
+    }
+
+    /**
+     * Notifies the result of a {@link Dataset} authentication request to the service.
+     *
+     * <p>Typically called by the UI after user taps the "Tap to autofill" affordance, or after
+     * it gets the results from a fingerprint authentication.
+     */
+    void notifyDatasetAuthenticationResult(Bundle extras, int flags) {
+        if (DEBUG) Slog.d(TAG, "notifyDatasetAuthenticationResult(): flags=" + flags
+                + ", extras=" + bundleToString(extras));
+        synchronized (mLock) {
+            try {
+                mService.authenticateDataset(extras, flags);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Error sending authentication result back to service: " + e);
+            }
+        }
     }
 
     void dumpLocked(String prefix, PrintWriter pw) {
@@ -399,15 +444,15 @@
         pw.print(prefix); pw.print("mUserId="); pw.println(mUserId);
         pw.print(prefix); pw.print("mUid="); pw.println(mUid);
         pw.print(prefix); pw.print("mComponent="); pw.println(mComponent.flattenToShortString());
+        pw.print(prefix); pw.print("mService: "); pw.println(mService);
         pw.print(prefix); pw.print("mBound="); pw.println(mBound);
-        pw.print(prefix); pw.print("mService="); pw.println(mService);
         pw.print(prefix); pw.print("mEstimateTimeOfDeath=");
             TimeUtils.formatDuration(mEstimateTimeOfDeath, SystemClock.uptimeMillis(), pw);
         pw.println();
 
         if (DEBUG) {
             // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
-            pw.print(prefix); pw.println("Service info:");
+            pw.print(prefix); pw.println("ServiceInfo:");
             mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
         }
 
@@ -428,19 +473,19 @@
             }
         }
 
-        pw.print(prefix); pw.print("sServerCallbackCounter="); pw.println(sServerCallbackCounter);
-        final int size = mServerCallbacks.size();
+        pw.print(prefix); pw.print("sSessionIdCounter="); pw.println(sSessionIdCounter);
+        final int size = mSessions.size();
         if (size == 0) {
-            pw.print(prefix); pw.println("No server callbacks");
+            pw.print(prefix); pw.println("No sessions");
         } else {
-            pw.print(prefix); pw.print(size); pw.println(" server callbacks:");
+            pw.print(prefix); pw.print(size); pw.println(" sessions:");
             for (int i = 0; i < size; i++) {
-                pw.print(prefix2); pw.print(mServerCallbacks.keyAt(i));
-                final ServerCallback callback = mServerCallbacks.valueAt(i);
-                if (callback.appCallback == null) {
+                pw.print(prefix2); pw.print(mSessions.keyAt(i));
+                final Session session = mSessions.valueAt(i);
+                if (session.mAppCallback == null) {
                     pw.println("(no appCallback)");
                 } else {
-                    pw.print(" (app callback: "); pw.print(callback.appCallback) ; pw.println(")");
+                    pw.print(" (app callback: "); pw.print(session.mAppCallback) ; pw.println(")");
                 }
             }
             pw.println();
@@ -449,7 +494,7 @@
 
     @Override
     public String toString() {
-        return "[AutoFillManagerServiceImpl: userId=" + mUserId + ", uid=" + mUid
+        return "AutoFillManagerServiceImpl: [userId=" + mUserId + ", uid=" + mUid
                 + ", component=" + mComponent.flattenToShortString() + "]";
     }
 
@@ -473,49 +518,311 @@
     /**
      * A bridge between the {@link AutoFillService} implementation and the activity being
      * auto-filled (represented through the {@link IAutoFillAppCallback}).
+     *
+     * <p>Although the auto-fill requests and callbacks are stateless from the service's point of
+     * view, we need to keep state in the framework side for cases such as authentication. For
+     * example, when service return a {@link FillResponse} that contains all the fields needed
+     * to fill the activity but it requires authentication first, that response need to be held
+     * until the user authenticates or it times out.
      */
-    private final class ServerCallback extends IAutoFillServerCallback.Stub {
+    // TODO(b/33197203): make sure sessions are removed (and tested by CTS):
+    // - On all authentication scenarios.
+    // - When user does not interact back after a while.
+    // - When service is unbound.
+    private final class Session {
 
-        private final int id;
-        private final Bundle extras;
-        private IAutoFillAppCallback appCallback;
+        private final int mId;
+        private final Bundle mExtras;
+        private IAutoFillAppCallback mAppCallback;
 
-        private ServerCallback(int id, Bundle extras) {
-            this.id = id;
-            this.extras = extras;
+        // Token used on fingerprint authentication
+        private final IBinder mToken = new Binder();
+
+        private final IFingerprintService mFingerprintService;
+
+        @GuardedBy("mLock")
+        private FillResponse mResponseRequiringAuth;
+        @GuardedBy("mLock")
+        private Dataset mDatasetRequiringAuth;
+
+        // Used to auto-fill the activity directly when the FillCallback.onResponse() is called as
+        // the result of a successful user authentication on service's side.
+        @GuardedBy("mLock")
+        private boolean mAutoFillDirectly;
+
+        // TODO(b/33197203): use handler to handle results?
+        // TODO(b/33197203): handle all callback methods and/or cancelation?
+        private IFingerprintServiceReceiver mServiceReceiver =
+                new IFingerprintServiceReceiver.Stub() {
+
+            @Override
+            public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
+                if (DEBUG) Slog.d(TAG, "onEnrollResult()");
+            }
+
+            @Override
+            public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
+                if (DEBUG) Slog.d(TAG, "onAcquired()");
+            }
+
+            @Override
+            public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
+                if (DEBUG) Slog.d(TAG, "onAuthenticationSucceeded(): " + fp.getGroupId());
+
+                // First, check what was authenticated, a response or a dataset.
+                // Then, decide how to handle it:
+                // - If service provided data, handle them directly.
+                // - Otherwise, notify service.
+
+                mAutoFillDirectly = true;
+
+                if (mDatasetRequiringAuth != null) {
+                    if (mDatasetRequiringAuth.isEmpty()) {
+                        notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
+                                FLAG_AUTHENTICATION_SUCCESS);
+                    } else {
+                        autoFillAppLocked(mDatasetRequiringAuth, true);
+                    }
+                } else if (mResponseRequiringAuth != null) {
+                    final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
+                    if (datasets.isEmpty()) {
+                        notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
+                                FLAG_AUTHENTICATION_SUCCESS);
+                    } else {
+                        showResponseLocked(mResponseRequiringAuth, true);
+                    }
+                } else {
+                    Slog.w(TAG, "onAuthenticationSucceeded(): no response or dataset");
+                }
+
+                mUi.dismissFingerprintRequest(mUserId, true);
+            }
+
+            @Override
+            public void onAuthenticationFailed(long deviceId) {
+                if (DEBUG) Slog.d(TAG, "onAuthenticationFailed()");
+                // Do nothing - onError() will be called after a few failures...
+            }
+
+            @Override
+            public void onError(long deviceId, int error, int vendorCode) {
+                if (DEBUG) Slog.d(TAG, "onError()");
+
+                // Notify service so it can fallback to its own authentication
+                if (mDatasetRequiringAuth != null) {
+                    notifyDatasetAuthenticationResult(mDatasetRequiringAuth.getExtras(),
+                            FLAG_AUTHENTICATION_ERROR);
+                } else if (mResponseRequiringAuth != null) {
+                    notifyResponseAuthenticationResult(mResponseRequiringAuth.getExtras(),
+                            FLAG_AUTHENTICATION_ERROR);
+                } else {
+                    Slog.w(TAG, "onError(): no response or dataset");
+                }
+
+                mUi.dismissFingerprintRequest(mUserId, false);
+            }
+
+            @Override
+            public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
+                if (DEBUG) Slog.d(TAG, "onRemoved()");
+            }
+
+            @Override
+            public void onEnumerated(long deviceId, int fingerId, int groupId, int remaining) {
+                if (DEBUG) Slog.d(TAG, "onEnumerated()");
+            }
+        };
+
+        private IAutoFillServerCallback mServerCallback = new IAutoFillServerCallback.Stub() {
+            @Override
+            public void showResponse(FillResponse response) {
+                // TODO(b/33197203): add MetricsLogger call
+                if (response == null) {
+                    if (DEBUG) Slog.d(TAG, "showResponse(): null response");
+
+                    removeSelf();
+                    return;
+                }
+
+                synchronized (mLock) {
+                    showResponseLocked(response, response.isAuthRequired());
+                }
+            }
+
+            @Override
+            public void showError(CharSequence message) {
+                // TODO(b/33197203): add MetricsLogger call
+                if (DEBUG) Slog.d(TAG, "showError(): " + message);
+
+                mUi.showError(message);
+
+                removeSelf();
+            }
+
+            @Override
+            public void highlightSavedFields(AutoFillId[] ids) {
+                // TODO(b/33197203): add MetricsLogger call
+                if (DEBUG) Slog.d(TAG, "highlightSavedFields(): " + Arrays.toString(ids));
+
+                mUi.highlightSavedFields(ids);
+
+                removeSelf();
+            }
+
+            @Override
+            public void unlockFillResponse(int flags) {
+                // TODO(b/33197203): add proper MetricsLogger calls?
+                if (DEBUG) Log.d(TAG, "unlockUser(): flags=" + flags);
+
+                synchronized (mLock) {
+                    if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
+                        if (mResponseRequiringAuth == null) {
+                            Log.wtf(TAG, "unlockUser(): no mResponseRequiringAuth on flags "
+                                    + flags);
+                            removeSelf();
+                            return;
+                        }
+                        final List<Dataset> datasets = mResponseRequiringAuth.getDatasets();
+                        if (datasets.isEmpty()) {
+                            Log.w(TAG, "unlockUser(): no dataset on previous response: "
+                                    + mResponseRequiringAuth);
+                            removeSelf();
+                            return;
+                        }
+                        mAutoFillDirectly = true;
+                        showResponseLocked(mResponseRequiringAuth, false);
+                    }
+                    // TODO(b/33197203): show UI error on authentication failure?
+                    // Or let service handle it?
+                }
+            }
+
+            @Override
+            public void unlockDataset(Dataset dataset, int flags) {
+                // TODO(b/33197203): add proper MetricsLogger calls?
+                if (DEBUG) Log.d(TAG, "unlockDataset(): dataset=" + dataset + ", flags=" + flags);
+
+                if ((flags & FLAG_AUTHENTICATION_SUCCESS) != 0) {
+                    autoFillAppLocked(dataset != null ? dataset : mDatasetRequiringAuth, true);
+                    return;
+                }
+                removeSelf();
+            }
+        };
+
+        private Session(int id, Bundle extras) {
+            this.mId = id;
+            this.mExtras = extras;
+            this.mFingerprintService = IFingerprintService.Stub
+                    .asInterface(ServiceManager.getService("fingerprint"));
         }
 
-        @Override
-        public void showResponse(FillResponse response) {
-            // TODO(b/33197203): add MetricsLogger call
-            if (DEBUG) Slog.d(TAG, "showResponse(): " + response);
+        private void showResponseLocked(FillResponse response, boolean authRequired) {
+            if (DEBUG) Slog.d(TAG, "showResponse(directly=" + mAutoFillDirectly
+                    + ", authRequired=" + authRequired +"):" + response);
 
-            mUi.showOptions(mUserId, id, response);
+            if (mAutoFillDirectly && response != null) {
+                final List<Dataset> datasets = response.getDatasets();
+                if (datasets.size() == 1) {
+                    // User authenticated and provider returned just 1 dataset - auto-fill it now!
+                    final Dataset dataset = datasets.get(0);
+                    if (DEBUG) Slog.d(TAG, "auto-filling directly from auth: " + dataset);
+
+                    autoFillAppLocked(dataset, true);
+                    return;
+                }
+            }
+
+            if (!authRequired) {
+                // TODO(b/33197203): add MetricsLogger call
+                mUi.showOptions(mUserId, mId, response);
+                return;
+            }
+
+            // Handles response that requires authentication.
+            // TODO(b/33197203): add MetricsLogger call, including if fingerprint requested
+
+            mResponseRequiringAuth = response;
+            final boolean requiresFingerprint = response.hasCryptoObject();
+            if (requiresFingerprint) {
+                // TODO(b/33197203): check if fingerprint is available first and call error callback
+                // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+                // Start scanning for the fingerprint.
+                scanFingerprint(response.getCryptoObjectOpId());
+            }
+            // Displays the message asking the user to tap (or fingerprint) for AutoFill.
+            mUi.showFillResponseAuthenticationRequest(mUserId, mId, requiresFingerprint,
+                    response.getExtras(), response.getFlags());
         }
 
-        @Override
-        public void showError(String message) {
-            // TODO(b/33197203): add MetricsLogger call
-            if (DEBUG) Slog.d(TAG, "showError(): " + message);
+        void autoFill(Dataset dataset) {
+            synchronized (mLock) {
+                // Autofill it directly...
+                if (!dataset.isAuthRequired()) {
+                    autoFillAppLocked(dataset, true);
+                    return;
+                }
 
-            mUi.showError(message);
+                // ...or handle authentication.
 
-            removeSelf();
+                mDatasetRequiringAuth = dataset;
+                final boolean requiresFingerprint = dataset.hasCryptoObject();
+                if (requiresFingerprint) {
+                    // TODO(b/33197203): check if fingerprint is available first and call error callback
+                    // with FLAG_FINGERPRINT_AUTHENTICATION_NOT_AVAILABLE if it's not.
+                    // Start scanning for the fingerprint.
+                    scanFingerprint(dataset.getCryptoObjectOpId());
+                    // Displays the message asking the user to tap (or fingerprint) for AutoFill.
+                    mUi.showDatasetFingerprintAuthenticationRequest(dataset);
+                } else {
+                    try {
+                        mService.authenticateDataset(dataset.getExtras(),
+                                FLAG_AUTHENTICATION_REQUESTED);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Error authenticating dataset: " + e);
+                    }
+                }
+            }
         }
 
-        @Override
-        public void highlightSavedFields(AutoFillId[] ids) {
+        private void autoFillAppLocked(Dataset dataset, boolean removeSelf) {
+            try {
+                if (DEBUG) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
+                mAppCallback.autoFill(dataset);
+
+                // TODO(b/33197203): temporarily hack: show the save notification, since save is
+                // not integrated with IME yet.
+                mUi.showSaveNotification(mUserId, null, dataset);
+
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Error auto-filling activity: " + e);
+            }
+            if (removeSelf) {
+                removeSelf();
+            }
+        }
+
+        private void scanFingerprint(long opId) {
             // TODO(b/33197203): add MetricsLogger call
-            if (DEBUG) Slog.d(TAG, "showSaved(): " + Arrays.toString(ids));
+            if (DEBUG) Slog.d(TAG, "Starting fingerprint scan for op id: " + opId);
 
-            mUi.highlightSavedFields(ids);
-
-            removeSelf();
+            // TODO(b/33197203): since we're clearing the AutoFillService's identity, make sure
+            // this method is only called at the proper times, otherwise a malicious provider could
+            // keep the callback refence to bypass the check
+            final long token = Binder.clearCallingIdentity();
+            try {
+                // TODO(b/33197203): set a timeout?
+                mFingerprintService.authenticate(mToken, opId, mUserId, mServiceReceiver, 0, null);
+            } catch (RemoteException e) {
+                // Local call, shouldn't happen.
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         private void removeSelf() {
             synchronized (mLock) {
-                removeServerCallbackLocked(id);
+                removeSessionLocked(mId);
             }
         }
     }
diff --git a/services/autofill/java/com/android/server/autofill/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
index 08e81d3..ad525d4 100644
--- a/services/autofill/java/com/android/server/autofill/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/AutoFillUI.java
@@ -15,47 +15,36 @@
  */
 package com.android.server.autofill;
 
-import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
 import static android.view.View.AUTO_FILL_FLAG_TYPE_FILL;
+import static android.view.View.AUTO_FILL_FLAG_TYPE_SAVE;
 
-import static com.android.server.autofill.AutoFillManagerService.DEBUG;
+import static com.android.server.autofill.Helper.DEBUG;
+import static com.android.server.autofill.Helper.bundleToString;
 
 import android.app.Activity;
-import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.Notification.Action;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.provider.Settings;
 import android.service.autofill.AutoFillService;
-import android.text.TextUtils;
-import android.util.Log;
 import android.util.Slog;
-import android.view.autofill.Dataset;
 import android.view.autofill.AutoFillId;
+import android.view.autofill.Dataset;
 import android.view.autofill.FillResponse;
 import android.widget.Toast;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.UiThread;
 
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
-import java.util.Set;
 
 /**
  * Handles all auto-fill related UI tasks.
@@ -69,15 +58,16 @@
 
     AutoFillUI(Context context, AutoFillManagerService service, Object lock) {
         mContext = context;
-        mResolver = context.getContentResolver();
         mService = service;
         mLock = lock;
+
+        setNotificationListener();
     }
 
     /**
      * Displays an error message to the user.
      */
-    void showError(String message) {
+    void showError(CharSequence message) {
         // TODO(b/33197203): proper implementation
         UiThread.getHandler().runWithScissors(() -> {
             Toast.makeText(mContext, "AutoFill error: " + message, Toast.LENGTH_LONG).show();
@@ -99,10 +89,115 @@
      * Shows the options from a {@link FillResponse} so the user can pick up the proper
      * {@link Dataset} (when the response has one).
      */
-    void showOptions(int userId, int callbackId, FillResponse response) {
+    void showOptions(int userId, int sessionId, FillResponse response) {
         // TODO(b/33197203): proper implementation
         // TODO(b/33197203): make sure if removes the callback from cache
-        showOptionsNotification(userId, callbackId, response);
+        showOptionsNotification(userId, sessionId, response);
+    }
+
+    /**
+     * Shows an UI affordance indicating that user action is required before a {@link FillResponse}
+     * can be used.
+     *
+     * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint or tap to
+     * autofill" or "Tap to autofill", depending on the value of {@code usesFingerprint}.
+     */
+    void showFillResponseAuthenticationRequest(int userId, int sessionId, boolean usesFingerprint,
+            Bundle extras, int flags) {
+        // TODO(b/33197203): proper implementation
+        showAuthNotification(userId, sessionId, usesFingerprint, extras, flags);
+    }
+
+    /**
+     * Shows an UI affordance asking indicating that user action is required before a
+     * {@link Dataset} can be used.
+     *
+     * <p>It typically replaces the auto-fill bar with a message saying "Press fingerprint to
+     * autofill".
+     */
+    void showDatasetFingerprintAuthenticationRequest(Dataset dataset) {
+        if (DEBUG) Slog.d(TAG, "showDatasetAuthenticationRequest(): dataset=" + dataset);
+
+        // TODO(b/33197203): proper implementation (either pop up a fingerprint dialog or replace
+        // the auto-fill bar with a new message.
+        UiThread.getHandler().runWithScissors(() -> {
+            Toast.makeText(mContext, "AutoFill: press fingerprint to unlock " + dataset.getName(),
+                    Toast.LENGTH_LONG).show();
+        }, 0);
+    }
+
+    /**
+     * Called by service after the user user the fingerprint sensors to authenticate.
+     */
+    void dismissFingerprintRequest(int userId, boolean success) {
+        if (DEBUG) Slog.d(TAG, "dismissFingerprintRequest(): ok=" + success);
+
+        dismissAuthNotification(userId);
+
+        if (!success) {
+            // TODO(b/33197203): proper implementation (snack bar / i18n string)
+            UiThread.getHandler().runWithScissors(() -> {
+                Toast.makeText(mContext, "AutoFill: fingerprint failed", Toast.LENGTH_LONG).show();
+            }, 0);
+        }
+    }
+
+    private AutoFillManagerServiceImpl getServiceLocked(int userId) {
+        final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
+        if (service == null) {
+            Slog.w(TAG, "no auto-fill service for user " + userId);
+        }
+        return service;
+    }
+
+    private void onSaveRequested(int userId, Bundle responseExtras, Bundle datasetExtras) {
+        synchronized (mLock) {
+            final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+            if (service == null) return;
+
+            final Bundle extras = (responseExtras == null && datasetExtras == null)
+                    ? null : new Bundle();
+
+            if (responseExtras != null) {
+                if (DEBUG) Slog.d(TAG, "response extras on save notification: " +
+                        bundleToString(responseExtras));
+                extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, responseExtras);
+            }
+            if (datasetExtras != null) {
+                if (DEBUG) Slog.d(TAG, "dataset extras on save notificataion: " +
+                        bundleToString(datasetExtras));
+                extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetExtras);
+            }
+
+            service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
+        }
+    }
+
+    private void onDatasetPicked(int userId, Dataset dataset, int sessionId) {
+        synchronized (mLock) {
+            final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+            if (service == null) return;
+
+            service.autoFillApp(sessionId, dataset);
+        }
+    }
+
+    private void onSessionDone(int userId, int sessionId) {
+        synchronized (mLock) {
+            final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+            if (service == null) return;
+
+            service.removeSessionLocked(sessionId);
+        }
+    }
+
+    private void onResponseAuthenticationRequested(int userId, Bundle extras, int flags) {
+        synchronized (mLock) {
+            final AutoFillManagerServiceImpl service = getServiceLocked(userId);
+            if (service == null) return;
+
+            service.notifyResponseAuthenticationResult(extras, flags);
+        }
     }
 
     /////////////////////////////////////////////////////////////////////////////////
@@ -117,144 +212,81 @@
     // Extras used in the notification intents
     private static final String EXTRA_USER_ID = "user_id";
     private static final String EXTRA_NOTIFICATION_TYPE = "notification_type";
-    private static final String EXTRA_CALLBACK_ID = "callback_id";
+    private static final String EXTRA_SESSION_ID = "session_id";
     private static final String EXTRA_FILL_RESPONSE = "fill_response";
     private static final String EXTRA_DATASET = "dataset";
+    private static final String EXTRA_AUTH_REQUIRED_EXTRAS = "auth_required_extras";
+    private static final String EXTRA_FLAGS = "flags";
 
-    private static final String TYPE_EMULATE = "emulate";
     private static final String TYPE_OPTIONS = "options";
-    private static final String TYPE_DELETE_CALLBACK = "delete_callback";
+    private static final String TYPE_FINISH_SESSION = "finish_session";
     private static final String TYPE_PICK_DATASET = "pick_dataset";
     private static final String TYPE_SAVE = "save";
+    private static final String TYPE_AUTH_RESPONSE = "auth_response";
 
-    static final int MSG_SHOW_ALL_NOTIFICATIONS = 42;
-    static final int SHOW_ALL_NOTIFICATIONS_DELAY_MS = 5000;
-
+    @GuardedBy("mLock")
     private BroadcastReceiver mNotificationReceiver;
-    private final ContentResolver mResolver;
+    @GuardedBy("mLock")
     private final AutoFillManagerService mService;
     private final Object mLock;
 
     // Hack used to generate unique pending intents
     static int sResultCode = 0;
 
+    private void setNotificationListener() {
+        synchronized (mLock) {
+            if (mNotificationReceiver == null) {
+                mNotificationReceiver = new NotificationReceiver();
+                mContext.registerReceiver(mNotificationReceiver,
+                        new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
+            }
+        }
+    }
+
     final class NotificationReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             final int userId = intent.getIntExtra(EXTRA_USER_ID, -1);
-
-            final AutoFillManagerServiceImpl service = mService.getServiceForUserLocked(userId);
-            if (service == null) {
-                Slog.w(TAG, "no auto-fill service for user " + userId);
-                return;
-            }
-
-            final int callbackId = intent.getIntExtra(EXTRA_CALLBACK_ID, -1);
+            final int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
             final String type = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
             if (type == null) {
                 Slog.wtf(TAG, "No extra " + EXTRA_NOTIFICATION_TYPE + " on intent " + intent);
                 return;
             }
-            final FillResponse fillData = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
+            final FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE);
             final Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET);
-            final Bundle datasetArgs = dataset == null ? null : dataset.getExtras();
-            final Bundle fillDataArgs = fillData == null ? null : fillData.getExtras();
-
-            // Bundle sent on AutoFillService methods - only set if service provided a bundle
-            final Bundle extras = (datasetArgs == null && fillDataArgs == null)
-                    ? null : new Bundle();
+            final Bundle responseExtras = response == null ? null : response.getExtras();
+            final Bundle datasetExtras = dataset == null ? null : dataset.getExtras();
+            final int flags = intent.getIntExtra(EXTRA_FLAGS, 0);
 
             if (DEBUG) Slog.d(TAG, "Notification received: type=" + type + ", userId=" + userId
-                    + ", callbackId=" + callbackId);
+                    + ", sessionId=" + sessionId);
             synchronized (mLock) {
                 switch (type) {
-                    case TYPE_EMULATE:
-                        service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_FILL);
-                        break;
                     case TYPE_SAVE:
-                        if (datasetArgs != null) {
-                            if (DEBUG) Log.d(TAG, "filldata args on save notificataion: " +
-                                    bundleToString(fillDataArgs));
-                            extras.putBundle(AutoFillService.EXTRA_RESPONSE_EXTRAS, fillDataArgs);
-                        }
-                        if (dataset != null) {
-                            if (DEBUG) Log.d(TAG, "dataset args on save notificataion: " +
-                                    bundleToString(datasetArgs));
-                            extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
-                        }
-                        service.requestAutoFill(null, extras, AUTO_FILL_FLAG_TYPE_SAVE);
+                        onSaveRequested(userId, responseExtras, datasetExtras);
                         break;
-                    case TYPE_DELETE_CALLBACK:
-                        service.removeServerCallbackLocked(callbackId);
+                    case TYPE_FINISH_SESSION:
+                        onSessionDone(userId, sessionId);
                         break;
                     case TYPE_PICK_DATASET:
-                        service.autoFillApp(callbackId, dataset);
+                        onDatasetPicked(userId, dataset, sessionId);
+
                         // Must cancel notification because it might be comming from action
-                        if (DEBUG) Log.d(TAG, "Cancelling notification");
+                        if (DEBUG) Slog.d(TAG, "Cancelling notification");
                         NotificationManager.from(mContext).cancel(TYPE_OPTIONS, userId);
 
-                        if (datasetArgs != null) {
-                            if (DEBUG) Log.d(TAG, "adding dataset's extra_data on save intent: "
-                                    + bundleToString(datasetArgs));
-                            extras.putBundle(AutoFillService.EXTRA_DATASET_EXTRAS, datasetArgs);
-                        }
-
-                        // Also show notification with option to save the data
-                        showSaveNotification(userId, fillData, dataset);
+                        break;
+                    case TYPE_AUTH_RESPONSE:
+                        onResponseAuthenticationRequested(userId,
+                                intent.getBundleExtra(EXTRA_AUTH_REQUIRED_EXTRAS), flags);
                         break;
                     default: {
                         Slog.w(TAG, "Unknown notification type: " + type);
                     }
                 }
             }
-        }
-    }
-
-    private ComponentName getProviderForUser(int userId) {
-        ComponentName serviceComponent = null;
-        ServiceInfo serviceInfo = null;
-        final String componentName = Settings.Secure.getStringForUser(
-                mResolver, Settings.Secure.AUTO_FILL_SERVICE, userId);
-        if (!TextUtils.isEmpty(componentName)) {
-            try {
-                serviceComponent = ComponentName.unflattenFromString(componentName);
-                serviceInfo =
-                        AppGlobals.getPackageManager().getServiceInfo(serviceComponent, 0, userId);
-            } catch (RuntimeException | RemoteException e) {
-                Slog.wtf(TAG, "Bad auto-fill service name " + componentName, e);
-                return null;
-            }
-        }
-
-        if (DEBUG) Slog.d(TAG, "getServiceComponentForUser(" + userId + "): component="
-                + serviceComponent + ", info: " + serviceInfo);
-        if (serviceInfo == null) {
-            Slog.w(TAG, "no service info for " + serviceComponent);
-            return null;
-        }
-        return serviceComponent;
-    }
-
-    void showAllNotifications() {
-        final UserManager userManager =
-                (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
-        final List<UserInfo> allUsers = userManager.getUsers(true);
-
-        for (UserInfo user : allUsers) {
-            final ComponentName serviceComponent = getProviderForUser(user.id);
-            if (serviceComponent != null) {
-                showMainNotification(serviceComponent, user.id);
-            }
-        }
-    }
-
-    void updateNotification(int userId) {
-        final ComponentName serviceComponent = getProviderForUser(userId);
-        if (serviceComponent == null) {
-            cancelMainNotification(userId);
-        } else {
-            showMainNotification(serviceComponent, userId);
+            collapseStatusBar();
         }
     }
 
@@ -265,125 +297,45 @@
         return intent;
     }
 
-    private PendingIntent newPickDatasetPI(int userId, int callbackId, FillResponse response,
+    private PendingIntent newPickDatasetPI(int userId, int sessionId, FillResponse response,
             Dataset dataset) {
         final int resultCode = ++ sResultCode;
-        if (DEBUG) Log.d(TAG, "newPickDatasetPI: userId=" + userId + ", callback=" + callbackId
+        if (DEBUG) Slog.d(TAG, "newPickDatasetPI: userId=" + userId + ", sessionId=" + sessionId
                 + ", resultCode=" + resultCode);
 
         final Intent intent = newNotificationIntent(userId, TYPE_PICK_DATASET);
-        intent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+        intent.putExtra(EXTRA_SESSION_ID, sessionId);
         intent.putExtra(EXTRA_FILL_RESPONSE, response);
         intent.putExtra(EXTRA_DATASET, dataset);
         return PendingIntent.getBroadcast(mContext, resultCode, intent,
                 PendingIntent.FLAG_ONE_SHOT);
     }
 
-    private static String bundleToString(Bundle bundle) {
-        if (bundle == null) {
-            return "null";
-        }
-        final Set<String> keySet = bundle.keySet();
-        final StringBuilder builder = new StringBuilder("[Bundle with ").append(keySet.size())
-                .append(" keys:");
-        for (String key : keySet) {
-            final Object value = bundle.get(key);
-            builder.append(' ').append(key).append('=');
-            builder.append((value instanceof Object[])
-                    ? Arrays.toString((Objects[]) value) : value);
-        }
-        return builder.append(']').toString();
-    }
-
-    /**
-     * Shows a permanent notification that triggers the auto-fill workflow for the given user.
-     *
-     * <p>It emulates calling the auto-fill service when the IME is shown.
-     */
-    private void showMainNotification(ComponentName serviceComponent, int userId) {
-        if (DEBUG) Log.d(TAG, "showNotification() for " + userId + ": " + serviceComponent);
-
-        synchronized (mLock) {
-            if (mNotificationReceiver == null) {
-                mNotificationReceiver = new NotificationReceiver();
-                mContext.registerReceiver(mNotificationReceiver,
-                        new IntentFilter(NOTIFICATION_AUTO_FILL_INTENT));
-            }
-        }
-
-        final Intent fillIntent = newNotificationIntent(userId, TYPE_EMULATE);
-        final PendingIntent fillPendingIntent = PendingIntent.getBroadcast(mContext,
-                -1, fillIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
-        final String packageName = serviceComponent.getPackageName();
-        String providerName = null;
-        final PackageManager pm = mContext.getPackageManager();
-        try {
-            final ApplicationInfo info = pm.getApplicationInfoAsUser(packageName, 0, userId);
-            if (info != null) {
-                providerName = pm.getApplicationLabel(info).toString();
-            }
-        } catch (Exception e) {
-            providerName = packageName;
-        }
-        final String title = "AutoFill IME Emulation";
-        final String subTitle = "Tap notification to start auto-fill workflow (by '" + providerName
-                + "' on top activity on user " + userId + ".\n"
-                + "Once provider replies, a new notification will show your options.";
-
-        final Notification notification = new Notification.Builder(mContext)
-                .setCategory(Notification.CATEGORY_SYSTEM)
-                .setOngoing(true)
-                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                .setLocalOnly(true)
-                .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setContentTitle(title)
-                .setStyle(new Notification.BigTextStyle().bigText(subTitle))
-                .setContentIntent(fillPendingIntent)
-                .build();
-        NotificationManager.from(mContext).notify(TYPE_EMULATE, userId, notification);
-    }
-
-    /**
-     * Cancels the permament notification created by
-     * {@link #showMainNotification(ComponentName, int)}.
-     */
-    private void cancelMainNotification(int userId) {
-        if (DEBUG) Log.d(TAG, "cancelNotificationLocked(): " + userId);
-        NotificationManager.from(mContext).cancel(TYPE_EMULATE, userId);
-    }
-
     /**
      * Shows a notification with the results of an auto-fill request, using notications actions
      * to emulate the auto-fill bar buttons displaying the dataset names.
      */
-    private void showOptionsNotification(int userId, int callbackId, FillResponse response) {
+    private void showOptionsNotification(int userId, int sessionId, FillResponse response) {
         final long token = Binder.clearCallingIdentity();
         try {
-            showOptionsNotificationAsSystem(userId, callbackId, response);
+            showOptionsNotificationAsSystem(userId, sessionId, response);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    private void showOptionsNotificationAsSystem(int userId, int callbackId,
+    private void showOptionsNotificationAsSystem(int userId, int sessionId,
             FillResponse response) {
         // Make sure server callback is removed from cache if user cancels the notification.
-        final Intent deleteIntent = newNotificationIntent(userId, TYPE_DELETE_CALLBACK);
-        deleteIntent.putExtra(EXTRA_CALLBACK_ID, callbackId);
+        final Intent deleteIntent = newNotificationIntent(userId, TYPE_FINISH_SESSION)
+                .putExtra(EXTRA_SESSION_ID, sessionId);
         final PendingIntent deletePendingIntent = PendingIntent.getBroadcast(mContext,
                 ++sResultCode, deleteIntent, PendingIntent.FLAG_ONE_SHOT);
 
         final String title = "AutoFill Options";
 
-        final Notification.Builder notification = new Notification.Builder(mContext)
-                .setCategory(Notification.CATEGORY_SYSTEM)
+        final Notification.Builder notification = newNotificationBuilder()
                 .setOngoing(false)
-                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                .setLocalOnly(true)
-                .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
                 .setDeleteIntent(deletePendingIntent)
                 .setContentTitle(title);
 
@@ -418,10 +370,19 @@
                 autoCancel = false;
                 final int size = datasets.size();
                 subTitle = "There are " + size + " option(s).\n"
-                        + "Use the notification action(s) to select the proper one.";
+                        + "Use the notification action(s) to select the proper one."
+                        + "Actions with (F) require fingerprint unlock, and with (P) require"
+                        + "provider authentication to unlock";
                 for (Dataset dataset : datasets) {
-                    final CharSequence name = dataset.getName();
-                    final PendingIntent pi = newPickDatasetPI(userId, callbackId, response, dataset);
+                    final StringBuilder name = new StringBuilder(dataset.getName());
+                    if (dataset.isAuthRequired()) {
+                        if (dataset.hasCryptoObject()) {
+                            name.append("(F)");
+                        } else {
+                            name.append("(P)");
+                        }
+                    }
+                    final PendingIntent pi = newPickDatasetPI(userId, sessionId, response, dataset);
                     notification.addAction(new Action.Builder(null, name, pi).build());
                 }
             }
@@ -437,9 +398,20 @@
         }
     }
 
-    private void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+    void showSaveNotification(int userId, FillResponse response, Dataset dataset) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            showSaveNotificationAsSystem(userId, response, dataset);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void showSaveNotificationAsSystem(int userId, FillResponse response, Dataset dataset) {
         final Intent saveIntent = newNotificationIntent(userId, TYPE_SAVE);
-        saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
+        if (response != null) {
+            saveIntent.putExtra(EXTRA_FILL_RESPONSE, response);
+        }
         if (dataset != null) {
             saveIntent.putExtra(EXTRA_DATASET, dataset);
         }
@@ -447,17 +419,15 @@
                 ++sResultCode, saveIntent, PendingIntent.FLAG_ONE_SHOT);
 
         final String title = "AutoFill Save";
-        final String subTitle = "Tap notification to ask provider to save fields: \n"
-                + Arrays.toString(response.getSavableIds());
+        // Response is not set after fillign an authenticated dataset...
+        final String subTitle = response == null
+                ? "Tap notification to ask provider to save fields."
+                : "Tap notification to ask provider to save fields: \n"
+                        + Arrays.toString(response.getSavableIds());
 
-        final Notification notification = new Notification.Builder(mContext)
-                .setCategory(Notification.CATEGORY_SYSTEM)
+        final Notification notification = newNotificationBuilder()
                 .setAutoCancel(true)
                 .setOngoing(false)
-                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                .setLocalOnly(true)
-                .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
                 .setContentTitle(title)
                 .setContentIntent(savePendingIntent)
                 .setStyle(new Notification.BigTextStyle().bigText(subTitle))
@@ -465,6 +435,68 @@
         NotificationManager.from(mContext).notify(TYPE_SAVE, userId, notification);
     }
 
+    private void showAuthNotification(int userId, int sessionId, boolean usesFingerprint,
+            Bundle extras, int flags) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            showAuthNotificationAsSystem(userId, sessionId, usesFingerprint, extras, flags);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void showAuthNotificationAsSystem(int userId, int sessionId,
+            boolean usesFingerprint, Bundle extras, int flags) {
+        final String title = "AutoFill Authentication";
+        final StringBuilder subTitle = new StringBuilder("Provider require user authentication.\n");
+
+        final Intent authIntent = newNotificationIntent(userId, TYPE_AUTH_RESPONSE)
+                .putExtra(EXTRA_SESSION_ID, sessionId);
+        if (extras != null) {
+            authIntent.putExtra(EXTRA_AUTH_REQUIRED_EXTRAS, extras);
+        }
+        if (flags != 0) {
+            authIntent.putExtra(EXTRA_FLAGS, flags);
+        }
+        final PendingIntent authPendingIntent = PendingIntent.getBroadcast(mContext, ++sResultCode,
+                authIntent, PendingIntent.FLAG_ONE_SHOT);
+
+        if (usesFingerprint) {
+            subTitle.append("But kindly accepts your fingerprint instead"
+                    + "\n(tap fingerprint sensor to trigger it)");
+
+        } else {
+            subTitle.append("Tap notification to launch its authentication UI.");
+        }
+
+        final Notification.Builder notification = newNotificationBuilder()
+                .setAutoCancel(true)
+                .setOngoing(false)
+                .setContentTitle(title)
+                .setStyle(new Notification.BigTextStyle().bigText(subTitle.toString()));
+        if (authPendingIntent != null) {
+            notification.setContentIntent(authPendingIntent);
+        }
+        NotificationManager.from(mContext).notify(TYPE_AUTH_RESPONSE, userId, notification.build());
+    }
+
+    private void dismissAuthNotification(int userId) {
+        NotificationManager.from(mContext).cancel(TYPE_AUTH_RESPONSE, userId);
+    }
+
+    private Notification.Builder newNotificationBuilder() {
+        return new Notification.Builder(mContext)
+                .setCategory(Notification.CATEGORY_SYSTEM)
+                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                .setLocalOnly(true)
+                .setColor(mContext.getColor(
+                        com.android.internal.R.color.system_notification_accent_color));
+    }
+
+    private void collapseStatusBar() {
+        final StatusBarManager sbm = (StatusBarManager) mContext.getSystemService("statusbar");
+        sbm.collapsePanels();
+    }
     /////////////////////////////////////////
     // End of temporary notification code. //
     /////////////////////////////////////////
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
new file mode 100644
index 0000000..79095a1
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.autofill;
+
+import android.os.Bundle;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+
+final class Helper {
+
+    static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable
+    static final String REDACTED = "[REDACTED]";
+
+    static void append(StringBuilder builder, Bundle bundle) {
+        if (bundle == null) {
+            builder.append("N/A");
+        } else if (!DEBUG) {
+            builder.append(REDACTED);
+        } else {
+            final Set<String> keySet = bundle.keySet();
+            builder.append("[Bundle with ").append(keySet.size()).append(" extras:");
+            for (String key : keySet) {
+                final Object value = bundle.get(key);
+                builder.append(' ').append(key).append('=');
+                builder.append((value instanceof Object[])
+                        ? Arrays.toString((Objects[]) value) : value);
+            }
+            builder.append(']');
+        }
+    }
+
+    static String bundleToString(Bundle bundle) {
+        final StringBuilder builder = new StringBuilder();
+        append(builder, bundle);
+        return builder.toString();
+    }
+
+    private Helper() {
+        throw new UnsupportedOperationException("contains static members only");
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 7e82586..88c05b5 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -34,13 +34,15 @@
 import android.app.backup.BackupTransport;
 import android.app.backup.FullBackup;
 import android.app.backup.FullBackupDataOutput;
-import android.app.backup.IBackupObserver;
-import android.app.backup.RestoreDescription;
-import android.app.backup.RestoreSet;
 import android.app.backup.IBackupManager;
+import android.app.backup.IBackupObserver;
 import android.app.backup.IFullBackupRestoreObserver;
 import android.app.backup.IRestoreObserver;
 import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.app.backup.SelectBackupTransportCallback;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -55,16 +57,15 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.Environment.UserEnvironment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -79,15 +80,12 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
-import android.os.Environment.UserEnvironment;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.EventLog;
 import android.util.Log;
@@ -105,6 +103,8 @@
 import com.android.server.SystemService;
 import com.android.server.backup.PackageManagerBackupAgent.Metadata;
 
+import libcore.io.IoUtils;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -139,7 +139,6 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Random;
@@ -166,8 +165,6 @@
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 
-import libcore.io.IoUtils;
-
 public class BackupManagerService {
 
     private static final String TAG = "BackupManagerService";
@@ -271,6 +268,8 @@
     private IStorageManager mStorageManager;
     IBackupManager mBackupManagerBinder;
 
+    private final TransportManager mTransportManager;
+
     boolean mEnabled;   // access to this is synchronized on 'this'
     boolean mProvisioned;
     boolean mAutoRestore;
@@ -322,16 +321,6 @@
     final Object mClearDataLock = new Object();
     volatile boolean mClearingData;
 
-    // Transport bookkeeping
-    final ArraySet<ComponentName> mTransportWhitelist;
-    final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
-    final ArrayMap<String,String> mTransportNames
-            = new ArrayMap<String,String>();             // component name -> registration name
-    final ArrayMap<String,IBackupTransport> mTransports
-            = new ArrayMap<String,IBackupTransport>();   // registration name -> binder
-    final ArrayMap<String,TransportConnection> mTransportConnections
-            = new ArrayMap<String,TransportConnection>();
-    String mCurrentTransport;
     ActiveRestoreSession mActiveRestoreSession;
 
     // Watch the device provisioning operation during setup
@@ -756,7 +745,7 @@
             {
                 mLastBackupPass = System.currentTimeMillis();
 
-                IBackupTransport transport = getTransport(mCurrentTransport);
+                IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
                 if (transport == null) {
                     Slog.v(TAG, "Backup requested but no transport available");
                     synchronized (mQueueLock) {
@@ -1202,32 +1191,19 @@
         // Set up our transport options and initialize the default transport
         // TODO: Don't create transports that we don't need to?
         SystemConfig systemConfig = SystemConfig.getInstance();
-        mTransportWhitelist = systemConfig.getBackupTransportWhitelist();
+        Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
 
         String transport = Settings.Secure.getString(context.getContentResolver(),
                 Settings.Secure.BACKUP_TRANSPORT);
         if (TextUtils.isEmpty(transport)) {
             transport = null;
         }
-        mCurrentTransport = transport;
-        if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport);
+        String currentTransport = transport;
+        if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
 
-        // Find all transport hosts and bind to their services
-        // TODO: http://b/22388012
-        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
-                mTransportServiceIntent, 0, UserHandle.USER_SYSTEM);
-        if (DEBUG) {
-            Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size()));
-        }
-        if (hosts != null) {
-            for (int i = 0; i < hosts.size(); i++) {
-                final ServiceInfo transportService = hosts.get(i).serviceInfo;
-                if (MORE_DEBUG) {
-                    Slog.v(TAG, "   " + transportService.packageName + "/" + transportService.name);
-                }
-                tryBindTransport(transportService);
-            }
-        }
+        mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
+                mTransportBoundListener);
+        mTransportManager.registerAllTransports();
 
         // Now that we know about valid backup participants, parse any
         // leftover journal files into the pending backup set
@@ -1751,7 +1727,7 @@
         mBackupHandler.removeMessages(MSG_RETRY_INIT);
 
         try {
-            IBackupTransport transport = getTransport(transportName);
+            IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
             if (transport != null) {
                 String transportDirName = transport.transportDirName();
                 File stateDir = new File(mBaseStateDir, transportDirName);
@@ -1829,49 +1805,39 @@
         }
     }
 
-    // Add a transport to our set of available backends.  If 'transport' is null, this
-    // is an unregistration, and the transport's entry is removed from our bookkeeping.
-    private void registerTransport(String name, String component, IBackupTransport transport) {
-        synchronized (mTransports) {
-            if (DEBUG) Slog.v(TAG, "Registering transport "
-                    + component + "::" + name + " = " + transport);
-            if (transport != null) {
-                mTransports.put(name, transport);
-                mTransportNames.put(component, name);
-            } else {
-                mTransports.remove(mTransportNames.get(component));
-                mTransportNames.remove(component);
-                // Nothing further to do in the unregistration case
-                return;
-            }
-        }
+    private TransportManager.TransportBoundListener mTransportBoundListener =
+            new TransportManager.TransportBoundListener() {
+        @Override
+        public boolean onTransportBound(IBackupTransport transport) {
+            // If the init sentinel file exists, we need to be sure to perform the init
+            // as soon as practical.  We also create the state directory at registration
+            // time to ensure it's present from the outset.
+            String name = null;
+            try {
+                name = transport.name();
+                String transportDirName = transport.transportDirName();
+                File stateDir = new File(mBaseStateDir, transportDirName);
+                stateDir.mkdirs();
 
-        // If the init sentinel file exists, we need to be sure to perform the init
-        // as soon as practical.  We also create the state directory at registration
-        // time to ensure it's present from the outset.
-        try {
-            String transportName = transport.transportDirName();
-            File stateDir = new File(mBaseStateDir, transportName);
-            stateDir.mkdirs();
+                File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+                if (initSentinel.exists()) {
+                    synchronized (mQueueLock) {
+                        mPendingInits.add(name);
 
-            File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
-            if (initSentinel.exists()) {
-                synchronized (mQueueLock) {
-                    mPendingInits.add(name);
-
-                    // TODO: pick a better starting time than now + 1 minute
-                    long delay = 1000 * 60; // one minute, in milliseconds
-                    mAlarmManager.set(AlarmManager.RTC_WAKEUP,
-                            System.currentTimeMillis() + delay, mRunInitIntent);
+                        // TODO: pick a better starting time than now + 1 minute
+                        long delay = 1000 * 60; // one minute, in milliseconds
+                        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                                System.currentTimeMillis() + delay, mRunInitIntent);
+                    }
                 }
+                return true;
+            } catch (Exception e) {
+                // the transport threw when asked its file naming prefs; declare it invalid
+                Slog.w(TAG, "Failed to regiser transport: " + name);
+                return false;
             }
-        } catch (Exception e) {
-            // the transport threw when asked its file naming prefs; declare it invalid
-            Slog.e(TAG, "Unable to register transport as " + name);
-            mTransportNames.remove(component);
-            mTransports.remove(name);
         }
-    }
+    };
 
     // ----- Track installation/removal of packages -----
     BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1899,75 +1865,17 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    try {
-                        String[] components =
-                                intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+                    String[] components =
+                            intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
-                        if (MORE_DEBUG) {
-                            Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
-                            for (int i = 0; i < components.length; i++) {
-                                Slog.i(TAG, "   * " + components[i]);
-                            }
-                        }
-
-                        // In general we need to try to bind any time we see a component enable
-                        // state change, because that change may have made a transport available.
-                        // However, because we currently only support a single transport component
-                        // per package, we can skip the bind attempt if the change (a) affects a
-                        // package known to host a transport, but (b) does not affect the known
-                        // transport component itself.
-                        //
-                        // In addition, if the change *is* to a known transport component, we need
-                        // to unbind it before retrying the binding.
-                        boolean tryBind = true;
-                        synchronized (mTransports) {
-                            TransportConnection conn = mTransportConnections.get(pkgName);
-                            if (conn != null) {
-                                // We have a bound transport in this package; do we need to rebind it?
-                                final ServiceInfo svc = conn.mTransport;
-                                ComponentName svcName =
-                                        new ComponentName(svc.packageName, svc.name);
-                                if (svc.packageName.equals(pkgName)) {
-                                    final String className = svcName.getClassName();
-                                    if (MORE_DEBUG) {
-                                        Slog.i(TAG, "Checking need to rebind " + className);
-                                    }
-                                    // See whether it's the transport component within this package
-                                    boolean isTransport = false;
-                                    for (int i = 0; i < components.length; i++) {
-                                        if (className.equals(components[i])) {
-                                            // Okay, it's an existing transport component.
-                                            final String flatName = svcName.flattenToShortString();
-                                            mContext.unbindService(conn);
-                                            mTransportConnections.remove(pkgName);
-                                            mTransports.remove(mTransportNames.get(flatName));
-                                            mTransportNames.remove(flatName);
-                                            isTransport = true;
-                                            break;
-                                        }
-                                    }
-                                    if (!isTransport) {
-                                        // A non-transport component within a package that is hosting
-                                        // a bound transport
-                                        tryBind = false;
-                                    }
-                                }
-                            }
-                        }
-                        // and now (re)bind as appropriate
-                        if (tryBind) {
-                            if (MORE_DEBUG) {
-                                Slog.i(TAG, "Yes, need to recheck binding");
-                            }
-                            PackageInfo app = mPackageManager.getPackageInfo(pkgName, 0);
-                            checkForTransportAndBind(app);
-                        }
-                    } catch (NameNotFoundException e) {
-                        // Nope, can't find it - just ignore
-                        if (MORE_DEBUG) {
-                            Slog.w(TAG, "Can't find changed package " + pkgName);
+                    if (MORE_DEBUG) {
+                        Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+                        for (int i = 0; i < components.length; i++) {
+                            Slog.i(TAG, "   * " + components[i]);
                         }
                     }
+
+                    mTransportManager.onPackageChanged(pkgName, components);
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -2015,19 +1923,7 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        // Transport maintenance: rebind to known existing transports that have
-                        // just been updated; and bind to any newly-installed transport services.
-                        synchronized (mTransports) {
-                            final TransportConnection conn = mTransportConnections.get(packageName);
-                            if (conn != null) {
-                                if (MORE_DEBUG) {
-                                    Slog.i(TAG, "Transport package changed; rebinding");
-                                }
-                                bindTransport(conn.mTransport);
-                            } else {
-                                checkForTransportAndBind(app);
-                            }
-                        }
+                        mTransportManager.onPackageAdded(packageName);
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -2051,107 +1947,13 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
+                for (String pkgName : pkgList) {
+                    mTransportManager.onPackageRemoved(pkgName);
+                }
             }
         }
     };
 
-    // ----- Track connection to transports service -----
-    class TransportConnection implements ServiceConnection {
-        ServiceInfo mTransport;
-
-        public TransportConnection(ServiceInfo transport) {
-            mTransport = transport;
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName component, IBinder service) {
-            if (DEBUG) Slog.v(TAG, "Connected to transport " + component);
-            final String name = component.flattenToShortString();
-            try {
-                IBackupTransport transport = IBackupTransport.Stub.asInterface(service);
-                registerTransport(transport.name(), name, transport);
-                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 1);
-            } catch (Exception e) {
-                Slog.e(TAG, "Unable to register transport " + component
-                        + ": " + e.getMessage());
-                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName component) {
-            if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component);
-            final String name = component.flattenToShortString();
-            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0);
-            registerTransport(null, name, null);
-        }
-    };
-
-    // Check whether the given package hosts a transport, and bind if so
-    void checkForTransportAndBind(PackageInfo pkgInfo) {
-        Intent intent = new Intent(mTransportServiceIntent)
-                .setPackage(pkgInfo.packageName);
-        // TODO: http://b/22388012
-        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
-                intent, 0, UserHandle.USER_SYSTEM);
-        if (hosts != null) {
-            final int N = hosts.size();
-            for (int i = 0; i < N; i++) {
-                final ServiceInfo info = hosts.get(i).serviceInfo;
-                tryBindTransport(info);
-            }
-        }
-    }
-
-    // Verify that the service exists and is hosted by a privileged app, then proceed to bind
-    boolean tryBindTransport(ServiceInfo info) {
-        try {
-            PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
-            if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
-                    != 0) {
-                return bindTransport(info);
-            } else {
-                Slog.w(TAG, "Transport package " + info.packageName + " not privileged");
-            }
-        } catch (NameNotFoundException e) {
-            Slog.w(TAG, "Problem resolving transport package " + info.packageName);
-        }
-        return false;
-    }
-
-    // Actually bind; presumes that we have already validated the transport service
-    boolean bindTransport(ServiceInfo transport) {
-        ComponentName svcName = new ComponentName(transport.packageName, transport.name);
-        if (!mTransportWhitelist.contains(svcName)) {
-            Slog.w(TAG, "Proposed transport " + svcName + " not whitelisted; ignoring");
-            return false;
-        }
-
-        if (MORE_DEBUG) {
-            Slog.i(TAG, "Binding to transport host " + svcName);
-        }
-        Intent intent = new Intent(mTransportServiceIntent);
-        intent.setComponent(svcName);
-
-        TransportConnection connection;
-        synchronized (mTransports) {
-            connection = mTransportConnections.get(transport.packageName);
-            if (null == connection) {
-                connection = new TransportConnection(transport);
-                mTransportConnections.put(transport.packageName, connection);
-            } else {
-                // This is a rebind due to package upgrade.  The service won't be
-                // automatically relaunched for us until we explicitly rebind, but
-                // we need to unbind the now-orphaned original connection.
-                mContext.unbindService(connection);
-            }
-        }
-        // TODO: http://b/22388012
-        return mContext.bindServiceAsUser(intent,
-                connection, Context.BIND_AUTO_CREATE,
-                UserHandle.SYSTEM);
-    }
-
     // Add the backup agents in the given packages to our set of known backup participants.
     // If 'packageNames' is null, adds all backup agents in the whole system.
     void addPackageParticipantsLocked(String[] packageNames) {
@@ -2352,34 +2154,12 @@
         }
     }
 
-    // Return the given transport
-    private IBackupTransport getTransport(String transportName) {
-        synchronized (mTransports) {
-            IBackupTransport transport = mTransports.get(transportName);
-            if (transport == null) {
-                Slog.w(TAG, "Requested unavailable transport: " + transportName);
-            }
-            return transport;
-        }
-    }
-
     // What name is this transport registered under...?
     private String getTransportName(IBackupTransport transport) {
         if (MORE_DEBUG) {
             Slog.v(TAG, "Searching for transport name of " + transport);
         }
-        synchronized (mTransports) {
-            final int N = mTransports.size();
-            for (int i = 0; i < N; i++) {
-                if (mTransports.valueAt(i).equals(transport)) {
-                    if (MORE_DEBUG) {
-                        Slog.v(TAG, "  Name found: " + mTransports.keyAt(i));
-                    }
-                    return mTransports.keyAt(i);
-                }
-            }
-        }
-        return null;
+        return mTransportManager.getTransportName(transport);
     }
 
     // fire off a backup agent, blocking until it attaches or times out
@@ -2505,7 +2285,7 @@
             throw new IllegalArgumentException("No packages are provided for backup");
         }
 
-        IBackupTransport transport = getTransport(mCurrentTransport);
+        IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
         if (transport == null) {
             sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
             return BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -3025,7 +2805,7 @@
                     if (MORE_DEBUG) Slog.d(TAG, "Server requires init; rerunning");
                     addBackupTrace("init required; rerunning");
                     try {
-                        final String name = getTransportName(mTransport);
+                        final String name = mTransportManager.getTransportName(mTransport);
                         if (name != null) {
                             mPendingInits.add(name);
                         } else {
@@ -4503,7 +4283,7 @@
                     return;
                 }
 
-                IBackupTransport transport = getTransport(mCurrentTransport);
+                IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
                 if (transport == null) {
                     Slog.w(TAG, "Transport not present; full data backup not performed");
                     backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -5119,7 +4899,7 @@
 
                 headBusy = false;
 
-                if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+                if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
                     if (MORE_DEBUG) {
                         Slog.i(TAG, "Preconditions not met; not running full backup");
                     }
@@ -9115,7 +8895,8 @@
         public void run() {
             try {
                 for (String transportName : mQueue) {
-                    IBackupTransport transport = getTransport(transportName);
+                    IBackupTransport transport =
+                            mTransportManager.getTransportBinder(transportName);
                     if (transport == null) {
                         Slog.e(TAG, "Requested init for " + transportName + " but not found");
                         continue;
@@ -9312,7 +9093,8 @@
             if (MORE_DEBUG) Slog.v(TAG, "Found the app - running clear process");
             mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
             synchronized (mQueueLock) {
-                final IBackupTransport transport = getTransport(transportName);
+                final IBackupTransport transport =
+                        mTransportManager.getTransportBinder(transportName);
                 if (transport == null) {
                     // transport is currently unavailable -- make sure to retry
                     Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
@@ -9450,7 +9232,7 @@
             throw new IllegalStateException("Restore supported only for the device owner");
         }
 
-        if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+        if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
             Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
         } else {
             if (DEBUG) {
@@ -9718,10 +9500,7 @@
                     if (wasEnabled && mProvisioned) {
                         // NOTE: we currently flush every registered transport, not just
                         // the currently-active one.
-                        HashSet<String> allTransports;
-                        synchronized (mTransports) {
-                            allTransports = new HashSet<String>(mTransports.keySet());
-                        }
+                        String[] allTransports = mTransportManager.getBoundTransportNames();
                         // build the set of transports for which we are posting an init
                         for (String transport : allTransports) {
                             recordInitPendingLocked(true, transport);
@@ -9774,36 +9553,27 @@
     public String getCurrentTransport() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getCurrentTransport");
-        if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
-        return mCurrentTransport;
+        String currentTransport = mTransportManager.getCurrentTransportName();
+        if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + currentTransport);
+        return currentTransport;
     }
 
     // Report all known, available backup transports
     public String[] listAllTransports() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports");
 
-        String[] list = null;
-        ArrayList<String> known = new ArrayList<String>();
-        for (Map.Entry<String, IBackupTransport> entry : mTransports.entrySet()) {
-            if (entry.getValue() != null) {
-                known.add(entry.getKey());
-            }
-        }
+        return mTransportManager.getBoundTransportNames();
+    }
 
-        if (known.size() > 0) {
-            list = new String[known.size()];
-            known.toArray(list);
-        }
-        return list;
+    public ComponentName[] listAllTransportComponents() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+                "listAllTransportComponents");
+        return mTransportManager.getAllTransportCompenents();
     }
 
     public String[] getTransportWhitelist() {
         // No permission check, intentionally.
-        String[] whitelist = new String[mTransportWhitelist.size()];
-        for (int i = mTransportWhitelist.size() - 1; i >= 0; i--) {
-            whitelist[i] = mTransportWhitelist.valueAt(i).flattenToShortString();
-        }
-        return whitelist;
+        return mTransportManager.getTransportWhitelist().toArray(new String[0]);
     }
 
     // Select which transport to use for the next backup operation.
@@ -9811,22 +9581,58 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "selectBackupTransport");
 
-        synchronized (mTransports) {
-            final long oldId = Binder.clearCallingIdentity();
-            try {
-                String prevTransport = mCurrentTransport;
-                mCurrentTransport = transport;
-                Settings.Secure.putString(mContext.getContentResolver(),
-                        Settings.Secure.BACKUP_TRANSPORT, transport);
-                Slog.v(TAG, "selectBackupTransport() set " + mCurrentTransport
-                        + " returning " + prevTransport);
-                return prevTransport;
-            } finally {
-                Binder.restoreCallingIdentity(oldId);
-            }
+        final long oldId = Binder.clearCallingIdentity();
+        try {
+            String prevTransport = mTransportManager.selectTransport(transport);
+            Settings.Secure.putString(mContext.getContentResolver(),
+                    Settings.Secure.BACKUP_TRANSPORT, transport);
+            Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
+                    + " returning " + prevTransport);
+            return prevTransport;
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
         }
     }
 
+    public void selectBackupTransportAsync(final ComponentName transport,
+            final ISelectBackupTransportCallback listener) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+                "selectBackupTransportAsync");
+
+        final long oldId = Binder.clearCallingIdentity();
+
+        Slog.v(TAG, "selectBackupTransportAsync() called with transport " +
+                transport.flattenToShortString());
+
+        mTransportManager.ensureTransportReady(transport, new SelectBackupTransportCallback() {
+            @Override
+            public void onSuccess(String transportName) {
+                mTransportManager.selectTransport(transportName);
+                Settings.Secure.putString(mContext.getContentResolver(),
+                        Settings.Secure.BACKUP_TRANSPORT,
+                        mTransportManager.getCurrentTransportName());
+                Slog.v(TAG, "Transport successfully selected: " + transport.flattenToShortString());
+                try {
+                    listener.onSuccess(transportName);
+                } catch (RemoteException e) {
+                    // Nothing to do here.
+                }
+            }
+
+            @Override
+            public void onFailure(int reason) {
+                Slog.v(TAG, "Failed to select transport: " + transport.flattenToShortString());
+                try {
+                    listener.onFailure(reason);
+                } catch (RemoteException e) {
+                    // Nothing to do here.
+                }
+            }
+        });
+
+        Binder.restoreCallingIdentity(oldId);
+    }
+
     // Supply the configuration Intent for the given transport.  If the name is not one
     // of the available transports, or if the transport does not supply any configuration
     // UI, the method returns null.
@@ -9834,18 +9640,16 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getConfigurationIntent");
 
-        synchronized (mTransports) {
-            final IBackupTransport transport = mTransports.get(transportName);
-            if (transport != null) {
-                try {
-                    final Intent intent = transport.configurationIntent();
-                    if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
-                            + intent);
-                    return intent;
-                } catch (Exception e) {
-                    /* fall through to return null */
-                    Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
-                }
+        final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+        if (transport != null) {
+            try {
+                final Intent intent = transport.configurationIntent();
+                if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
+                        + intent);
+                return intent;
+            } catch (Exception e) {
+                /* fall through to return null */
+                Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
             }
         }
 
@@ -9861,17 +9665,15 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getDestinationString");
 
-        synchronized (mTransports) {
-            final IBackupTransport transport = mTransports.get(transportName);
-            if (transport != null) {
-                try {
-                    final String text = transport.currentDestinationString();
-                    if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
-                    return text;
-                } catch (Exception e) {
-                    /* fall through to return null */
-                    Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
-                }
+        final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+        if (transport != null) {
+            try {
+                final String text = transport.currentDestinationString();
+                if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
+                return text;
+            } catch (Exception e) {
+                /* fall through to return null */
+                Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
             }
         }
 
@@ -9883,18 +9685,16 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getDataManagementIntent");
 
-        synchronized (mTransports) {
-            final IBackupTransport transport = mTransports.get(transportName);
-            if (transport != null) {
-                try {
-                    final Intent intent = transport.dataManagementIntent();
-                    if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent "
-                            + intent);
-                    return intent;
-                } catch (Exception e) {
-                    /* fall through to return null */
-                    Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
-                }
+        final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+        if (transport != null) {
+            try {
+                final Intent intent = transport.dataManagementIntent();
+                if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent "
+                        + intent);
+                return intent;
+            } catch (Exception e) {
+                /* fall through to return null */
+                Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
             }
         }
 
@@ -9907,17 +9707,15 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "getDataManagementLabel");
 
-        synchronized (mTransports) {
-            final IBackupTransport transport = mTransports.get(transportName);
-            if (transport != null) {
-                try {
-                    final String text = transport.dataManagementLabel();
-                    if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
-                    return text;
-                } catch (Exception e) {
-                    /* fall through to return null */
-                    Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
-                }
+        final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+        if (transport != null) {
+            try {
+                final String text = transport.dataManagementLabel();
+                if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
+                return text;
+            } catch (Exception e) {
+                /* fall through to return null */
+                Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
             }
         }
 
@@ -9979,7 +9777,7 @@
         }
 
         // Do we have a transport to fetch data for us?
-        IBackupTransport transport = getTransport(mCurrentTransport);
+        IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
         if (transport == null) {
             if (DEBUG) Slog.w(TAG, "No transport");
             skip = true;
@@ -10033,7 +9831,7 @@
 
         boolean needPermission = true;
         if (transport == null) {
-            transport = mCurrentTransport;
+            transport = mTransportManager.getCurrentTransportName();
 
             if (packageName != null) {
                 PackageInfo app = null;
@@ -10127,7 +9925,7 @@
                     appIsStopped(packageInfo.applicationInfo)) {
                 return false;
             }
-            IBackupTransport transport = getTransport(mCurrentTransport);
+            IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
             if (transport != null) {
                 try {
                     return transport.isAppEligibleForBackup(packageInfo,
@@ -10156,7 +9954,7 @@
 
         ActiveRestoreSession(String packageName, String transport) {
             mPackageName = packageName;
-            mRestoreTransport = getTransport(transport);
+            mRestoreTransport = mTransportManager.getTransportBinder(transport);
         }
 
         public void markTimedOut() {
@@ -10515,7 +10313,7 @@
             pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled());
 
             pw.println("Transport whitelist:");
-            for (ComponentName transport : mTransportWhitelist) {
+            for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
                 pw.print("    ");
                 pw.println(transport.flattenToShortString());
             }
@@ -10524,9 +10322,9 @@
             final String[] transports = listAllTransports();
             if (transports != null) {
                 for (String t : listAllTransports()) {
-                    pw.println((t.equals(mCurrentTransport) ? "  * " : "    ") + t);
+                    pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? "  * " : "    ") + t);
                     try {
-                        IBackupTransport transport = getTransport(t);
+                        IBackupTransport transport = mTransportManager.getTransportBinder(t);
                         File dir = new File(mBaseStateDir, transport.transportDirName());
                         pw.println("       destination: " + transport.currentDestinationString());
                         pw.println("       intent: " + transport.configurationIntent());
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index d677f5e..a1a2c95 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -20,6 +20,8 @@
 import android.app.backup.IBackupObserver;
 import android.app.backup.IFullBackupRestoreObserver;
 import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
@@ -275,6 +277,12 @@
     }
 
     @Override
+    public ComponentName[] listAllTransportComponents() throws RemoteException {
+        BackupManagerService svc = mService;
+        return (svc != null) ? svc.listAllTransportComponents() : null;
+    }
+
+    @Override
     public String[] getTransportWhitelist() {
         BackupManagerService svc = mService;
         return (svc != null) ? svc.getTransportWhitelist() : null;
@@ -287,6 +295,15 @@
     }
 
     @Override
+    public void selectBackupTransportAsync(ComponentName transport,
+            ISelectBackupTransportCallback listener) throws RemoteException {
+        BackupManagerService svc = mService;
+        if (svc != null) {
+            svc.selectBackupTransportAsync(transport, listener);
+        }
+    }
+
+    @Override
     public Intent getConfigurationIntent(String transport) throws RemoteException {
         BackupManagerService svc = mService;
         return (svc != null) ? svc.getConfigurationIntent(transport) : null;
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
new file mode 100644
index 0000000..93d5a1e
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -0,0 +1,410 @@
+/*
+ * 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.backup;
+
+import android.app.backup.BackupManager;
+import android.app.backup.SelectBackupTransportCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.EventLogTags;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handles in-memory bookkeeping of all BackupTransport objects.
+ */
+class TransportManager {
+
+    private static final String TAG = "BackupTransportManager";
+
+    private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+    private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final Set<ComponentName> mTransportWhitelist;
+
+    /**
+     * This listener is called after we bind to any transport. If it returns true, this is a valid
+     * transport.
+     */
+    private final TransportBoundListener mTransportBoundListener;
+
+    private String mCurrentTransportName;
+
+    /** Lock on this before accessing mValidTransports and mBoundTransports. */
+    private final Object mTransportLock = new Object();
+
+    /**
+     * We have detected these transports on the device. Unless in exceptional cases, we are also
+     * bound to all of these.
+     */
+    @GuardedBy("mTransportLock")
+    private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>();
+
+    /** We are currently bound to these transports. */
+    @GuardedBy("mTransportLock")
+    private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
+
+    TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
+            TransportBoundListener listener) {
+        mContext = context;
+        mPackageManager = context.getPackageManager();
+        mTransportWhitelist = whitelist;
+        mCurrentTransportName = defaultTransport;
+        mTransportBoundListener = listener;
+    }
+
+    void onPackageAdded(String packageName) {
+        // New package added. Bind to all transports it contains.
+        synchronized (mTransportLock) {
+            log_verbose("Package added. Binding to all transports. " + packageName);
+            bindToAllInternal(packageName, null /* all components */);
+        }
+    }
+
+    void onPackageRemoved(String packageName) {
+        // Package removed. Remove all its transports from our list. These transports have already
+        // been removed from mBoundTransports because onServiceDisconnected would already been
+        // called on TransportConnection objects.
+        synchronized (mTransportLock) {
+            for (ComponentName transport : mValidTransports.keySet()) {
+                if (transport.getPackageName().equals(packageName)) {
+                    TransportConnection removed = mValidTransports.remove(transport);
+                    if (removed != null) {
+                        mContext.unbindService(removed);
+                        log_verbose("Package removed, Removing transport: " +
+                                transport.flattenToShortString());
+                    }
+                }
+            }
+        }
+    }
+
+    void onPackageChanged(String packageName, String[] components) {
+        synchronized (mTransportLock) {
+            // Remove all changed components from mValidTransports. We'll bind to them again
+            // and re-add them if still valid.
+            for (String component : components) {
+                ComponentName componentName = new ComponentName(packageName, component);
+                TransportConnection removed = mValidTransports.remove(componentName);
+                if (removed != null) {
+                    mContext.unbindService(removed);
+                    log_verbose("Package changed. Removing transport: " +
+                            componentName.flattenToShortString());
+                }
+            }
+            bindToAllInternal(packageName, components);
+        }
+    }
+
+    IBackupTransport getTransportBinder(String transportName) {
+        synchronized (mTransportLock) {
+            ComponentName component = mBoundTransports.get(transportName);
+            if (component == null) {
+                Slog.w(TAG, "Transport " + transportName + " not bound.");
+                return null;
+            }
+            TransportConnection conn = mValidTransports.get(component);
+            if (conn == null) {
+                Slog.w(TAG, "Transport " + transportName + " not valid.");
+                return null;
+            }
+            return conn.getBinder();
+        }
+    }
+
+    IBackupTransport getCurrentTransportBinder() {
+        return getTransportBinder(mCurrentTransportName);
+    }
+
+    String getTransportName(IBackupTransport binder) {
+        synchronized (mTransportLock) {
+            for (TransportConnection conn : mValidTransports.values()) {
+                if (conn.getBinder() == binder) {
+                    return conn.getName();
+                }
+            }
+        }
+        return null;
+    }
+
+    String[] getBoundTransportNames() {
+        synchronized (mTransportLock) {
+            return mBoundTransports.keySet().toArray(new String[0]);
+        }
+    }
+
+    ComponentName[] getAllTransportCompenents() {
+        synchronized (mTransportLock) {
+            return mValidTransports.keySet().toArray(new ComponentName[0]);
+        }
+    }
+
+    String getCurrentTransportName() {
+        return mCurrentTransportName;
+    }
+
+    Set<ComponentName> getTransportWhitelist() {
+        return mTransportWhitelist;
+    }
+
+    String selectTransport(String transport) {
+        synchronized (mTransportLock) {
+            String prevTransport = mCurrentTransportName;
+            mCurrentTransportName = transport;
+            return prevTransport;
+        }
+    }
+
+    void ensureTransportReady(ComponentName transportComponent, SelectBackupTransportCallback listener) {
+        synchronized (mTransportLock) {
+            TransportConnection conn = mValidTransports.get(transportComponent);
+            if (conn == null) {
+                listener.onFailure(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
+                return;
+            }
+            // Transport can be unbound if the process hosting it crashed.
+            conn.bindIfUnbound();
+            conn.addListener(listener);
+        }
+    }
+
+    void registerAllTransports() {
+        bindToAllInternal(null /* all packages */, null /* all components */);
+    }
+
+    /**
+     * Bind to all transports belonging to the given package and the given component list.
+     * null acts a wildcard.
+     *
+     * If packageName is null, bind to all transports in all packages.
+     * If components is null, bind to all transports in the given package.
+     */
+    private void bindToAllInternal(String packageName, String[] components) {
+        PackageInfo pkgInfo = null;
+        if (packageName != null) {
+            try {
+                pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                Slog.w(TAG, "Package not found: " + packageName);
+                return;
+            }
+        }
+
+        Intent intent = new Intent(mTransportServiceIntent);
+        if (packageName != null) {
+            intent.setPackage(packageName);
+        }
+
+        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
+                intent, 0, UserHandle.USER_SYSTEM);
+        if (hosts != null) {
+            for (ResolveInfo host : hosts) {
+                final ServiceInfo info = host.serviceInfo;
+                boolean shouldBind = false;
+                if (components != null && packageName != null) {
+                    for (String component : components) {
+                        ComponentName cn = new ComponentName(pkgInfo.packageName, component);
+                        if (info.getComponentName().equals(cn)) {
+                            shouldBind = true;
+                            break;
+                        }
+                    }
+                } else {
+                    shouldBind = true;
+                }
+                if (shouldBind && isTransportTrusted(info.getComponentName())) {
+                    tryBindTransport(info);
+                }
+            }
+        }
+    }
+
+    /** Transport has to be whitelisted and privileged. */
+    private boolean isTransportTrusted(ComponentName transport) {
+        if (!mTransportWhitelist.contains(transport)) {
+            Slog.w(TAG, "BackupTransport " + transport.flattenToShortString() +
+                    " not whitelisted.");
+            return false;
+        }
+        try {
+            PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0);
+            if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+                    == 0) {
+                Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
+                return false;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Package not found.", e);
+            return false;
+        }
+        return true;
+    }
+
+    private void tryBindTransport(ServiceInfo transport) {
+        Slog.d(TAG, "Binding to transport: " + transport.getComponentName().flattenToShortString());
+        // TODO: b/22388012 (Multi user backup and restore)
+        TransportConnection connection = new TransportConnection(transport.getComponentName());
+        if (bindToTransport(transport.getComponentName(), connection)) {
+            synchronized (mTransportLock) {
+                mValidTransports.put(transport.getComponentName(), connection);
+            }
+        } else {
+            Slog.w(TAG, "Couldn't bind to transport " + transport.getComponentName());
+        }
+    }
+
+    private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) {
+        Intent intent = new Intent(mTransportServiceIntent)
+                .setComponent(componentName);
+        return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+                UserHandle.SYSTEM);
+    }
+
+    private class TransportConnection implements ServiceConnection {
+
+        // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
+        private IBackupTransport mBinder;
+        private final List<SelectBackupTransportCallback> mListeners = new ArrayList<>();
+        private String mTransportName;
+
+        private final ComponentName mTransportComponent;
+
+        private TransportConnection(ComponentName transportComponent) {
+            mTransportComponent = transportComponent;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName component, IBinder binder) {
+            synchronized (mTransportLock) {
+                mBinder = IBackupTransport.Stub.asInterface(binder);
+                boolean success = false;
+
+                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+                    component.flattenToShortString(), 1);
+
+                try {
+                    mTransportName = mBinder.name();
+                    // BackupManager requests some fields from the transport. If they are
+                    // invalid, throw away this transport.
+                    success = mTransportBoundListener.onTransportBound(mBinder);
+                } catch (RemoteException e) {
+                    success = false;
+                    Slog.e(TAG, "Couldn't get transport name.", e);
+                } finally {
+                    if (success) {
+                        Slog.d(TAG, "Bound to transport: " + component.flattenToShortString());
+                        mBoundTransports.put(mTransportName, component);
+                        for (SelectBackupTransportCallback listener : mListeners) {
+                            listener.onSuccess(mTransportName);
+                        }
+                    } else {
+                        Slog.w(TAG, "Bound to transport " + component.flattenToShortString() +
+                                " but it is invalid");
+                        EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+                                component.flattenToShortString(), 0);
+                        mContext.unbindService(this);
+                        mValidTransports.remove(component);
+                        mBinder = null;
+                        for (SelectBackupTransportCallback listener : mListeners) {
+                            listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID);
+                        }
+                    }
+                    mListeners.clear();
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName component) {
+            synchronized (mTransportLock) {
+                mBinder = null;
+                mBoundTransports.remove(mTransportName);
+            }
+            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+                    component.flattenToShortString(), 0);
+            Slog.w(TAG, "Disconnected from transport " + component.flattenToShortString());
+        }
+
+        private IBackupTransport getBinder() {
+            synchronized (mTransportLock) {
+                return mBinder;
+            }
+        }
+
+        private String getName() {
+            synchronized (mTransportLock) {
+                return mTransportName;
+            }
+        }
+
+        private void bindIfUnbound() {
+            synchronized (mTransportLock) {
+                if (mBinder == null) {
+                    Slog.d(TAG,
+                            "Rebinding to transport " + mTransportComponent.flattenToShortString());
+                    bindToTransport(mTransportComponent, this);
+                }
+            }
+        }
+
+        private void addListener(SelectBackupTransportCallback listener) {
+            synchronized (mTransportLock) {
+                if (mBinder == null) {
+                    // We are waiting for bind to complete. If mBinder is set to null after the bind
+                    // is complete due to transport being invalid, we won't find 'this' connection
+                    // object in mValidTransports list and this function can't be called.
+                    mListeners.add(listener);
+                } else {
+                    listener.onSuccess(mTransportName);
+                }
+            }
+        }
+    }
+
+    interface TransportBoundListener {
+        /** Should return true if this is a valid transport. */
+        boolean onTransportBound(IBackupTransport binder);
+    }
+
+    private static void log_verbose(String message) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Slog.v(TAG, message);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 0e07ec0..b1560e6 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -275,7 +275,7 @@
                 } catch (IllegalArgumentException e) {
                     // Failed to parse the settings string, log this and move on
                     // with defaults.
-                    Slog.e(TAG, "Bad device idle settings", e);
+                    Slog.e(TAG, "Bad alarm manager settings", e);
                 }
 
                 MIN_FUTURITY = mParser.getLong(KEY_MIN_FUTURITY, DEFAULT_MIN_FUTURITY);
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index e7f1d16..822fc96 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -2034,21 +2034,32 @@
                 writer.println("  time since enabled: " + onDurationString + "\n");
             }
 
-            writer.println("Enable log:");
-            for (ActiveLog log : mActiveLogs) {
-                writer.println(log);
+            if (mActiveLogs.size() == 0) {
+                writer.println("Bluetooth never enabled!");
+            } else {
+                writer.println("Enable log:");
+                for (ActiveLog log : mActiveLogs) {
+                    writer.println("  " + log);
+                }
             }
 
-            writer.println("\n" + mBleApps.size() + " BLE Apps registered:");
+            String bleAppString = "No BLE Apps registered.";
+            if (mBleApps.size() == 1) {
+                bleAppString = "1 BLE App registered:";
+            } else if (mBleApps.size() > 1) {
+                bleAppString = mBleApps.size() + " BLE Apps registered:";
+            }
+            writer.println("\n" + bleAppString);
             for (ClientDeathRecipient app : mBleApps.values()) {
-                writer.println(app.getPackageName());
+                writer.println("  " + app.getPackageName());
             }
 
+            writer.println("");
             writer.flush();
             if (args.length == 0) {
-              // Add arg to produce output
-              args = new String[1];
-              args[0] = "--print";
+                // Add arg to produce output
+                args = new String[1];
+                args[0] = "--print";
             }
         }
 
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 2d40e8e..8ef34dc 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -23,6 +23,7 @@
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -70,6 +71,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.ILockSettings;
@@ -123,20 +125,23 @@
 
     private final Object mSeparateChallengeLock = new Object();
 
+    private final Injector mInjector;
     private final Context mContext;
     private final Handler mHandler;
-    private final LockSettingsStorage mStorage;
+    @VisibleForTesting
+    protected final LockSettingsStorage mStorage;
     private final LockSettingsStrongAuth mStrongAuth;
     private final SynchronizedStrongAuthTracker mStrongAuthTracker;
 
-    private LockPatternUtils mLockPatternUtils;
+    private final LockPatternUtils mLockPatternUtils;
+    private final NotificationManager mNotificationManager;
+    private final UserManager mUserManager;
+    private final IActivityManager mActivityManager;
+
+    private final KeyStore mKeyStore;
+
     private boolean mFirstCallToVold;
-    private IGateKeeperService mGateKeeperService;
-    private NotificationManager mNotificationManager;
-    private UserManager mUserManager;
-
-    private final KeyStore mKeyStore = KeyStore.getInstance();
-
+    protected IGateKeeperService mGateKeeperService;
     /**
      * The UIDs that are used for system credential storage in keystore.
      */
@@ -177,7 +182,9 @@
         }
     }
 
-    private class SynchronizedStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+    @VisibleForTesting
+    protected static class SynchronizedStrongAuthTracker
+            extends LockPatternUtils.StrongAuthTracker {
         public SynchronizedStrongAuthTracker(Context context) {
             super(context);
         }
@@ -196,8 +203,8 @@
             }
         }
 
-        void register() {
-            mStrongAuth.registerStrongAuthTracker(this.mStub);
+        void register(LockSettingsStrongAuth strongAuth) {
+            strongAuth.registerStrongAuthTracker(this.mStub);
         }
     }
 
@@ -211,7 +218,7 @@
     public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) {
         if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
         // Only for managed profile
-        if (!UserManager.get(mContext).getUserInfo(managedUserId).isManagedProfile()) {
+        if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) {
             return;
         }
         // Do not tie managed profile when work challenge is enabled
@@ -258,38 +265,103 @@
         }
     }
 
-    public LockSettingsService(Context context) {
-        mContext = context;
-        mHandler = new Handler();
-        mStrongAuth = new LockSettingsStrongAuth(context);
-        // Open the database
+    static class Injector {
 
-        mLockPatternUtils = new LockPatternUtils(context);
+        protected Context mContext;
+
+        public Injector(Context context) {
+            mContext = context;
+        }
+
+        public Context getContext() {
+            return mContext;
+        }
+
+        public Handler getHandler() {
+            return new Handler();
+        }
+
+        public LockSettingsStorage getStorage() {
+            final LockSettingsStorage storage = new LockSettingsStorage(mContext);
+            storage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+                @Override
+                public void initialize(SQLiteDatabase db) {
+                    // Get the lockscreen default from a system property, if available
+                    boolean lockScreenDisable = SystemProperties.getBoolean(
+                            "ro.lockscreen.disable.default", false);
+                    if (lockScreenDisable) {
+                        storage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
+                    }
+                }
+            });
+            return storage;
+        }
+
+        public LockSettingsStrongAuth getStrongAuth() {
+            return new LockSettingsStrongAuth(mContext);
+        }
+
+        public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+            return new SynchronizedStrongAuthTracker(mContext);
+        }
+
+        public IActivityManager getActivityManager() {
+            return ActivityManager.getService();
+        }
+
+        public LockPatternUtils getLockPatternUtils() {
+            return new LockPatternUtils(mContext);
+        }
+
+        public NotificationManager getNotificationManager() {
+            return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+
+        public UserManager getUserManager() {
+            return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        }
+
+        public KeyStore getKeyStore() {
+            return KeyStore.getInstance();
+        }
+
+        public IStorageManager getStorageManager() {
+            final IBinder service = ServiceManager.getService("mount");
+            if (service != null) {
+                return IStorageManager.Stub.asInterface(service);
+            }
+            return null;
+        }
+    }
+
+    public LockSettingsService(Context context) {
+        this(new Injector(context));
+    }
+
+    @VisibleForTesting
+    protected LockSettingsService(Injector injector) {
+        mInjector = injector;
+        mContext = injector.getContext();
+        mKeyStore = injector.getKeyStore();
+        mHandler = injector.getHandler();
+        mStrongAuth = injector.getStrongAuth();
+        mActivityManager = injector.getActivityManager();
+
+        mLockPatternUtils = injector.getLockPatternUtils();
         mFirstCallToVold = true;
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_STARTING);
         filter.addAction(Intent.ACTION_USER_REMOVED);
-        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+        injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
+                null, null);
 
-        mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() {
-            @Override
-            public void initialize(SQLiteDatabase db) {
-                // Get the lockscreen default from a system property, if available
-                boolean lockScreenDisable = SystemProperties.getBoolean(
-                        "ro.lockscreen.disable.default", false);
-                if (lockScreenDisable) {
-                    mStorage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
-                }
-            }
-        });
-        mNotificationManager = (NotificationManager)
-                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mStrongAuthTracker = new SynchronizedStrongAuthTracker(mContext);
-        mStrongAuthTracker.register();
-
+        mStorage = injector.getStorage();
+        mNotificationManager = injector.getNotificationManager();
+        mUserManager = injector.getUserManager();
+        mStrongAuthTracker = injector.getStrongAuthTracker();
+        mStrongAuthTracker.register(mStrongAuth);
     }
 
     /**
@@ -748,7 +820,8 @@
         ks.unlock(userHandle, password);
     }
 
-    private String getDecryptedPasswordForTiedProfile(int userId)
+    @VisibleForTesting
+    protected String getDecryptedPasswordForTiedProfile(int userId)
             throws KeyStoreException, UnrecoverableKeyException,
             NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
             InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
@@ -814,7 +887,7 @@
         };
 
         try {
-            ActivityManager.getService().unlockUser(userId, token, secret, listener);
+            mActivityManager.unlockUser(userId, token, secret, listener);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -961,7 +1034,8 @@
         }
     }
 
-    private void tieProfileLockToParent(int userId, String password) {
+    @VisibleForTesting
+    protected void tieProfileLockToParent(int userId, String password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
         byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
         byte[] encryptionResult;
@@ -1085,8 +1159,8 @@
 
     private void addUserKeyAuth(int userId, byte[] token, byte[] secret)
             throws RemoteException {
-        final UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
-        final IStorageManager storageManager = getStorageManager();
+        final UserInfo userInfo = mUserManager.getUserInfo(userId);
+        final IStorageManager storageManager = mInjector.getStorageManager();
         final long callingId = Binder.clearCallingIdentity();
         try {
             storageManager.addUserKeyAuth(userId, userInfo.serialNumber, token, secret);
@@ -1097,7 +1171,7 @@
 
     private void fixateNewestUserKeyAuth(int userId)
             throws RemoteException {
-        final IStorageManager storageManager = getStorageManager();
+        final IStorageManager storageManager = mInjector.getStorageManager();
         final long callingId = Binder.clearCallingIdentity();
         try {
             storageManager.fixateNewestUserKeyAuth(userId);
@@ -1396,7 +1470,7 @@
         // we should, within the first minute of decrypting the phone if this
         // service can't connect to vold, it restarts, and then the new instance
         // does successfully connect.
-        final IStorageManager service = getStorageManager();
+        final IStorageManager service = mInjector.getStorageManager();
         String password;
         long identity = Binder.clearCallingIdentity();
         try {
@@ -1561,14 +1635,6 @@
             Secure.LOCK_SCREEN_OWNER_INFO
     };
 
-    private IStorageManager getStorageManager() {
-        final IBinder service = ServiceManager.getService("mount");
-        if (service != null) {
-            return IStorageManager.Stub.asInterface(service);
-        }
-        return null;
-    }
-
     private class GateKeeperDiedRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
index f72663a..e131251 100644
--- a/services/core/java/com/android/server/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/LockSettingsShellCommand.java
@@ -77,7 +77,8 @@
             }
             return 0;
         } catch (Exception e) {
-            getErrPrintWriter().println("Error while executing command: " + e);
+            getErrPrintWriter().println("Error while executing command: " + cmd);
+            e.printStackTrace(getErrPrintWriter());
             return -1;
         }
     }
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 3d973a0..c858036 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -119,9 +119,13 @@
         boolean isBaseZeroPattern;
     }
 
-    public LockSettingsStorage(Context context, Callback callback) {
+    public LockSettingsStorage(Context context) {
         mContext = context;
-        mOpenHelper = new DatabaseHelper(context, callback);
+        mOpenHelper = new DatabaseHelper(context);
+    }
+
+    public void setDatabaseOnCreateCallback(Callback callback) {
+        mOpenHelper.setCallback(callback);
     }
 
     public void writeKeyValue(String key, String value, int userId) {
@@ -472,11 +476,14 @@
 
         private static final int DATABASE_VERSION = 2;
 
-        private final Callback mCallback;
+        private Callback mCallback;
 
-        public DatabaseHelper(Context context, Callback callback) {
+        public DatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
             setWriteAheadLoggingEnabled(true);
+        }
+
+        public void setCallback(Callback callback) {
             mCallback = callback;
         }
 
@@ -492,7 +499,9 @@
         @Override
         public void onCreate(SQLiteDatabase db) {
             createTable(db);
-            mCallback.initialize(db);
+            if (mCallback != null) {
+                mCallback.initialize(db);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index e8ecc3e..dab4dfb 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -41,6 +41,10 @@
 import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
 import android.net.Uri;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -76,7 +80,9 @@
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 /**
  * Backing service for {@link android.net.NetworkScoreManager}.
@@ -391,6 +397,7 @@
                     isEmpty = callbackList == null
                             || callbackList.getRegisteredCallbackCount() == 0;
                 }
+
                 if (isEmpty) {
                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
                         Log.v(TAG, "No scorer registered for type " + entry.getKey()
@@ -399,18 +406,10 @@
                     continue;
                 }
 
-                sendCallback(new Consumer<INetworkScoreCache>() {
-                    @Override
-                    public void accept(INetworkScoreCache networkScoreCache) {
-                        try {
-                            networkScoreCache.updateScores(entry.getValue());
-                        } catch (RemoteException e) {
-                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                                Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
-                            }
-                        }
-                    }
-                }, Collections.singleton(callbackList));
+                final BiConsumer<INetworkScoreCache, Object> consumer =
+                        new FilteringCacheUpdatingConsumer(mContext, entry.getValue(),
+                                entry.getKey());
+                sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
             }
 
             return true;
@@ -419,6 +418,229 @@
         }
     }
 
+    /**
+     * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
+     * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
+     * accepted {@link INetworkScoreCache} implementation.
+     */
+    @VisibleForTesting
+    public static class FilteringCacheUpdatingConsumer
+            implements BiConsumer<INetworkScoreCache, Object> {
+        private final Context mContext;
+        private final List<ScoredNetwork> mScoredNetworkList;
+        private final int mNetworkType;
+        // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters.
+        private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
+        private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+
+        public FilteringCacheUpdatingConsumer(Context context,
+                List<ScoredNetwork> scoredNetworkList, int networkType) {
+            this(context, scoredNetworkList, networkType, null, null);
+        }
+
+        @VisibleForTesting
+        public FilteringCacheUpdatingConsumer(Context context,
+                List<ScoredNetwork> scoredNetworkList, int networkType,
+                Function<List<ScoredNetwork>, List<ScoredNetwork>> currentNetworkFilter,
+                Function<List<ScoredNetwork>, List<ScoredNetwork>> scanResultsFilter) {
+            mContext = context;
+            mScoredNetworkList = scoredNetworkList;
+            mNetworkType = networkType;
+            mCurrentNetworkFilter = currentNetworkFilter;
+            mScanResultsFilter = scanResultsFilter;
+        }
+
+        @Override
+        public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
+            int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
+            if (cookie instanceof Integer) {
+                filterType = (Integer) cookie;
+            }
+
+            try {
+                final List<ScoredNetwork> filteredNetworkList =
+                        filterScores(mScoredNetworkList, filterType);
+                if (!filteredNetworkList.isEmpty()) {
+                    networkScoreCache.updateScores(
+                            Collections.unmodifiableList(filteredNetworkList));
+                }
+            } catch (RemoteException e) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
+                }
+            }
+        }
+
+        /**
+         * Applies the appropriate filter and returns the filtered results.
+         */
+        private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
+                int filterType) {
+            switch (filterType) {
+                case NetworkScoreManager.CACHE_FILTER_NONE:
+                    return scoredNetworkList;
+
+                case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
+                    if (mCurrentNetworkFilter == null) {
+                        mCurrentNetworkFilter =
+                                new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
+                    }
+                    return mCurrentNetworkFilter.apply(scoredNetworkList);
+
+                case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
+                    if (mScanResultsFilter == null) {
+                        mScanResultsFilter = new ScanResultsScoreCacheFilter(
+                                new ScanResultsSupplier(mContext));
+                    }
+                    return mScanResultsFilter.apply(scoredNetworkList);
+
+                default:
+                    Log.w(TAG, "Unknown filter type: " + filterType);
+                    return scoredNetworkList;
+            }
+        }
+    }
+
+    /**
+     * Helper class that improves the testability of the cache filter Functions.
+     */
+    private static class WifiInfoSupplier implements Supplier<WifiInfo> {
+        private final Context mContext;
+
+        WifiInfoSupplier(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public WifiInfo get() {
+            WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+            if (wifiManager != null) {
+                return wifiManager.getConnectionInfo();
+            }
+            Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
+            return null;
+        }
+    }
+
+    /**
+     * Helper class that improves the testability of the cache filter Functions.
+     */
+    private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
+        private final Context mContext;
+
+        ScanResultsSupplier(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public List<ScanResult> get() {
+            WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
+            if (wifiScanner != null) {
+                return wifiScanner.getSingleScanResults();
+            }
+            Log.w(TAG, "WifiScanner is null, failed to return scan results.");
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
+     * {@link ScoredNetwork} associated with the current network. If no network is connected the
+     * returned list will be empty.
+     * <p>
+     * Note: this filter performs some internal caching for consistency and performance. The
+     *       current network is determined at construction time and never changed. Also, the
+     *       last filtered list is saved so if the same input is provided multiple times in a row
+     *       the computation is only done once.
+     */
+    @VisibleForTesting
+    public static class CurrentNetworkScoreCacheFilter
+            implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
+        private final NetworkKey mCurrentNetwork;
+        private Pair<List<ScoredNetwork>, Integer> mCache;
+
+        CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
+            mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
+        }
+
+        @Override
+        public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
+            if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            final int inputListHash = scoredNetworks.hashCode();
+            if (mCache == null || mCache.second != inputListHash) {
+                ScoredNetwork currentScore = null;
+                for (int i = 0; i < scoredNetworks.size(); i++) {
+                    final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+                    if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
+                        currentScore = scoredNetwork;
+                        break;
+                    }
+                }
+
+                if (currentScore == null) {
+                    mCache = Pair.create(Collections.emptyList(), inputListHash);
+                } else {
+                    mCache = Pair.create(Collections.singletonList(currentScore), inputListHash);
+                }
+            }
+
+            return mCache.first;
+        }
+    }
+
+    /**
+     * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
+     * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
+     * If there are no {@link ScanResult}s the returned list will be empty.
+     * <p>
+     * Note: this filter performs some internal caching for consistency and performance. The
+     *       current set of ScanResults is determined at construction time and never changed.
+     *       Also, the last filtered list is saved so if the same input is provided multiple
+     *       times in a row the computation is only done once.
+     */
+    @VisibleForTesting
+    public static class ScanResultsScoreCacheFilter
+            implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
+        private final List<NetworkKey> mScanResultKeys;
+        private Pair<List<ScoredNetwork>, Integer> mCache;
+
+        ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
+            mScanResultKeys = new ArrayList<>();
+            List<ScanResult> scanResults = resultsSupplier.get();
+            for (int i = 0; i < scanResults.size(); i++) {
+                ScanResult scanResult = scanResults.get(i);
+                mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
+            }
+        }
+
+        @Override
+        public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
+            if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            final int inputListHash = scoredNetworks.hashCode();
+            if (mCache == null || mCache.second != inputListHash) {
+                List<ScoredNetwork> filteredScores = new ArrayList<>();
+                for (int i = 0; i < scoredNetworks.size(); i++) {
+                    final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+                    for (int j = 0; j < mScanResultKeys.size(); j++) {
+                        final NetworkKey scanResultKey = mScanResultKeys.get(j);
+                        if (scanResultKey.equals(scoredNetwork.networkKey)) {
+                            filteredScores.add(scoredNetwork);
+                        }
+                    }
+                }
+                mCache = Pair.create(filteredScores, inputListHash);
+            }
+
+            return mCache.first;
+        }
+    }
+
     private boolean isCallerSystemUid() {
         // REQUEST_NETWORK_SCORES is a signature only permission.
         return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
@@ -499,9 +721,9 @@
 
     /** Clear scores. Callers are responsible for checking permissions as appropriate. */
     private void clearInternal() {
-        sendCallback(new Consumer<INetworkScoreCache>() {
+        sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
             @Override
-            public void accept(INetworkScoreCache networkScoreCache) {
+            public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
                 try {
                     networkScoreCache.clearScores();
                 } catch (RemoteException e) {
@@ -675,9 +897,9 @@
         }
         writer.println("Current scorer: " + currentScorer.packageName);
 
-        sendCallback(new Consumer<INetworkScoreCache>() {
+        sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
             @Override
-            public void accept(INetworkScoreCache networkScoreCache) {
+            public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
                 try {
                   TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
                 } catch (IOException | RemoteException e) {
@@ -708,14 +930,15 @@
         }
     }
 
-    private void sendCallback(Consumer<INetworkScoreCache> consumer,
+    private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
             Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
         for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
             synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
                 final int count = callbackList.beginBroadcast();
                 try {
                     for (int i = 0; i < count; i++) {
-                        consumer.accept(callbackList.getBroadcastItem(i));
+                        consumer.accept(callbackList.getBroadcastItem(i),
+                                callbackList.getRegisteredCallbackCookie(i));
                     }
                 } finally {
                     callbackList.finishBroadcast();
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index bb8401f..d51e96a 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.os.Build;
 import android.os.RecoverySystem;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -45,6 +46,7 @@
 public class RescueParty {
     private static final String TAG = "RescueParty";
 
+    private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
     private static final String PROP_RESCUE_LEVEL = "sys.rescue_level";
     private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
     private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
@@ -55,16 +57,23 @@
     private static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
     private static final int LEVEL_FACTORY_RESET = 4;
 
+    private static final boolean DISABLE_RESET_SETTINGS = true;
+
     /** Threshold for boot loops */
     private static final Threshold sBoot = new BootThreshold();
     /** Threshold for app crash loops */
     private static SparseArray<Threshold> sApps = new SparseArray<>();
 
+    private static boolean isDisabled() {
+        return Build.IS_ENG || SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false);
+    }
+
     /**
      * Take note of a boot event. If we notice too many of these events
      * happening in rapid succession, we'll send out a rescue party.
      */
     public static void noteBoot(Context context) {
+        if (isDisabled()) return;
         if (sBoot.incrementAndTest()) {
             sBoot.reset();
             incrementRescueLevel(sBoot.uid);
@@ -77,6 +86,7 @@
      * events happening in rapid succession, we'll send out a rescue party.
      */
     public static void notePersistentAppCrash(Context context, int uid) {
+        if (isDisabled()) return;
         Threshold t = sApps.get(uid);
         if (t == null) {
             t = new AppThreshold(uid);
@@ -151,6 +161,8 @@
     }
 
     private static void resetAllSettings(Context context, int mode) throws Exception {
+        if (DISABLE_RESET_SETTINGS) return;
+
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
         Exception res = null;
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 440ac90..8f99127 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -45,12 +45,16 @@
 import android.service.dreams.Sandman;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.view.WindowManagerInternal;
 import android.view.WindowManagerPolicy;
 
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
 
 import com.android.internal.R;
 import com.android.internal.app.DisableCarModeActivity;
@@ -343,6 +347,28 @@
         }
 
         @Override
+        public String[] getAvailableThemes() {
+            if (getContext().checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_THEME_OVERLAY)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.e(TAG, "getAvailableThemes requires MODIFY_THEME_OVERLAY permission");
+                return null;
+            }
+            String def = SystemProperties.get("ro.boot.vendor.overlay.theme");
+            if (TextUtils.isEmpty(def)) {
+                def = null;
+            }
+            String[] fileList = new File("/vendor/overlay").list();
+            if (fileList == null) return new String[0];
+            ArrayList<String> options = new ArrayList(fileList.length + 1);
+            Collections.addAll(options, fileList);
+            if (!options.contains(def)) {
+                options.add(0, def);
+            }
+            return options.toArray(new String[options.size()]);
+        }
+
+        @Override
         public int getNightMode() {
             synchronized (mLock) {
                 return mNightMode;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
new file mode 100644
index 0000000..3c90f93
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -0,0 +1,157 @@
+/*
+ * 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.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+/**
+ * Settings constants that can modify the activity manager's behavior.
+ */
+final class ActivityManagerConstants extends ContentObserver {
+    // Key names stored in the settings value.
+    private static final String KEY_ENFORCE_BG_CHECK = "enforce_bg_check";
+    private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
+
+    private static final boolean DEFAULT_ENFORCE_BG_CHECK = SystemProperties.getBoolean(
+            "debug.bgcheck", false);
+    private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
+
+    // Enforce background check on apps targeting O?
+    public boolean ENFORCE_BG_CHECK = DEFAULT_ENFORCE_BG_CHECK;
+
+    // Maximum number of cached processes we will allow.
+    public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
+
+    private final ActivityManagerService mService;
+    private ContentResolver mResolver;
+    private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+    private int mOverrideMaxCachedProcesses = -1;
+
+    // The maximum number of cached processes we will keep around before killing them.
+    // NOTE: this constant is *only* a control to not let us go too crazy with
+    // keeping around processes on devices with large amounts of RAM.  For devices that
+    // are tighter on RAM, the out of memory killer is responsible for killing background
+    // processes as RAM is needed, and we should *never* be relying on this limit to
+    // kill them.  Also note that this limit only applies to cached background processes;
+    // we have no limit on the number of service, visible, foreground, or other such
+    // processes and the number of those processes does not count against the cached
+    // process limit.
+    public int CUR_MAX_CACHED_PROCESSES;
+
+    // The maximum number of empty app processes we will let sit around.
+    public int CUR_MAX_EMPTY_PROCESSES;
+
+    // The number of empty apps at which we don't consider it necessary to do
+    // memory trimming.
+    public int CUR_TRIM_EMPTY_PROCESSES;
+
+    // The number of cached at which we don't consider it necessary to do
+    // memory trimming.
+    public int CUR_TRIM_CACHED_PROCESSES;
+
+    public ActivityManagerConstants(ActivityManagerService service, Handler handler) {
+        super(handler);
+        mService = service;
+        updateMaxCachedProcesses();
+    }
+
+    public void start(ContentResolver resolver) {
+        mResolver = resolver;
+        mResolver.registerContentObserver(Settings.Global.getUriFor(
+                Settings.Global.ACTIVITY_MANAGER_CONSTANTS), false, this);
+        updateConstants();
+    }
+
+    public void setOverrideMaxCachedProcesses(int value) {
+        mOverrideMaxCachedProcesses = value;
+        updateMaxCachedProcesses();
+    }
+
+    public int getOverrideMaxCachedProcesses() {
+        return mOverrideMaxCachedProcesses;
+    }
+
+    public static int computeEmptyProcessLimit(int totalProcessLimit) {
+        return totalProcessLimit/2;
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        updateConstants();
+    }
+
+    private void updateConstants() {
+        synchronized (mService) {
+            try {
+                mParser.setString(Settings.Global.getString(mResolver,
+                        Settings.Global.ACTIVITY_MANAGER_CONSTANTS));
+            } catch (IllegalArgumentException e) {
+                // Failed to parse the settings string, log this and move on
+                // with defaults.
+                Slog.e("ActivityManagerConstants", "Bad activity manager config settings", e);
+            }
+
+            ENFORCE_BG_CHECK = mParser.getBoolean(KEY_ENFORCE_BG_CHECK, DEFAULT_ENFORCE_BG_CHECK);
+            MAX_CACHED_PROCESSES = mParser.getInt(KEY_MAX_CACHED_PROCESSES,
+                    DEFAULT_MAX_CACHED_PROCESSES);
+            updateMaxCachedProcesses();
+        }
+    }
+
+    private void updateMaxCachedProcesses() {
+        CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
+                ? MAX_CACHED_PROCESSES : mOverrideMaxCachedProcesses;
+        CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
+
+        // Note the trim levels do NOT depend on the override process limit, we want
+        // to consider the same level the point where we do trimming regardless of any
+        // additional enforced limit.
+        final int rawMaxEmptyProcesses = computeEmptyProcessLimit(MAX_CACHED_PROCESSES);
+        CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses/2;
+        CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
+                + Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":");
+
+        pw.print("  "); pw.print(KEY_ENFORCE_BG_CHECK); pw.print("=");
+        pw.println(ENFORCE_BG_CHECK);
+
+        pw.print("  "); pw.print(KEY_MAX_CACHED_PROCESSES); pw.print("=");
+        pw.println(MAX_CACHED_PROCESSES);
+
+        pw.println();
+        if (mOverrideMaxCachedProcesses >= 0) {
+            pw.print("  mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
+        }
+        pw.print("  CUR_MAX_CACHED_PROCESSES="); pw.println(CUR_MAX_CACHED_PROCESSES);
+        pw.print("  CUR_MAX_EMPTY_PROCESSES="); pw.println(CUR_MAX_EMPTY_PROCESSES);
+        pw.print("  CUR_TRIM_EMPTY_PROCESSES="); pw.println(CUR_TRIM_EMPTY_PROCESSES);
+        pw.print("  CUR_TRIM_CACHED_PROCESSES="); pw.println(CUR_TRIM_CACHED_PROCESSES);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a72950b..43afbd1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -761,11 +761,6 @@
     ProcessRecord mHeavyWeightProcess = null;
 
     /**
-     * Are we enforcing background restrictions?
-     */
-    final boolean mEnforceBackgroundCheck;
-
-    /**
      * Non-persistent app uid whitelist for background restrictions
      */
     int[] mBackgroundUidWhitelist = new int[] {
@@ -1515,9 +1510,6 @@
      */
     boolean mBooted = false;
 
-    int mProcessLimit = ProcessList.MAX_CACHED_APPS;
-    int mProcessLimitOverride = -1;
-
     WindowManagerService mWindowManager;
     final ActivityThread mSystemThread;
 
@@ -1639,6 +1631,8 @@
     final MainHandler mHandler;
     final UiHandler mUiHandler;
 
+    final ActivityManagerConstants mConstants;
+
     PackageManagerInternal mPackageManagerInt;
 
     // VoiceInteraction session ID that changes for each new request except when
@@ -2620,10 +2614,9 @@
         mPermissionReviewRequired = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_permissionReviewRequired);
 
-        mEnforceBackgroundCheck = SystemProperties.getBoolean("debug.bgcheck", false);
         mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
         if (DEBUG_BACKGROUND_CHECK) {
-            Slog.d(TAG, "Enforcing O+ bg restrictions: " + mEnforceBackgroundCheck);
+            Slog.d(TAG, "Enforcing O+ bg restrictions: " + mConstants.ENFORCE_BG_CHECK);
             StringBuilder sb = new StringBuilder(200);
             sb.append("  ");
             for (String a : mBackgroundLaunchBroadcasts) {
@@ -2639,6 +2632,8 @@
         mHandler = new MainHandler(mHandlerThread.getLooper());
         mUiHandler = new UiHandler();
 
+        mConstants = new ActivityManagerConstants(this, mHandler);
+
         /* static; one-time init here */
         if (sKillHandler == null) {
             sKillThread = new ServiceThread(TAG + ":kill",
@@ -7276,9 +7271,6 @@
 
     /**
      * Whitelists {@code targetUid} to temporarily bypass Power Save mode.
-     *
-     * <p>{@code callerUid} must be allowed to request such whitelist by calling
-     * {@link #addTempPowerSaveWhitelistGrantorUid(int)}.
      */
     void tempWhitelistAppForPowerSave(int callerPid, int callerUid, int targetUid, long duration) {
         if (DEBUG_WHITELISTS) {
@@ -7470,8 +7462,7 @@
         enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
                 "setProcessLimit()");
         synchronized (this) {
-            mProcessLimit = max < 0 ? ProcessList.MAX_CACHED_APPS : max;
-            mProcessLimitOverride = max;
+            mConstants.setOverrideMaxCachedProcesses(max);
         }
         trimApplications();
     }
@@ -7479,7 +7470,7 @@
     @Override
     public int getProcessLimit() {
         synchronized (this) {
-            return mProcessLimitOverride;
+            return mConstants.getOverrideMaxCachedProcesses();
         }
     }
 
@@ -7625,12 +7616,11 @@
                     final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
                     final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
                     final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
-                            ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
-                                    aspectRatio)
+                            ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio)
                             : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
                             bounds, true /* moveHomeStackToFront */);
-                    mWindowManager.setPictureInPictureActions(actions);
+                    mStackSupervisor.getStack(PINNED_STACK_ID).setPictureInPictureActions(actions);
                 };
 
                 if (isKeyguardLocked()) {
@@ -7678,12 +7668,11 @@
 
                 // Only update the saved args from the args that are set
                 r.pictureInPictureArgs.copyOnlySet(args);
-                if (r.getStack().getStackId() == PINNED_STACK_ID) {
+                final ActivityStack stack = r.getStack();
+                if (stack.getStackId() == PINNED_STACK_ID) {
                     // If the activity is already in picture-in-picture, update the pinned stack now
-                    mWindowManager.setPictureInPictureAspectRatio(
-                            r.pictureInPictureArgs.getAspectRatio());
-                    mWindowManager.setPictureInPictureActions(
-                            r.pictureInPictureArgs.getActions());
+                    stack.setPictureInPictureAspectRatio(r.pictureInPictureArgs.getAspectRatio());
+                    stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
                 }
             }
         } finally {
@@ -8036,7 +8025,7 @@
     // Unified app-op and target sdk check
     int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
         // Apps that target O+ are always subject to background check
-        if (mEnforceBackgroundCheck && packageTargetSdk >= Build.VERSION_CODES.O) {
+        if (mConstants.ENFORCE_BG_CHECK && packageTargetSdk >= Build.VERSION_CODES.O) {
             if (DEBUG_BACKGROUND_CHECK) {
                 Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
             }
@@ -9341,7 +9330,7 @@
         if (tr.mBounds != null) {
             rti.bounds = new Rect(tr.mBounds);
         }
-        rti.isDockable = tr.canGoInDockedStack();
+        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreen();
         rti.resizeMode = tr.mResizeMode;
 
         ActivityRecord base = null;
@@ -10221,7 +10210,9 @@
             synchronized (this) {
                 if (animate) {
                     if (stackId == PINNED_STACK_ID) {
-                        mWindowManager.animateResizePinnedStack(bounds, animationDuration);
+                        final ActivityStack pinnedStack =
+                                mStackSupervisor.getStack(PINNED_STACK_ID);
+                        pinnedStack.animateResizePinnedStack(bounds, animationDuration);
                     } else {
                         throw new IllegalArgumentException("Stack: " + stackId
                                 + " doesn't support animated resize.");
@@ -11559,6 +11550,7 @@
             mSystemThread.installSystemProviders(providers);
         }
 
+        mConstants.start(mContext.getContentResolver());
         mCoreSettingsObserver = new CoreSettingsObserver(this);
         mFontScaleSettingObserver = new FontScaleSettingObserver();
 
@@ -12044,14 +12036,12 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 if (mUserController.shouldConfirmCredentials(userId)) {
-                    final int currentUserId = mUserController.getCurrentUserIdLocked();
-                    if (!mKeyguardController.isKeyguardLocked()) {
-                        // If the device is not locked, we will prompt for credentials immediately.
-                        mStackSupervisor.lockAllProfileTasks(userId);
-                    } else {
+                    if (mKeyguardController.isKeyguardLocked()) {
                         // Showing launcher to avoid user entering credential twice.
+                        final int currentUserId = mUserController.getCurrentUserIdLocked();
                         startHomeActivityLocked(currentUserId, "notifyLockedProfile");
                     }
+                    mStackSupervisor.lockAllProfileTasks(userId);
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -13404,13 +13394,12 @@
             if (supportsMultiWindow || forceResizable) {
                 mSupportsMultiWindow = true;
                 mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
-                mSupportsPictureInPicture = supportsPictureInPicture || forceResizable;
             } else {
                 mSupportsMultiWindow = false;
                 mSupportsFreeformWindowManagement = false;
-                mSupportsPictureInPicture = false;
             }
             mSupportsSplitScreenMultiWindow = supportsSplitScreenMultiWindow;
+            mSupportsPictureInPicture = supportsPictureInPicture;
             mWindowManager.setForceResizableTasks(mForceResizableActivities);
             mWindowManager.setSupportsPictureInPicture(mSupportsPictureInPicture);
             // This happens before any activities are started, so we can change global configuration
@@ -14496,6 +14485,10 @@
                 synchronized (this) {
                     dumpAssociationsLocked(fd, pw, args, opti, true, dumpClient, dumpPackage);
                 }
+            } else if ("settings".equals(cmd)) {
+                synchronized (this) {
+                    mConstants.dump(pw);
+                }
             } else if ("services".equals(cmd) || "s".equals(cmd)) {
                 if (dumpClient) {
                     ActiveServices.ServiceDumper dumper;
@@ -14536,6 +14529,11 @@
         } else if (dumpClient) {
             ActiveServices.ServiceDumper sdumper;
             synchronized (this) {
+                mConstants.dump(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
                 pw.println();
                 if (dumpAll) {
@@ -14594,6 +14592,11 @@
 
         } else {
             synchronized (this) {
+                mConstants.dump(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
                 pw.println();
                 if (dumpAll) {
@@ -17756,6 +17759,7 @@
                 // Not backing this app up any more; reset its OOM adjustment
                 final ProcessRecord proc = mBackupTarget.app;
                 updateOomAdjLocked(proc);
+                proc.inFullBackup = false;
 
                 // If the app crashed during backup, 'thread' will be null here
                 if (proc.thread != null) {
@@ -19594,7 +19598,7 @@
 
     /** Helper method that requests bounds from WM and applies them to stack. */
     private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
-        final Rect newBounds = mWindowManager.getBoundsForNewConfiguration(stackId);
+        final Rect newBounds = mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration();
         mStackSupervisor.resizeStackLocked(
                 stackId, newBounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
                 false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
@@ -19615,7 +19619,8 @@
                                    && config.navigation == Configuration.NAVIGATION_NONAV);
         int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
         final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
-                && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE)));
+                && !(modeType == Configuration.UI_MODE_TYPE_WATCH && "user".equals(Build.TYPE))
+                && modeType != Configuration.UI_MODE_TYPE_TELEVISION);
         return inputMethodExists && uiModeSupportsDialogs && !inVrMode;
     }
 
@@ -21393,17 +21398,8 @@
         mNewNumServiceProcs = 0;
         mNewNumAServiceProcs = 0;
 
-        final int emptyProcessLimit;
-        final int cachedProcessLimit;
-        if (mProcessLimit <= 0) {
-            emptyProcessLimit = cachedProcessLimit = 0;
-        } else if (mProcessLimit == 1) {
-            emptyProcessLimit = 1;
-            cachedProcessLimit = 0;
-        } else {
-            emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);
-            cachedProcessLimit = mProcessLimit - emptyProcessLimit;
-        }
+        final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+        final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;
 
         // Let's determine how many processes we have running vs.
         // how many slots we have for background processes; we may want
@@ -21511,7 +21507,7 @@
                         }
                         break;
                     case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
-                        if (numEmpty > ProcessList.TRIM_EMPTY_APPS
+                        if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
                                 && app.lastActivityTime < oldTime) {
                             app.kill("empty for "
                                     + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
@@ -21564,8 +21560,8 @@
         // memory they want.
         final int numCachedAndEmpty = numCached + numEmpty;
         int memFactor;
-        if (numCached <= ProcessList.TRIM_CACHED_APPS
-                && numEmpty <= ProcessList.TRIM_EMPTY_APPS) {
+        if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
+                && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
             if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
                 memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
             } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 1a2a31b..df8dd6b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2406,6 +2406,7 @@
             pw.println("    provider [COMP_SPEC]: provider client-side state");
             pw.println("    s[ervices] [COMP_SPEC ...]: service state");
             pw.println("    as[sociations]: tracked app associations");
+            pw.println("    settings: currently applied config settings");
             pw.println("    service [COMP_SPEC]: service client-side state");
             pw.println("    package [PACKAGE_NAME]: all state related to given package");
             pw.println("    all: dump all activities");
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index e46d204..65b8554 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -3,9 +3,7 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -17,7 +15,7 @@
 import android.os.SystemClock;
 import android.util.Slog;
 
-import com.android.internal.logging.LogBuilder;
+import android.metrics.LogMaker;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
@@ -172,7 +170,7 @@
         MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
                 (int) (SystemClock.uptimeMillis() / 1000));
 
-        LogBuilder builder = new LogBuilder(MetricsEvent.APP_TRANSITION);
+        LogMaker builder = new LogMaker(MetricsEvent.APP_TRANSITION);
         builder.addTaggedData(MetricsEvent.APP_TRANSITION_COMPONENT_NAME, componentName);
         builder.addTaggedData(MetricsEvent.APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
         builder.addTaggedData(MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index a968e0b..2a849b6 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -22,6 +22,8 @@
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -37,7 +39,6 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
@@ -450,6 +451,7 @@
         }
         if (info != null) {
             pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
+            pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
         }
         pw.println(prefix + "supportsPictureInPictureWhilePausing: "
                 + supportsPictureInPictureWhilePausing);
@@ -875,24 +877,50 @@
     }
 
     boolean isResizeable() {
-        return ActivityInfo.isResizeableMode(info.resizeMode);
+        return ActivityInfo.isResizeableMode(info.resizeMode) || info.supportsPictureInPicture();
     }
 
-    boolean isResizeableOrForced() {
-        return !isHomeActivity() && (isResizeable() || service.mForceResizableActivities);
-    }
-
-    boolean isNonResizableOrForced() {
+    /**
+     * @return whether this activity is non-resizeable or forced to be resizeable
+     */
+    boolean isNonResizableOrForcedResizable() {
         return info.resizeMode != RESIZE_MODE_RESIZEABLE
-                && info.resizeMode != RESIZE_MODE_RESIZEABLE_AND_PIPABLE
                 && info.resizeMode != RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
     }
 
     /**
-     * @return whether this activity's resize mode supports PIP.
+     * @return whether this activity supports PiP multi-window and can be put in the pinned stack.
      */
     boolean supportsPictureInPicture() {
-        return !isHomeActivity() && info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+        return service.mSupportsPictureInPicture && !isHomeActivity()
+                && info.supportsPictureInPicture();
+    }
+
+    /**
+     * @return whether this activity supports split-screen multi-window and can be put in the docked
+     *         stack.
+     */
+    boolean supportsSplitScreen() {
+        // An activity can not be docked even if it is considered resizeable because it only
+        // supports picture-in-picture mode but has a non-resizeable resizeMode
+        return service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
+    }
+
+    /**
+     * @return whether this activity supports freeform multi-window and can be put in the freeform
+     *         stack.
+     */
+    boolean supportsFreeform() {
+        return service.mSupportsFreeformWindowManagement && supportsResizeableMultiWindow();
+    }
+
+    /**
+     * @return whether this activity supports non-PiP multi-window.
+     */
+    private boolean supportsResizeableMultiWindow() {
+        return service.mSupportsMultiWindow && !isHomeActivity()
+                && (ActivityInfo.isResizeableMode(info.resizeMode)
+                        || service.mForceResizableActivities);
     }
 
     /**
@@ -911,13 +939,15 @@
             case PAUSED:
                 // When pausing, only allow enter PiP if not on the lockscreen and there is not
                 // already an existing PiP activity
-                return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing;
+                return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing
+                        && checkEnterPictureInPictureOnHideAppOpsState();
             case STOPPING:
                 // When stopping in a valid state, then only allow enter PiP as in the pause state.
                 // Otherwise, fall through to throw an exception if the caller is trying to enter
                 // PiP in an invalid stopping state.
                 if (supportsPictureInPictureWhilePausing) {
-                    return !isKeyguardLocked && !hasPinnedStack;
+                    return !isKeyguardLocked && !hasPinnedStack
+                            && checkEnterPictureInPictureOnHideAppOpsState();
                 }
             default:
                 throw new IllegalStateException(caller
@@ -926,8 +956,17 @@
         }
     }
 
-    boolean canGoInDockedStack() {
-        return !isHomeActivity() && isResizeableOrForced();
+    /**
+     * @return Whether AppOps allows this package to enter picture-in-picture when it is hidden.
+     */
+    private boolean checkEnterPictureInPictureOnHideAppOpsState() {
+        try {
+            return service.getAppOpsService().checkOperation(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+                    appInfo.uid, packageName) == MODE_ALLOWED;
+        } catch (RemoteException e) {
+            // Local call
+        }
+        return false;
     }
 
     boolean isAlwaysFocusable() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 963a9dc..98b5835c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -62,7 +62,6 @@
 import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
-import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
@@ -73,7 +72,6 @@
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
 import static java.lang.Integer.MAX_VALUE;
-import static java.lang.Integer.MIN_VALUE;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -82,6 +80,7 @@
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityController;
+import android.app.RemoteAction;
 import android.app.ResultInfo;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -116,6 +115,8 @@
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
 import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+import com.android.server.wm.StackWindowController;
+import com.android.server.wm.StackWindowListener;
 import com.android.server.wm.WindowManagerService;
 
 import java.io.FileDescriptor;
@@ -130,7 +131,7 @@
 /**
  * State and management of a single stack of activities.
  */
-final class ActivityStack extends ConfigurationContainer {
+final class ActivityStack extends ConfigurationContainer implements StackWindowListener {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
     private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
@@ -242,6 +243,7 @@
 
     final ActivityManagerService mService;
     private final WindowManagerService mWindowManager;
+    private StackWindowController mWindowContainerController;
     private final RecentTasks mRecentTasks;
 
     /**
@@ -330,7 +332,7 @@
     private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
-    private final Rect tempRect2 = new Rect();
+    private final Rect mTmpRect2 = new Rect();
 
     /** Run all ActivityStacks through this */
     private final ActivityStackSupervisor mStackSupervisor;
@@ -443,7 +445,7 @@
     }
 
     ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
-            RecentTasks recentTasks) {
+            RecentTasks recentTasks, boolean onTop) {
         mActivityContainer = activityContainer;
         mStackSupervisor = activityContainer.getOuter();
         mService = mStackSupervisor.mService;
@@ -454,13 +456,25 @@
         mRecentTasks = recentTasks;
         mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
                 ? new LaunchingTaskPositioner() : null;
+        final ActivityStackSupervisor.ActivityDisplay display = mActivityContainer.mActivityDisplay;
+        mTmpRect2.setEmpty();
+        mWindowContainerController = new StackWindowController(mStackId, this,
+                display.mDisplayId, onTop, mTmpRect2);
+        activityContainer.mStack = this;
+        mStackSupervisor.mActivityContainers.put(mStackId, activityContainer);
+        postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
+    }
+
+    StackWindowController getWindowContainerController() {
+        return mWindowContainerController;
     }
 
     /** Adds the stack to specified display and calls WindowManager to do the same. */
-    void addToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
-        final Rect bounds = mWindowManager.addStackToDisplay(mStackId, activityDisplay.mDisplayId,
-                onTop);
-        postAddToDisplay(activityDisplay, bounds);
+    void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+        removeFromDisplay();
+        mTmpRect2.setEmpty();
+        mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2);
+        postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
     }
 
     /**
@@ -469,10 +483,10 @@
      * @param bounds Updated bounds.
      */
     private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
-            Rect bounds) {
+            Rect bounds, boolean onTop) {
         mDisplayId = activityDisplay.mDisplayId;
         mStacks = activityDisplay.mStacks;
-        mBounds = bounds;
+        mBounds = bounds != null ? new Rect(bounds) : null;
         mFullscreen = mBounds == null;
         if (mTaskPositioner != null) {
             mTaskPositioner.setDisplay(activityDisplay.mDisplay);
@@ -480,6 +494,7 @@
         }
         onParentChanged();
 
+        activityDisplay.attachStack(this, onTop);
         if (mStackId == DOCKED_STACK_ID) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
@@ -489,16 +504,6 @@
     }
 
     /**
-     * Moves the stack to specified display.
-     * @param activityDisplay Target display to move the stack to.
-     */
-    void moveToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay) {
-        removeFromDisplay();
-        final Rect bounds = mWindowManager.moveStackToDisplay(mStackId, activityDisplay.mDisplayId);
-        postAddToDisplay(activityDisplay, bounds);
-    }
-
-    /**
      * Updates the inner state of the stack to remove it from its current parent, so it can be
      * either destroyed completely or re-parented.
      */
@@ -520,7 +525,8 @@
     void remove() {
         removeFromDisplay();
         mStackSupervisor.deleteActivityContainerRecord(mStackId);
-        mWindowManager.removeStack(mStackId);
+        mWindowContainerController.removeContainer();
+        mWindowContainerController = null;
         onParentChanged();
     }
 
@@ -528,6 +534,43 @@
         mActivityContainer.mActivityDisplay.mDisplay.getSize(out);
     }
 
+    void animateResizePinnedStack(Rect bounds, int animationDuration) {
+        mWindowContainerController.animateResizePinnedStack(bounds, animationDuration);
+    }
+
+    void setPictureInPictureAspectRatio(float aspectRatio) {
+        mWindowContainerController.setPictureInPictureAspectRatio(aspectRatio);
+    }
+
+    void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
+        mWindowContainerController.getStackDockedModeBounds(outBounds, ignoreVisibility);
+    }
+
+    void prepareFreezingTaskBounds() {
+        mWindowContainerController.prepareFreezingTaskBounds();
+    }
+
+    void setPictureInPictureActions(List<RemoteAction> actions) {
+        mWindowContainerController.setPictureInPictureActions(actions);
+    }
+
+    void getWindowContainerBounds(Rect outBounds) {
+        if (mWindowContainerController != null) {
+            mWindowContainerController.getBounds(outBounds);
+            return;
+        }
+        outBounds.setEmpty();
+    }
+
+    Rect getBoundsForNewConfiguration() {
+        return mWindowContainerController.getBoundsForNewConfiguration();
+    }
+
+    void positionChildWindowContainerAtTop(TaskRecord child) {
+        mWindowContainerController.positionChildAtTop(child.getWindowContainerController(),
+                true /* includingParents */);
+    }
+
     /**
      * Defers updating the bounds of the stack. If the stack was resized/repositioned while
      * deferring, the bounds will update in {@link #continueUpdateBounds()}.
@@ -548,8 +591,7 @@
         final boolean wasDeferred = mUpdateBoundsDeferred;
         mUpdateBoundsDeferred = false;
         if (wasDeferred && mUpdateBoundsDeferredCalled) {
-            mStackSupervisor.resizeStackUncheckedLocked(this,
-                    mDeferredBounds.isEmpty() ? null : mDeferredBounds,
+            resize(mDeferredBounds.isEmpty() ? null : mDeferredBounds,
                     mDeferredTaskBounds.isEmpty() ? null : mDeferredTaskBounds,
                     mDeferredTaskInsetBounds.isEmpty() ? null : mDeferredTaskInsetBounds);
         }
@@ -767,7 +809,8 @@
 
         task = topTask();
         if (task != null) {
-            task.moveWindowContainerToTop(true /* includingParents */);
+            mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+                    true /* includingParents */);
         }
     }
 
@@ -786,7 +829,7 @@
             mTaskHistory.remove(task);
             mTaskHistory.add(0, task);
             updateTaskMovement(task, false);
-            task.moveWindowContainerToBottom();
+            mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
         }
     }
 
@@ -1545,7 +1588,7 @@
             // home task even though it's not resizable.
             final ActivityRecord r = focusedStack.topRunningActivityLocked();
             final TaskRecord task = r != null ? r.task : null;
-            return task == null || task.canGoInDockedStack() || task.isHomeTask() ? STACK_VISIBLE
+            return task == null || task.supportsSplitScreen() || task.isHomeTask() ? STACK_VISIBLE
                     : STACK_INVISIBLE;
         }
 
@@ -2580,7 +2623,8 @@
         position = getAdjustedPositionForTask(task, position, null /* starting */);
         mTaskHistory.remove(task);
         mTaskHistory.add(position, task);
-        task.positionWindowContainerAt(position);
+        mWindowContainerController.positionChildAt(task.getWindowContainerController(), position,
+                task.mBounds, task.getOverrideConfiguration());
         updateTaskMovement(task, true);
     }
 
@@ -2592,7 +2636,8 @@
         final int position = getAdjustedPositionForTask(task, mTaskHistory.size(), starting);
         mTaskHistory.add(position, task);
         updateTaskMovement(task, true);
-        task.moveWindowContainerToTop(true /* includingParents */);
+        mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+                true /* includingParents */);
     }
 
     private void updateTaskReturnToForTopInsertion(TaskRecord task) {
@@ -2620,7 +2665,7 @@
                 // This also makes sure that non-home activities are visible under a transparent
                 // non-home activity.
                 task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
-            } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask != task)) {
+            } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
                 // If it's a last task over home - we default to keep its return to type not to
                 // make underlying task focused when this one will be finished.
                 int returnToType = isLastTaskOverHome
@@ -2870,7 +2915,8 @@
                     targetTask.addActivityAtBottom(p);
                 }
 
-                targetTask.moveWindowContainerToBottom();
+                mWindowContainerController.positionChildAtBottom(
+                        targetTask.getWindowContainerController());
                 replyChainEnd = -1;
             } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
                 // If the activity should just be removed -- either
@@ -3006,7 +3052,8 @@
                         if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p
                                 + " from " + srcPos + " in to resetting task " + task);
                     }
-                    task.moveWindowContainerToTop(true /* includingParents */);
+                    mWindowContainerController.positionChildAtTop(
+                            task.getWindowContainerController(), true /* includingParents */);
 
                     // Now we've moved it in to place...  but what if this is
                     // a singleTop activity and we have put it on top of another
@@ -4375,7 +4422,7 @@
         }
 
         mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
-        tr.moveWindowContainerToBottom();
+        mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
 
         final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
         if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) {
@@ -4438,11 +4485,24 @@
         }
     }
 
-    /** Update override configurations of all tasks in the stack. */
-    void updateOverrideConfiguration(Rect stackBounds, Rect tempTaskBounds,
-            Rect tempTaskInsetBounds) {
+    // TODO: Figure-out a way to consolidate with resize() method below.
+    @Override
+    public void requestResize(Rect bounds) {
+        mService.resizeStack(mStackId, bounds, true /* allowResizeInDockedMode */,
+                false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
+    }
 
-        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : stackBounds;
+    // TODO: Can only be called from special methods in ActivityStackSupervisor.
+    // Need to consolidate those calls points into this resize method so anyone can call directly.
+    void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
+        bounds = TaskRecord.validateBounds(bounds);
+
+        if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
+            return;
+        }
+
+        // Update override configurations of all tasks in the stack.
+        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
         final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
 
         mTmpBounds.clear();
@@ -4456,9 +4516,9 @@
                     // For freeform stack we don't adjust the size of the tasks to match that
                     // of the stack, but we do try to make sure the tasks are still contained
                     // with the bounds of the stack.
-                    tempRect2.set(task.mBounds);
-                    fitWithinBounds(tempRect2, stackBounds);
-                    task.updateOverrideConfiguration(tempRect2);
+                    mTmpRect2.set(task.mBounds);
+                    fitWithinBounds(mTmpRect2, bounds);
+                    task.updateOverrideConfiguration(mTmpRect2);
                 } else {
                     task.updateOverrideConfiguration(taskBounds, insetBounds);
                 }
@@ -4471,11 +4531,9 @@
             }
         }
 
-        // We might trigger a configuration change. Save the current task bounds for freezing.
-        mWindowManager.prepareFreezingTaskBounds(mStackId);
-        mFullscreen = mWindowManager.resizeStack(mStackId, stackBounds, mTmpConfigs, mTmpBounds,
+        mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds,
                 mTmpInsetBounds);
-        setBounds(stackBounds);
+        setBounds(bounds);
     }
 
 
@@ -4665,7 +4723,7 @@
             }
             ci.numActivities = numActivities;
             ci.numRunning = numRunning;
-            ci.isDockable = task.canGoInDockedStack();
+            ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen();
             ci.resizeMode = task.mResizeMode;
             list.add(ci);
         }
@@ -4899,7 +4957,8 @@
         addTask(task, toTop ? MAX_VALUE : 0, reason);
         if (toTop) {
             // TODO: figure-out a way to remove this call.
-            task.moveWindowContainerToTop(true /* includingParents */);
+            mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+                    true /* includingParents */);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 2c1c3a1..29032f8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -90,7 +90,6 @@
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
 import static java.lang.Integer.MAX_VALUE;
-import static java.lang.Integer.MIN_VALUE;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -377,7 +376,7 @@
 
     // TODO: Add listener for removal of references.
     /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
-    private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
+    SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
 
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -2199,7 +2198,7 @@
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
         mWindowManager.deferSurfaceLayout();
         try {
-            resizeStackUncheckedLocked(stack, bounds, tempTaskBounds, tempTaskInsetBounds);
+            stack.resize(bounds, tempTaskBounds, tempTaskInsetBounds);
             if (!deferResume) {
                 stack.ensureVisibleActivitiesConfigurationLocked(
                         stack.topRunningActivityLocked(), preserveWindows);
@@ -2237,17 +2236,6 @@
         mResizingTasksDuringAnimation.clear();
     }
 
-    void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
-            Rect tempTaskInsetBounds) {
-        bounds = TaskRecord.validateBounds(bounds);
-
-        if (!stack.updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
-            return;
-        }
-
-        stack.updateOverrideConfiguration(bounds, tempTaskBounds, tempTaskInsetBounds);
-    }
-
     void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
         final ActivityStack stack = getStack(fromStackId);
         if (stack == null) {
@@ -2341,8 +2329,7 @@
             // Don't allow re-entry while resizing. E.g. due to docked stack detaching.
             mAllowDockedStackResize = false;
             ActivityRecord r = stack.topRunningActivityLocked();
-            resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
-                    tempDockedTaskInsetBounds);
+            stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds);
 
             // TODO: Checking for isAttached might not be needed as if the user passes in null
             // dockedBounds then they want the docked stack to be dismissed.
@@ -2359,10 +2346,10 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
-                mWindowManager.getStackDockedModeBounds(
-                        HOME_STACK_ID, tempRect, true /* ignoreVisibility */);
                 for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
+                    final ActivityStack current = getStack(i);
+                    if (current != null && StackId.isResizeableByDockedStack(i)) {
+                        current.getStackDockedModeBounds(tempRect, true /* ignoreVisibility */);
                         resizeStackLocked(i, tempRect, tempOtherTaskBounds,
                                 tempOtherTaskInsetBounds, preserveWindows,
                                 true /* allowResizeInDockedMode */, deferResume);
@@ -2395,8 +2382,7 @@
         mWindowManager.deferSurfaceLayout();
         try {
             ActivityRecord r = stack.topRunningActivityLocked();
-            resizeStackUncheckedLocked(stack, pinnedBounds, tempPinnedTaskBounds,
-                    null);
+            stack.resize(pinnedBounds, tempPinnedTaskBounds, null);
             stack.ensureVisibleActivitiesConfigurationLocked(r, false);
         } finally {
             mWindowManager.continueSurfaceLayout();
@@ -2405,14 +2391,13 @@
     }
 
     ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
-        ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+        final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
         if (activityDisplay == null) {
             return null;
         }
 
-        ActivityContainer activityContainer = new ActivityContainer(stackId);
-        mActivityContainers.put(stackId, activityContainer);
-        activityContainer.addToDisplayLocked(activityDisplay, onTop);
+        final ActivityContainer activityContainer =
+                new ActivityContainer(stackId, activityDisplay, onTop);
         return activityContainer.mStack;
     }
 
@@ -2576,7 +2561,7 @@
             // This means that tasks that were on external displays will be restored on the
             // primary display.
             stackId = task.getLaunchStackId();
-        } else if (stackId == DOCKED_STACK_ID && !task.canGoInDockedStack()) {
+        } else if (stackId == DOCKED_STACK_ID && !task.supportsSplitScreen()) {
             // Preferred stack is the docked stack, but the task can't go in the docked stack.
             // Put it in the fullscreen stack.
             stackId = FULLSCREEN_WORKSPACE_STACK_ID;
@@ -2771,7 +2756,8 @@
             }
 
             // We might trigger a configuration change. Save the current task bounds for freezing.
-            mWindowManager.prepareFreezingTaskBounds(stack.mStackId);
+            // TODO: Should this call be moved inside the resize method in WM?
+            stack.prepareFreezingTaskBounds();
 
             // Make sure the task has the appropriate bounds/size for the stack it is in.
             if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
@@ -2840,7 +2826,11 @@
 
     void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
             boolean moveHomeStackToFront) {
+
         mWindowManager.deferSurfaceLayout();
+        // Need to make sure the pinned stack exist so we can resize it below...
+        final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+
         try {
             final TaskRecord task = r.task;
 
@@ -2850,9 +2840,6 @@
                 requestVisibleBehindLocked(r, false);
             }
 
-            // Need to make sure the pinned stack exist so we can resize it below...
-            final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-
             // Resize the pinned stack to match the current size of the task the activity we are
             // going to be moving is currently contained in. We do this to have the right starting
             // animation bounds for the pinned stack to the desired bounds the caller wants.
@@ -2892,7 +2879,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        mWindowManager.animateResizePinnedStack(bounds, -1);
+        stack.animateResizePinnedStack(bounds, -1);
         mService.mTaskChangeNotificationController.notifyActivityPinned();
     }
 
@@ -3357,7 +3344,7 @@
                 stack.switchUserLocked(userId);
                 TaskRecord task = stack.topTask();
                 if (task != null) {
-                    task.moveWindowContainerToTop(true /* includingParents */);
+                    stack.positionChildWindowContainerAtTop(task);
                 }
             }
         }
@@ -3795,7 +3782,7 @@
     private StackInfo getStackInfoLocked(ActivityStack stack) {
         final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
         StackInfo info = new StackInfo();
-        mWindowManager.getStackBounds(stack.mStackId, info.bounds);
+        stack.getWindowContainerBounds(info.bounds);
         info.displayId = DEFAULT_DISPLAY;
         info.stackId = stack.mStackId;
         info.userId = stack.mCurrentUser;
@@ -3895,14 +3882,14 @@
         }
 
         final ActivityRecord topActivity = task.getTopActivity();
-        if (!task.canGoInDockedStack() || forceNonResizable) {
+        if (!task.supportsSplitScreen() || forceNonResizable) {
             // Display a warning toast that we tried to put a non-dockable task in the docked stack.
             mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
 
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
             moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID);
-        } else if (topActivity != null && topActivity.isNonResizableOrForced()
+        } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
                 && !topActivity.noDisplay) {
             String packageName = topActivity.appInfo.packageName;
             mService.mTaskChangeNotificationController.notifyActivityForcedResizable(
@@ -4321,7 +4308,7 @@
                 FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
         final int mStackId;
         IActivityContainerCallback mCallback = null;
-        final ActivityStack mStack;
+        ActivityStack mStack;
         ActivityRecord mParentActivity = null;
         String mIdString;
 
@@ -4335,10 +4322,11 @@
         final static int CONTAINER_STATE_FINISHING = 2;
         int mContainerState = CONTAINER_STATE_HAS_SURFACE;
 
-        ActivityContainer(int stackId) {
+        ActivityContainer(int stackId, ActivityDisplay activityDisplay, boolean onTop) {
             synchronized (mService) {
                 mStackId = stackId;
-                mStack = new ActivityStack(this, mRecentTasks);
+                mActivityDisplay = activityDisplay;
+                new ActivityStack(this, mRecentTasks, onTop);
                 mIdString = "ActivtyContainer{" + mStackId + "}";
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "Creating " + this);
             }
@@ -4346,21 +4334,18 @@
 
         /**
          * Adds the stack to specified display. Also calls WindowManager to do the same from
-         * {@link ActivityStack#addToDisplay(ActivityDisplay, boolean)}.
+         * {@link ActivityStack#reparent(ActivityDisplay, boolean)}.
          * @param activityDisplay The display to add the stack to.
-         * @param onTop If true the stack will be place at the top of the display, else at the
-         *              bottom.
          */
-        void addToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
+        void addToDisplayLocked(ActivityDisplay activityDisplay) {
             if (DEBUG_STACK) Slog.d(TAG_STACK, "addToDisplayLocked: " + this
-                    + " to display=" + activityDisplay + " onTop=" + onTop);
+                    + " to display=" + activityDisplay);
             if (mActivityDisplay != null) {
                 throw new IllegalStateException("ActivityContainer is already attached, " +
                         "displayId=" + mActivityDisplay.mDisplayId);
             }
             mActivityDisplay = activityDisplay;
-            mStack.addToDisplay(activityDisplay, onTop);
-            activityDisplay.attachActivities(mStack, onTop);
+            mStack.reparent(activityDisplay, true /* onTop */);
         }
 
         @Override
@@ -4370,7 +4355,7 @@
                 if (activityDisplay == null) {
                     return;
                 }
-                addToDisplayLocked(activityDisplay, true /* onTop */);
+                addToDisplayLocked(activityDisplay);
             }
         }
 
@@ -4447,7 +4432,7 @@
             if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
                     + " current displayId=" + mActivityDisplay.mDisplayId);
 
-            mActivityDisplay.detachActivitiesLocked(mStack);
+            mActivityDisplay.detachStack(mStack);
             mActivityDisplay = null;
         }
 
@@ -4463,8 +4448,7 @@
             removeFromDisplayLocked();
 
             mActivityDisplay = activityDisplay;
-            mStack.moveToDisplay(activityDisplay);
-            activityDisplay.attachActivities(mStack, ON_TOP);
+            mStack.reparent(activityDisplay, ON_TOP);
         }
 
         @Override
@@ -4554,7 +4538,8 @@
         boolean mDrawn = false;
 
         VirtualActivityContainer(ActivityRecord parent, IActivityContainerCallback callback) {
-            super(getNextStackId());
+            super(getNextStackId(), parent.getStack().mActivityContainer.mActivityDisplay,
+                    true /* onTop */);
             mParentActivity = parent;
             mCallback = callback;
             mContainerState = CONTAINER_STATE_NO_SURFACE;
@@ -4586,7 +4571,7 @@
                         new VirtualActivityDisplay(width, height, density);
                 mActivityDisplay = virtualActivityDisplay;
                 mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
-                addToDisplayLocked(virtualActivityDisplay, true /* onTop */);
+                addToDisplayLocked(virtualActivityDisplay);
             }
 
             if (mSurface != null) {
@@ -4675,10 +4660,9 @@
             mDisplayId = display.getDisplayId();
         }
 
-        void attachActivities(ActivityStack stack, boolean onTop) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK,
-                    "attachActivities: attaching " + stack + " to displayId=" + mDisplayId
-                    + " onTop=" + onTop);
+        void attachStack(ActivityStack stack, boolean onTop) {
+            if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack
+                    + " to displayId=" + mDisplayId + " onTop=" + onTop);
             if (onTop) {
                 mStacks.add(stack);
             } else {
@@ -4686,8 +4670,8 @@
             }
         }
 
-        void detachActivitiesLocked(ActivityStack stack) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "detachActivitiesLocked: detaching " + stack
+        void detachStack(ActivityStack stack) {
+            if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack
                     + " from displayId=" + mDisplayId);
             mStacks.remove(stack);
         }
@@ -4765,8 +4749,8 @@
         }
 
         @Override
-        void detachActivitiesLocked(ActivityStack stack) {
-            super.detachActivitiesLocked(stack);
+        void detachStack(ActivityStack stack) {
+            super.detachStack(stack);
             if (mVirtualDisplay != null) {
                 mVirtualDisplay.release();
                 mVirtualDisplay = null;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 26d2ee2..b913a23 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -460,6 +460,7 @@
             final String splitName = rInfo.ephemeralResponse.splitName;
             final boolean needsPhaseTwo = rInfo.ephemeralResponse.needsPhase2;
             final String token = rInfo.ephemeralResponse.token;
+            final int versionCode = rInfo.ephemeralResponse.resolveInfo.getVersionCode();
             if (needsPhaseTwo) {
                 // request phase two resolution
                 mService.getPackageManagerInternalLocked().requestEphemeralResolutionPhaseTwo(
@@ -467,8 +468,8 @@
                         callingPackage, userId);
             }
             intent = EphemeralResolver.buildEphemeralInstallerIntent(intent, ephemeralIntent,
-                    callingPackage, resolvedType, userId, packageName, splitName, token,
-                    needsPhaseTwo);
+                    callingPackage, resolvedType, userId, packageName, splitName, versionCode,
+                    token, needsPhaseTwo);
             resolvedType = null;
             callingUid = realCallingUid;
             callingPid = realCallingPid;
@@ -1834,7 +1835,7 @@
                 mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
                 mIntent, null, null, true, mStartActivity.mActivityType);
         mStartActivity.setTask(task, null);
-        mStartActivity.task.moveWindowContainerToTop(true /* includingParents */);
+        mStartActivity.task.getStack().positionChildWindowContainerAtTop(mStartActivity.task);
         if (DEBUG_TASKS) Slog.v(TAG_TASKS,
                 "Starting new activity " + mStartActivity + " in new guessed " + mStartActivity.task);
     }
@@ -1966,10 +1967,10 @@
                 canUseFocusedStack = true;
                 break;
             case DOCKED_STACK_ID:
-                canUseFocusedStack = r.canGoInDockedStack();
+                canUseFocusedStack = r.supportsSplitScreen();
                 break;
             case FREEFORM_WORKSPACE_STACK_ID:
-                canUseFocusedStack = r.isResizeableOrForced();
+                canUseFocusedStack = r.supportsFreeform();
                 break;
             default:
                 canUseFocusedStack = isDynamicStack(focusedStackId)
@@ -2056,29 +2057,24 @@
     }
 
     boolean isValidLaunchStackId(int stackId, ActivityRecord r) {
-        if (stackId == INVALID_STACK_ID || stackId == HOME_STACK_ID) {
-            return false;
+        switch (stackId) {
+            case INVALID_STACK_ID:
+            case HOME_STACK_ID:
+                return false;
+            case FULLSCREEN_WORKSPACE_STACK_ID:
+                return true;
+            case FREEFORM_WORKSPACE_STACK_ID:
+                return r.supportsFreeform();
+            case DOCKED_STACK_ID:
+                return r.supportsSplitScreen();
+            case PINNED_STACK_ID:
+                return r.supportsPictureInPicture();
+            case RECENTS_STACK_ID:
+                return r.isRecentsActivity();
+            default:
+                Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
+                return false;
         }
-
-        if (stackId != FULLSCREEN_WORKSPACE_STACK_ID
-                && (!mService.mSupportsMultiWindow || !r.isResizeableOrForced())) {
-            return false;
-        }
-
-        if (stackId == DOCKED_STACK_ID && r.canGoInDockedStack()) {
-            return true;
-        }
-
-        if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
-            return false;
-        }
-
-        final boolean supportsPip = mService.mSupportsPictureInPicture
-                && (r.supportsPictureInPicture() || mService.mForceResizableActivities);
-        if (stackId == PINNED_STACK_ID && !supportsPip) {
-            return false;
-        }
-        return true;
     }
 
     Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1322ecf..40effff 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -138,31 +138,9 @@
     // without empty apps being able to push them out of memory.
     static final int MIN_CACHED_APPS = 2;
 
-    // The maximum number of cached processes we will keep around before killing them.
-    // NOTE: this constant is *only* a control to not let us go too crazy with
-    // keeping around processes on devices with large amounts of RAM.  For devices that
-    // are tighter on RAM, the out of memory killer is responsible for killing background
-    // processes as RAM is needed, and we should *never* be relying on this limit to
-    // kill them.  Also note that this limit only applies to cached background processes;
-    // we have no limit on the number of service, visible, foreground, or other such
-    // processes and the number of those processes does not count against the cached
-    // process limit.
-    static final int MAX_CACHED_APPS = 32;
-
     // We allow empty processes to stick around for at most 30 minutes.
     static final long MAX_EMPTY_TIME = 30*60*1000;
 
-    // The maximum number of empty app processes we will let sit around.
-    private static final int MAX_EMPTY_APPS = computeEmptyProcessLimit(MAX_CACHED_APPS);
-
-    // The number of empty apps at which we don't consider it necessary to do
-    // memory trimming.
-    static final int TRIM_EMPTY_APPS = MAX_EMPTY_APPS/2;
-
-    // The number of cached at which we don't consider it necessary to do
-    // memory trimming.
-    static final int TRIM_CACHED_APPS = (MAX_CACHED_APPS-MAX_EMPTY_APPS)/3;
-
     // Threshold of number of cached+empty where we consider memory critical.
     static final int TRIM_CRITICAL_THRESHOLD = 3;
 
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 7b4d289..f12d7b7 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -85,6 +85,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
@@ -141,6 +142,7 @@
     private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
     private static final String ATTR_CALLING_UID = "calling_uid";
     private static final String ATTR_CALLING_PACKAGE = "calling_package";
+    private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture";
     private static final String ATTR_RESIZE_MODE = "resize_mode";
     private static final String ATTR_PRIVILEGED = "privileged";
     private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
@@ -188,6 +190,9 @@
 
     int mResizeMode;        // The resize mode of this task and its activities.
                             // Based on the {@link ActivityInfo#resizeMode} of the root activity.
+    boolean mSupportsPictureInPicture;  // Whether or not this task and its activities support PiP.
+            // Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag of the root
+            // activity.
     boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
                                      // changes on a temporary basis.
     private int mLockTaskMode;  // Which tasklock mode to launch this task in. One of
@@ -356,8 +361,9 @@
             boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
             TaskThumbnailInfo lastThumbnailInfo, int taskAffiliation, int prevTaskId,
             int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
-            int resizeMode, boolean privileged, boolean _realActivitySuspended,
-            boolean userSetupComplete, int minWidth, int minHeight) {
+            int resizeMode, boolean supportsPictureInPicture, boolean privileged,
+            boolean _realActivitySuspended, boolean userSetupComplete, int minWidth,
+            int minHeight) {
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
@@ -396,6 +402,7 @@
         mCallingUid = callingUid;
         mCallingPackage = callingPackage;
         mResizeMode = resizeMode;
+        mSupportsPictureInPicture = supportsPictureInPicture;
         mPrivileged = privileged;
         mMinWidth = minWidth;
         mMinHeight = minHeight;
@@ -414,9 +421,10 @@
 
         final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
         final Configuration overrideConfig = getOverrideConfiguration();
-        mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
-                userId, bounds, overrideConfig, mResizeMode, isHomeTask(), isOnTopLauncher(), onTop,
-                showForAllUsers, lastTaskDescription);
+        mWindowContainerController = new TaskWindowContainerController(taskId, this,
+                getStack().getWindowContainerController(), userId, bounds, overrideConfig,
+                mResizeMode, mSupportsPictureInPicture, isHomeTask(), isOnTopLauncher(),
+                onTop, showForAllUsers, lastTaskDescription);
     }
 
     void removeWindowContainer() {
@@ -450,6 +458,12 @@
         mWindowContainerController.setTaskDockedResizing(resizing);
     }
 
+    // TODO: Consolidate this with the resize() method below.
+    @Override
+    public void requestResize(Rect bounds, int resizeMode) {
+        mService.resizeTask(taskId, bounds, resizeMode);
+    }
+
     boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
         if (!isResizeable()) {
             Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
@@ -520,25 +534,6 @@
                 false /* forced */);
     }
 
-    // TODO: Remove once we have a stack controller.
-    void positionWindowContainerAt(int position) {
-        mWindowContainerController.positionAt(position, mBounds, getOverrideConfiguration());
-    }
-
-    // TODO: Replace with moveChildToTop?
-    void moveWindowContainerToTop(boolean includingParents) {
-        if (mWindowContainerController != null) {
-            mWindowContainerController.moveToTop(includingParents);
-        }
-    }
-
-    // TODO: Replace with moveChildToBottom?
-    void moveWindowContainerToBottom() {
-        if (mWindowContainerController != null) {
-            mWindowContainerController.moveToBottom();
-        }
-    }
-
     void getWindowContainerBounds(Rect bounds) {
         mWindowContainerController.getBounds(bounds);
     }
@@ -556,7 +551,7 @@
 
             // Must reparent first in window manager to avoid a situation where AM can delete the
             // we are coming from in WM before we reparent because it became empty.
-            mWindowContainerController.reparent(stackId, position);
+            mWindowContainerController.reparent(newStack.getWindowContainerController(), position);
 
             final ActivityStack prevStack = mStack;
             prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
@@ -691,6 +686,7 @@
             autoRemoveRecents = false;
         }
         mResizeMode = info.resizeMode;
+        mSupportsPictureInPicture = info.supportsPictureInPicture();
         mIsOnTopLauncher = (info.flags & FLAG_ON_TOP_LAUNCHER) != 0;
         mLockTaskMode = info.lockTaskLaunchMode;
         mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
@@ -1301,9 +1297,21 @@
         return mTaskToReturnTo == HOME_ACTIVITY_TYPE;
     }
 
+    private boolean isResizeable(boolean checkSupportsPip) {
+        return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
+                || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable;
+    }
+
     boolean isResizeable() {
-        return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode))
-                && !mTemporarilyUnresizable;
+        return isResizeable(true /* checkSupportsPip */);
+    }
+
+    boolean supportsSplitScreen() {
+        // A task can not be docked even if it is considered resizeable because it only supports
+        // picture-in-picture mode but has a non-resizeable resizeMode
+        return mService.mSupportsSplitScreenMultiWindow
+                && isResizeable(false /* checkSupportsPip */)
+                && !ActivityInfo.isPreserveOrientationMode(mResizeMode);
     }
 
     /**
@@ -1329,11 +1337,6 @@
         return isHomeTask() && mIsOnTopLauncher;
     }
 
-    boolean canGoInDockedStack() {
-        return isResizeable() && mService.mSupportsSplitScreenMultiWindow &&
-                !ActivityInfo.isPreserveOrientationMode(mResizeMode);
-    }
-
     /**
      * Find the activity in the history stack within the given task.  Returns
      * the index within the history at which it's found, or < 0 if not found.
@@ -1482,6 +1485,8 @@
         out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid));
         out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage);
         out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode));
+        out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE,
+                String.valueOf(mSupportsPictureInPicture));
         out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));
         if (mLastNonFullscreenBounds != null) {
             out.attribute(
@@ -1552,6 +1557,7 @@
         int callingUid = -1;
         String callingPackage = "";
         int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+        boolean supportsPictureInPicture = false;
         boolean privileged = false;
         Rect bounds = null;
         int minWidth = INVALID_MIN_SIZE;
@@ -1618,6 +1624,8 @@
                 callingPackage = attrValue;
             } else if (ATTR_RESIZE_MODE.equals(attrName)) {
                 resizeMode = Integer.parseInt(attrValue);
+            } else if (ATTR_SUPPORTS_PICTURE_IN_PICTURE.equals(attrName)) {
+                supportsPictureInPicture = Boolean.parseBoolean(attrValue);
             } else if (ATTR_PRIVILEGED.equals(attrName)) {
                 privileged = Boolean.parseBoolean(attrValue);
             } else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
@@ -1690,6 +1698,15 @@
             if (taskType == HOME_ACTIVITY_TYPE && resizeMode == RESIZE_MODE_RESIZEABLE) {
                 resizeMode = RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
             }
+        } else {
+            // This activity has previously marked itself explicitly as both resizeable and
+            // supporting picture-in-picture.  Since there is no longer a requirement for
+            // picture-in-picture activities to be resizeable, we can mark this simply as
+            // resizeable and supporting picture-in-picture separately.
+            if (resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED) {
+                resizeMode = RESIZE_MODE_RESIZEABLE;
+                supportsPictureInPicture = true;
+            }
         }
 
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
@@ -1697,8 +1714,9 @@
                 autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
                 activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
                 taskDescription, thumbnailInfo, taskAffiliation, prevTaskId, nextTaskId,
-                taskAffiliationColor, callingUid, callingPackage, resizeMode, privileged,
-                realActivitySuspended, userSetupComplete, minWidth, minHeight);
+                taskAffiliationColor, callingUid, callingPackage, resizeMode,
+                supportsPictureInPicture, privileged, realActivitySuspended, userSetupComplete,
+                minWidth, minHeight);
         task.updateOverrideConfiguration(bounds);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
@@ -2084,6 +2102,7 @@
         pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
         pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
                 pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
+                pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
                 pw.print(" isResizeable=" + isResizeable());
                 pw.print(" firstActiveTime=" + lastActiveTime);
                 pw.print(" lastActiveTime=" + lastActiveTime);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index df5f01d..31ef94f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5932,7 +5932,7 @@
      *   the whether any exposes the FLAG_ENABLE_ACCESSIBILITY_VOLUME flag
      * - set to false to listen to when accessibility services are started (e.g. "TalkBack started")
      */
-    private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = true;
+    private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = false;
 
     private void initA11yMonitoring() {
         final AccessibilityManager accessibilityManager =
@@ -6502,6 +6502,35 @@
         return mRecordMonitor.getActiveRecordingConfigurations();
     }
 
+    public void disableRingtoneSync() {
+        final int callingUserId = UserHandle.getCallingUserId();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            UserManager userManager = UserManager.get(mContext);
+
+            // Disable the sync setting
+            Settings.Secure.putIntForUser(mContentResolver,
+                    Settings.Secure.SYNC_PARENT_SOUNDS, 0 /* false */, callingUserId);
+
+            UserInfo parentInfo = userManager.getProfileParent(callingUserId);
+            if (parentInfo != null && parentInfo.id != callingUserId) {
+                // This is a managed profile, so we clone the ringtones from the parent profile
+                cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.RINGTONE);
+                cloneRingtoneSetting(callingUserId, parentInfo.id,
+                        Settings.System.NOTIFICATION_SOUND);
+                cloneRingtoneSetting(callingUserId, parentInfo.id, Settings.System.ALARM_ALERT);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void cloneRingtoneSetting(int userId, int parentId, String ringtoneSetting) {
+        String parentSetting = Settings.System.getStringForUser(mContentResolver, ringtoneSetting,
+                parentId);
+        Settings.System.putStringForUser(mContentResolver, ringtoneSetting, parentSetting, userId);
+    }
+
     //======================
     // Audio playback notification
     //======================
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 8d4f0a9..5fed397 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -40,6 +40,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
@@ -50,18 +51,106 @@
 import java.util.HashSet;
 import java.util.List;
 
+import java.lang.Thread;
+import java.lang.Runnable;
+import java.lang.InterruptedException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+// The following class is Android Emulator specific. It is used to read and
+// write contents of the host system's clipboard.
+class HostClipboardMonitor implements Runnable {
+    public interface HostClipboardCallback {
+        void onHostClipboardUpdated(String contents);
+    }
+
+    private RandomAccessFile mPipe = null;
+    private HostClipboardCallback mHostClipboardCallback;
+    private static final String PIPE_NAME = "pipe:clipboard";
+    private static final String PIPE_DEVICE = "/dev/qemu_pipe";
+
+    private void openPipe() {
+        try {
+            // String.getBytes doesn't include the null terminator,
+            // but the QEMU pipe device requires the pipe service name
+            // to be null-terminated.
+            byte[] b = new byte[PIPE_NAME.length() + 1];
+            b[PIPE_NAME.length()] = 0;
+            System.arraycopy(
+                PIPE_NAME.getBytes(),
+                0,
+                b,
+                0,
+                PIPE_NAME.length());
+            mPipe = new RandomAccessFile(PIPE_DEVICE, "rw");
+            mPipe.write(b);
+        } catch (IOException e) {
+            try {
+                if (mPipe != null) mPipe.close();
+            } catch (IOException ee) {}
+            mPipe = null;
+        }
+    }
+
+    public HostClipboardMonitor(HostClipboardCallback cb) {
+        mHostClipboardCallback = cb;
+    }
+
+    @Override
+    public void run() {
+        while(!Thread.interrupted()) {
+            try {
+                // There's no guarantee that QEMU pipes will be ready at the moment
+                // this method is invoked. We simply try to get the pipe open and
+                // retry on failure indefinitely.
+                while (mPipe == null) {
+                    openPipe();
+                    Thread.sleep(100);
+                }
+                int size = mPipe.readInt();
+                size = Integer.reverseBytes(size);
+                byte[] receivedData = new byte[size];
+                mPipe.readFully(receivedData);
+                mHostClipboardCallback.onHostClipboardUpdated(
+                    new String(receivedData));
+            } catch (IOException e) {
+                try {
+                    mPipe.close();
+                } catch (IOException ee) {}
+                mPipe = null;
+            } catch (InterruptedException e) {}
+        }
+    }
+
+    public void setHostClipboard(String content) {
+        try {
+            if (mPipe != null) {
+                mPipe.writeInt(Integer.reverseBytes(content.getBytes().length));
+                mPipe.write(content.getBytes());
+            }
+        } catch(IOException e) {
+            Slog.e("HostClipboardMonitor",
+                   "Failed to set host clipboard " + e.getMessage());
+        }
+    }
+}
+
 /**
  * Implementation of the clipboard for copy and paste.
  */
 public class ClipboardService extends SystemService {
 
     private static final String TAG = "ClipboardService";
+    private static final boolean IS_EMULATOR =
+        SystemProperties.getBoolean("ro.kernel.qemu", false);
 
     private final IActivityManager mAm;
     private final IUserManager mUm;
     private final PackageManager mPm;
     private final AppOpsManager mAppOps;
     private final IBinder mPermissionOwner;
+    private HostClipboardMonitor mHostClipboardMonitor = null;
+    private Thread mHostMonitorThread = null;
 
     private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
 
@@ -82,6 +171,23 @@
             Slog.w("clipboard", "AM dead", e);
         }
         mPermissionOwner = permOwner;
+        if (IS_EMULATOR) {
+            mHostClipboardMonitor = new HostClipboardMonitor(
+                new HostClipboardMonitor.HostClipboardCallback() {
+                    @Override
+                    public void onHostClipboardUpdated(String contents){
+                        ClipData clip =
+                            new ClipData("host clipboard",
+                                         new String[]{"text/plain"},
+                                         new ClipData.Item(contents));
+                        synchronized(mClipboards) {
+                            setPrimaryClipInternal(getClipboard(0), clip);
+                        }
+                    }
+                });
+            mHostMonitorThread = new Thread(mHostClipboardMonitor);
+            mHostMonitorThread.start();
+        }
     }
 
     @Override
@@ -142,6 +248,11 @@
                 if (clip != null && clip.getItemCount() <= 0) {
                     throw new IllegalArgumentException("No items");
                 }
+                if (clip.getItemAt(0).getText() != null &&
+                    mHostClipboardMonitor != null) {
+                    mHostClipboardMonitor.setHostClipboard(
+                        clip.getItemAt(0).getText().toString());
+                }
                 final int callingUid = Binder.getCallingUid();
                 if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
                         callingPackage) != AppOpsManager.MODE_ALLOWED) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index e84bf40..b0e4509 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -975,8 +975,6 @@
         private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
         private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
 
-        private int mPreviousMobileType = ConnectivityManager.TYPE_NONE;
-
         private static final int UPSTREAM_SETTLE_TIME_MS     = 10000;
 
         TetherMasterSM(String name, Looper looper) {
@@ -1010,43 +1008,14 @@
                 return false;
             }
 
-            protected boolean requestUpstreamMobileConnection(int apnType) {
-                if (apnType == ConnectivityManager.TYPE_NONE) { return false; }
-
-                if (apnType != mPreviousMobileType) {
-                    // Unregister any previous mobile upstream callback because
-                    // this request, if any, will be different.
-                    unrequestUpstreamMobileConnection();
-                }
-
-                if (mUpstreamNetworkMonitor.mobileNetworkRequested()) {
-                    // Looks like we already filed a request for this apnType.
-                    return true;
-                }
-
-                switch (apnType) {
-                    case ConnectivityManager.TYPE_MOBILE_DUN:
-                    case ConnectivityManager.TYPE_MOBILE:
-                    case ConnectivityManager.TYPE_MOBILE_HIPRI:
-                        mPreviousMobileType = apnType;
-                        break;
-                    default:
-                        return false;
-                }
-
-                // TODO: Replace this with a call to pass the current tethering
-                // configuration to mUpstreamNetworkMonitor and let it handle
-                // choosing APN type accordingly.
-                mUpstreamNetworkMonitor.updateMobileRequiresDun(
-                        apnType == ConnectivityManager.TYPE_MOBILE_DUN);
-
+            protected boolean requestUpstreamMobileConnection() {
+                mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
                 mUpstreamNetworkMonitor.registerMobileNetworkRequest();
                 return true;
             }
 
             protected void unrequestUpstreamMobileConnection() {
                 mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
-                mPreviousMobileType = ConnectivityManager.TYPE_NONE;
             }
 
             protected boolean turnOnMasterTetherSettings() {
@@ -1128,11 +1097,10 @@
                     case ConnectivityManager.TYPE_MOBILE_DUN:
                     case ConnectivityManager.TYPE_MOBILE_HIPRI:
                         // If we're on DUN, put our own grab on it.
-                        requestUpstreamMobileConnection(upType);
+                        requestUpstreamMobileConnection();
                         break;
                     case ConnectivityManager.TYPE_NONE:
-                        if (tryCell &&
-                                requestUpstreamMobileConnection(preferredUpstreamMobileApn)) {
+                        if (tryCell && requestUpstreamMobileConnection()) {
                             // We think mobile should be coming up; don't set a retry.
                         } else {
                             sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 0ec48b9..0357b1b 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -75,6 +75,11 @@
     };
 
     /**
+     * The transition time, in milliseconds, for Night Display to turn on/off.
+     */
+    private static final long TRANSITION_DURATION = 3000L;
+
+    /**
      * The identity matrix, used if one of the given matrices is {@code null}.
      */
     private static final float[] MATRIX_IDENTITY = new float[16];
@@ -285,8 +290,7 @@
 
             mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
                     from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
-            mColorMatrixAnimator.setDuration(getContext().getResources()
-                    .getInteger(android.R.integer.config_longAnimTime));
+            mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
             mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
                     getContext(), android.R.interpolator.fast_out_slow_in));
             mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 997222c..1feae3d 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -482,7 +482,7 @@
         UserManager um = UserManager.get(mContext);
 
         // Allow current user or profiles of the current user...
-        for (int profileId : um.getEnabledProfileIds(userId)) {
+        for (int profileId : um.getEnabledProfileIds(mCurrentUserId)) {
             if (profileId == userId) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9b37f12..3bf95ef 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -17,6 +17,7 @@
 package com.android.server.media;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
@@ -36,10 +37,13 @@
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
 import android.media.session.IActiveSessionsListener;
+import android.media.session.IOnMediaKeyListener;
+import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
 import android.media.session.ISessionCallback;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -78,10 +82,11 @@
 public class MediaSessionService extends SystemService implements Monitor {
     private static final String TAG = "MediaSessionService";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    // Leave log for media key event always.
-    private static final boolean DEBUG_MEDIA_KEY_EVENT = DEBUG || true;
+    // Leave log for key event always.
+    private static final boolean DEBUG_KEY_EVENT = true;
 
     private static final int WAKELOCK_TIMEOUT = 5000;
+    private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000;
 
     /* package */final IBinder mICallback = new Binder();
 
@@ -523,6 +528,14 @@
         }
     }
 
+    private String getCallingPackageName(int uid) {
+        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
+        if (packages != null && packages.length > 0) {
+            return packages[0];
+        }
+        return "";
+    }
+
     /**
      * Information about a particular user. The contents of this object is
      * guarded by mLock.
@@ -534,6 +547,15 @@
         private PendingIntent mLastMediaButtonReceiver;
         private ComponentName mRestoredMediaButtonReceiver;
 
+        private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener;
+        private int mOnVolumeKeyLongPressListenerUid;
+        private KeyEvent mInitialDownVolumeKeyEvent;
+        private int mInitialDownVolumeStream;
+        private boolean mInitialDownMusicOnly;
+
+        private IOnMediaKeyListener mOnMediaKeyListener;
+        private int mOnMediaKeyListenerUid;
+
         public UserRecord(Context context, int userId) {
             mContext = context;
             mUserId = userId;
@@ -564,6 +586,12 @@
             String indent = prefix + "  ";
             pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
             pw.println(indent + "Restored ButtonReceiver:" + mRestoredMediaButtonReceiver);
+            pw.println(indent + "Volume key long-press listener:" + mOnVolumeKeyLongPressListener);
+            pw.println(indent + "Volume key long-press listener package:" +
+                    getCallingPackageName(mOnVolumeKeyLongPressListenerUid));
+            pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
+            pw.println(indent + "Media key listener package: " +
+                    getCallingPackageName(mOnMediaKeyListenerUid));
             int size = mSessions.size();
             pw.println(indent + size + " Sessions:");
             for (int i = 0; i < size; i++) {
@@ -769,28 +797,10 @@
                 }
 
                 synchronized (mLock) {
-                    // If we don't have a media button receiver to fall back on
-                    // include non-playing sessions for dispatching
-                    boolean useNotPlayingSessions = true;
-                    for (int userId : mCurrentUserIdList) {
-                        UserRecord ur = mUserRecords.get(userId);
-                        if (ur.mLastMediaButtonReceiver != null
-                                || ur.mRestoredMediaButtonReceiver != null) {
-                            useNotPlayingSessions = false;
-                            break;
-                        }
-                    }
-
-                    if (DEBUG) {
-                        Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
-                                + useNotPlayingSessions);
-                    }
-                    MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
-                            mCurrentUserIdList, useNotPlayingSessions);
-                    if (isVoiceKey(keyEvent.getKeyCode())) {
-                        handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
+                    if (!isGlobalPriorityActive() && isVoiceKey(keyEvent.getKeyCode())) {
+                        handleVoiceKeyEventLocked(keyEvent, needWakeLock);
                     } else {
-                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, true);
                     }
                 }
             } finally {
@@ -799,13 +809,245 @@
         }
 
         @Override
+        public void setOnVolumeKeyLongPressListener(IOnVolumeKeyLongPressListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                // Enforce SET_VOLUME_KEY_LONG_PRESS_LISTENER permission.
+                if (getContext().checkPermission(
+                        android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER, pid, uid)
+                            != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Must hold the SET_VOLUME_KEY_LONG_PRESS_LISTENER" +
+                            " permission.");
+                }
+
+                synchronized (mLock) {
+                    UserRecord user = mUserRecords.get(UserHandle.getUserId(uid));
+                    if (user.mOnVolumeKeyLongPressListener != null &&
+                            user.mOnVolumeKeyLongPressListenerUid != uid) {
+                        Log.w(TAG, "Volume key long-press listener cannot be reset by another app");
+                        return;
+                    }
+
+                    user.mOnVolumeKeyLongPressListener = listener;
+                    user.mOnVolumeKeyLongPressListenerUid = uid;
+
+                    Log.d(TAG, "Volume key long-press listener "
+                            + listener + " is set by " + getCallingPackageName(uid));
+
+                    if (user.mOnVolumeKeyLongPressListener != null) {
+                        try {
+                            user.mOnVolumeKeyLongPressListener.asBinder().linkToDeath(
+                                    new IBinder.DeathRecipient() {
+                                        @Override
+                                        public void binderDied() {
+                                            synchronized (mLock) {
+                                                user.mOnVolumeKeyLongPressListener = null;
+                                            }
+                                        }
+                                    }, 0);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Failed to set death recipient "
+                                    + user.mOnVolumeKeyLongPressListener);
+                            user.mOnVolumeKeyLongPressListener = null;
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setOnMediaKeyListener(IOnMediaKeyListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                // Enforce SET_MEDIA_KEY_LISTENER permission.
+                if (getContext().checkPermission(
+                        android.Manifest.permission.SET_MEDIA_KEY_LISTENER, pid, uid)
+                            != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Must hold the SET_MEDIA_KEY_LISTENER" +
+                            " permission.");
+                }
+
+                synchronized (mLock) {
+                    int userId = UserHandle.getUserId(uid);
+                    UserRecord user = mUserRecords.get(userId);
+                    if (user.mOnMediaKeyListener != null && user.mOnMediaKeyListenerUid != uid) {
+                        Log.w(TAG, "Media key listener cannot be reset by another app");
+                        return;
+                    }
+
+                    user.mOnMediaKeyListener = listener;
+                    user.mOnMediaKeyListenerUid = uid;
+
+                    Log.d(TAG, "Media key listener " + user.mOnMediaKeyListener
+                            + " is set by " + getCallingPackageName(uid));
+
+                    if (user.mOnMediaKeyListener != null) {
+                        try {
+                            user.mOnMediaKeyListener.asBinder().linkToDeath(
+                                    new IBinder.DeathRecipient() {
+                                        @Override
+                                        public void binderDied() {
+                                            synchronized (mLock) {
+                                                user.mOnMediaKeyListener = null;
+                                            }
+                                        }
+                                    }, 0);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Failed to set death recipient " + user.mOnMediaKeyListener);
+                            user.mOnMediaKeyListener = null;
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        /**
+         * Handles the dispatching of the volume button events to one of the
+         * registered listeners. If there's a volume key long-press listener and
+         * there's no active global priority session, long-pressess will be sent to the
+         * long-press listener instead of adjusting volume.
+         *
+         * @param keyEvent a non-null KeyEvent whose key code is one of the
+         *            {@link KeyEvent#KEYCODE_VOLUME_UP},
+         *            {@link KeyEvent#KEYCODE_VOLUME_DOWN},
+         *            or {@link KeyEvent#KEYCODE_VOLUME_MUTE}.
+         * @param stream stream type to adjust volume.
+         * @param musicOnly true if both UI nor haptic feedback aren't needed when adjust volume.
+         */
+        @Override
+        public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
+            if (keyEvent == null ||
+                    (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
+                             && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
+                             && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
+                Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
+                return;
+            }
+
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+
+            if (DEBUG) {
+                Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
+                        + keyEvent);
+            }
+
+            try {
+                synchronized (mLock) {
+                    // Only consider full user.
+                    UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+
+                    if (mPriorityStack.isGlobalPriorityActive()
+                            || user.mOnVolumeKeyLongPressListener == null) {
+                        dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
+                    } else {
+                        // TODO: Consider the case when both volume up and down keys are pressed
+                        //       at the same time.
+                        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                            if (keyEvent.getRepeatCount() == 0) {
+                                user.mInitialDownVolumeKeyEvent = keyEvent;
+                                user.mInitialDownVolumeStream = stream;
+                                user.mInitialDownMusicOnly = musicOnly;
+                            }
+                            if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
+                                if (user.mInitialDownVolumeKeyEvent != null) {
+                                    dispatchVolumeKeyLongPressLocked(
+                                            user.mInitialDownVolumeKeyEvent);
+                                    // Mark that the key is already handled.
+                                    user.mInitialDownVolumeKeyEvent = null;
+                                }
+                                dispatchVolumeKeyLongPressLocked(keyEvent);
+                            }
+                        } else { // if up
+                            if (user.mInitialDownVolumeKeyEvent != null
+                                    && user.mInitialDownVolumeKeyEvent.getDownTime()
+                                            == keyEvent.getDownTime()) {
+                                // Short-press. Should change volume.
+                                dispatchVolumeKeyEventLocked(
+                                        user.mInitialDownVolumeKeyEvent,
+                                        user.mInitialDownVolumeStream,
+                                        user.mInitialDownMusicOnly);
+                                dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
+                            } else {
+                                dispatchVolumeKeyLongPressLocked(keyEvent);
+                            }
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        private void dispatchVolumeKeyLongPressLocked(KeyEvent keyEvent) {
+            // Only consider full user.
+            UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+            try {
+                user.mOnVolumeKeyLongPressListener.onVolumeKeyLongPress(keyEvent);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to send " + keyEvent + " to volume key long-press listener");
+            }
+        }
+
+        private void dispatchVolumeKeyEventLocked(
+                KeyEvent keyEvent, int stream, boolean musicOnly) {
+            boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
+            boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
+            int direction = 0;
+            boolean isMute = false;
+            switch (keyEvent.getKeyCode()) {
+                case KeyEvent.KEYCODE_VOLUME_UP:
+                    direction = AudioManager.ADJUST_RAISE;
+                    break;
+                case KeyEvent.KEYCODE_VOLUME_DOWN:
+                    direction = AudioManager.ADJUST_LOWER;
+                    break;
+                case KeyEvent.KEYCODE_VOLUME_MUTE:
+                    isMute = true;
+                    break;
+            }
+            if (down || up) {
+                int flags = AudioManager.FLAG_FROM_KEY;
+                if (musicOnly) {
+                    // This flag is used when the screen is off to only affect active media.
+                    flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
+                } else {
+                    // These flags are consistent with the home screen
+                    if (up) {
+                        flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
+                    } else {
+                        flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
+                    }
+                }
+                if (direction != 0) {
+                    // If this is action up we want to send a beep for non-music events
+                    if (up) {
+                        direction = 0;
+                    }
+                    dispatchAdjustVolumeLocked(stream, direction, flags);
+                } else if (isMute) {
+                    if (down && keyEvent.getRepeatCount() == 0) {
+                        dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
+                    }
+                }
+            }
+        }
+
+        @Override
         public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    MediaSessionRecord session = mPriorityStack
-                            .getDefaultVolumeSession(mCurrentUserIdList);
-                    dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
+                    dispatchAdjustVolumeLocked(suggestedStream, delta, flags);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -881,8 +1123,9 @@
             return resolvedUserId;
         }
 
-        private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
-                MediaSessionRecord session) {
+        private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
+            MediaSessionRecord session = mPriorityStack.getDefaultVolumeSession(mCurrentUserIdList);
+
             boolean preferSuggestedStream = false;
             if (isValidLocalStreamType(suggestedStream)
                     && AudioSystem.isStreamActive(suggestedStream, 0)) {
@@ -925,13 +1168,7 @@
             }
         }
 
-        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
-                MediaSessionRecord session) {
-            if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
-                // If the phone app has priority just give it the event
-                dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
-                return;
-            }
+        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {
             int action = keyEvent.getAction();
             boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
             if (action == KeyEvent.ACTION_DOWN) {
@@ -948,24 +1185,60 @@
                     if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
                         // Resend the down then send this event through
                         KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
-                        dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
-                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
+                        dispatchMediaKeyEventLocked(downEvent, needWakeLock, true);
+                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, true);
                     }
                 }
             }
         }
 
         private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
-                MediaSessionRecord session) {
+                boolean checkMediaKeyListener) {
+            // If we don't have a media button receiver to fall back on
+            // include non-playing sessions for dispatching.
+            boolean useNotPlayingSessions = true;
+            for (int userId : mCurrentUserIdList) {
+                UserRecord ur = mUserRecords.get(userId);
+                if (ur.mLastMediaButtonReceiver != null
+                        || ur.mRestoredMediaButtonReceiver != null) {
+                    useNotPlayingSessions = false;
+                    break;
+                }
+            }
+            if (DEBUG) {
+                Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
+                        + useNotPlayingSessions);
+            }
+
+            MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
+                    mCurrentUserIdList, useNotPlayingSessions);
+
+            if ((session == null
+                    || !session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY))
+                    && checkMediaKeyListener) {
+                // Only consider full user.
+                UserRecord user = mUserRecords.get(mCurrentUserIdList.get(0));
+                if (user.mOnMediaKeyListener != null) {
+                    if (DEBUG_KEY_EVENT) {
+                        Log.d(TAG, "Send " + keyEvent + " to media key listener");
+                    }
+                    try {
+                        user.mOnMediaKeyListener.onMediaKey(keyEvent,
+                                new MediaKeyListenerResultReceiver(keyEvent, needWakeLock));
+                        return;
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to send " + keyEvent + " to media key listener");
+                    }
+                }
+            }
             if (session != null) {
-                if (DEBUG_MEDIA_KEY_EVENT) {
+                if (DEBUG_KEY_EVENT) {
                     Log.d(TAG, "Sending " + keyEvent + " to " + session);
                 }
                 if (needWakeLock) {
                     mKeyEventReceiver.aquireWakeLockLocked();
                 }
-                // If we don't need a wakelock use -1 as the id so we
-                // won't release it later
+                // If we don't need a wakelock use -1 as the id so we won't release it later.
                 session.sendMediaButton(keyEvent,
                         needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                         mKeyEventReceiver, Process.SYSTEM_UID,
@@ -986,7 +1259,7 @@
                     mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
                     try {
                         if (user.mLastMediaButtonReceiver != null) {
-                            if (DEBUG_MEDIA_KEY_EVENT) {
+                            if (DEBUG_KEY_EVENT) {
                                 Log.d(TAG, "Sending " + keyEvent
                                         + " to the last known pendingIntent "
                                         + user.mLastMediaButtonReceiver);
@@ -995,7 +1268,7 @@
                                     needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                                     mediaButtonIntent, mKeyEventReceiver, mHandler);
                         } else {
-                            if (DEBUG_MEDIA_KEY_EVENT) {
+                            if (DEBUG_KEY_EVENT) {
                                 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
                                         + user.mRestoredMediaButtonReceiver);
                             }
@@ -1081,6 +1354,46 @@
                     && streamType <= AudioManager.STREAM_NOTIFICATION;
         }
 
+        private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
+            private KeyEvent mKeyEvent;
+            private boolean mNeedWakeLock;
+            private boolean mHandled;
+
+            private MediaKeyListenerResultReceiver(KeyEvent keyEvent, boolean needWakeLock) {
+                super(mHandler);
+                mHandler.postDelayed(this, MEDIA_KEY_LISTENER_TIMEOUT);
+                mKeyEvent = keyEvent;
+                mNeedWakeLock = needWakeLock;
+            }
+
+            @Override
+            public void run() {
+                Log.d(TAG, "The media key listener is timed-out for " + mKeyEvent);
+                dispatchMediaKeyEvent();
+            }
+
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == MediaSessionManager.RESULT_MEDIA_KEY_HANDLED) {
+                    mHandled = true;
+                    mHandler.removeCallbacks(this);
+                    return;
+                }
+                dispatchMediaKeyEvent();
+            }
+
+            private void dispatchMediaKeyEvent() {
+                if (mHandled) {
+                    return;
+                }
+                mHandled = true;
+                mHandler.removeCallbacks(this);
+                synchronized (mLock) {
+                    dispatchMediaKeyEventLocked(mKeyEvent, mNeedWakeLock, false);
+                }
+            }
+        }
+
         private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
 
         class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
new file mode 100644
index 0000000..4795fbf
--- /dev/null
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -0,0 +1,59 @@
+/**
+* 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.notification;
+
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * Determines whether a badge should be shown for this notification
+ */
+public class BadgeExtractor implements NotificationSignalExtractor {
+    private static final String TAG = "BadgeExtractor";
+    private static final boolean DBG = false;
+
+    private RankingConfig mConfig;
+
+    public void initialize(Context ctx, NotificationUsageStats usageStats) {
+        if (DBG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
+    }
+
+    public RankingReconsideration process(NotificationRecord record) {
+        if (record == null || record.getNotification() == null) {
+            if (DBG) Slog.d(TAG, "skipping empty notification");
+            return null;
+        }
+
+        if (mConfig == null) {
+            if (DBG) Slog.d(TAG, "missing config");
+            return null;
+        }
+        boolean appCanShowBadge =
+                mConfig.canShowBadge(record.sbn.getPackageName(), record.sbn.getUid());
+        if (!appCanShowBadge) {
+            record.setShowBadge(false);
+        } else {
+            record.setShowBadge(record.getChannel().canShowBadge() && appCanShowBadge);
+        }
+
+        return null;
+    }
+
+    @Override
+    public void setConfig(RankingConfig config) {
+        mConfig = config;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 1e0035d..6f49df4 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -30,7 +30,6 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.Slog;
 
 import java.util.Comparator;
 import java.util.Objects;
@@ -57,7 +56,15 @@
 
     @Override
     public int compare(NotificationRecord left, NotificationRecord right) {
-        // First up: sufficiently important ongoing notifications of certain categories
+        // first all colorized notifications
+        boolean leftImportantColorized = isImportantColorized(left);
+        boolean rightImportantColorized = isImportantColorized(right);
+
+        if (leftImportantColorized != rightImportantColorized) {
+            return -1 * Boolean.compare(leftImportantColorized, rightImportantColorized);
+        }
+
+        // sufficiently important ongoing notifications of certain categories
         boolean leftImportantOngoing = isImportantOngoing(left);
         boolean rightImportantOngoing = isImportantOngoing(right);
 
@@ -106,6 +113,13 @@
         return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
     }
 
+    private boolean isImportantColorized(NotificationRecord record) {
+        if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
+            return false;
+        }
+        return record.getNotification().isColorized();
+    }
+
     private boolean isImportantOngoing(NotificationRecord record) {
         if (!isOngoing(record)) {
             return false;
@@ -148,7 +162,6 @@
         return (record.getNotification().flags & ongoingFlags) != 0;
     }
 
-
     private Class<? extends Notification.Style> getNotificationStyle(NotificationRecord record) {
         String templateClass =
                 record.getNotification().extras.getString(Notification.EXTRA_TEMPLATE);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 168884d..45bdb9c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -32,6 +32,7 @@
 import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
 import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
 import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
+import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
 import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
 import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
@@ -50,6 +51,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
@@ -165,6 +167,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -229,6 +232,12 @@
 
     private static final long DELAY_FOR_ASSISTANT_TIME = 100;
 
+    private static final String ACTION_NOTIFICATION_TIMEOUT =
+            NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
+    private static final int REQUEST_CODE_TIMEOUT = 1;
+    private static final String SCHEME_TIMEOUT = "timeout";
+    private static final String EXTRA_KEY = "key";
+
     private IActivityManager mAm;
     private IPackageManager mPackageManager;
     private PackageManager mPackageManagerClient;
@@ -237,6 +246,7 @@
     @Nullable StatusBarManagerInternal mStatusBar;
     Vibrator mVibrator;
     private WindowManagerInternal mWindowManagerInternal;
+    private AlarmManager mAlarmManager;
 
     final IBinder mForegroundToken = new Binder();
     private Handler mHandler;
@@ -682,6 +692,29 @@
         updateLightsLocked();
     }
 
+    private final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+            if (ACTION_NOTIFICATION_TIMEOUT.equals(action)) {
+                final NotificationRecord record;
+                synchronized (mNotificationLock) {
+                    record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY));
+                }
+                if (record != null) {
+                    cancelNotification(record.sbn.getUid(), record.sbn.getInitialPid(),
+                            record.sbn.getPackageName(), record.sbn.getTag(),
+                            record.sbn.getId(), 0,
+                            Notification.FLAG_FOREGROUND_SERVICE, true, record.getUserId(),
+                            REASON_TIMEOUT, null);
+                }
+            }
+        }
+    };
+
     private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -966,6 +999,7 @@
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
         mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
 
         mHandler = new WorkerHandler(looper);
         mRankingThread.start();
@@ -1132,6 +1166,10 @@
         getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
                 null);
 
+        IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+        timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+        getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
+
         mSettingsObserver = new SettingsObserver(mHandler);
 
         mArchive = new Archive(resources.getInteger(
@@ -1243,6 +1281,35 @@
         sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
     }
 
+    private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
+            boolean fromAssistant) {
+        if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+            // cancel
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+                    UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+                    null);
+        }
+        if (fromAssistant) {
+            mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+        } else {
+            mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+        }
+
+        synchronized (mNotificationList) {
+            final int N = mNotificationList.size();
+            for (int i = N - 1; i >= 0; --i) {
+                NotificationRecord r = mNotificationList.get(i);
+                if (channel.getId() != null && channel.getId().equals(r.getChannel().getId())) {
+                    r.updateNotificationChannel(mRankingHelper.getNotificationChannel(
+                            r.sbn.getPackageName(), r.getUser().getIdentifier(),
+                            channel.getId(), false));
+                }
+            }
+        }
+        mRankingHandler.requestSort(true);
+        savePolicyFile();
+    }
+
     private ArrayList<ComponentName> getSuppressors() {
         ArrayList<ComponentName> names = new ArrayList<ComponentName>();
         for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1521,6 +1588,19 @@
         }
 
         @Override
+        public boolean canShowBadge(String pkg, int uid) {
+            checkCallerIsSystem();
+            return mRankingHelper.canShowBadge(pkg, uid);
+        }
+
+        @Override
+        public void setShowBadge(String pkg, int uid, boolean showBadge) {
+            checkCallerIsSystem();
+            mRankingHelper.setShowBadge(pkg, uid, showBadge);
+            savePolicyFile();
+        }
+
+        @Override
         public void createNotificationChannels(String pkg,
                 ParceledListSlice channelsList) throws RemoteException {
             checkCallerIsSystemOrSameApp(pkg);
@@ -1566,15 +1646,8 @@
         public void updateNotificationChannelForPackage(String pkg, int uid,
                 NotificationChannel channel) {
             enforceSystemOrSystemUI("Caller not system or systemui");
-            if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
-                // cancel
-                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
-                        UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
-                        null);
-            }
-            mRankingHelper.updateNotificationChannel(pkg, uid, channel);
-            mRankingHandler.requestSort(true);
-            savePolicyFile();
+            Preconditions.checkNotNull(channel);
+            updateNotificationChannelInt(pkg, uid, channel, false);
         }
 
         @Override
@@ -1648,6 +1721,9 @@
         /**
          * Public API for getting a list of current notifications for the calling package/uid.
          *
+         * Note that since notification posting is done asynchronously, this will not return
+         * notifications that are in the process of being posted.
+         *
          * @returns A list of all the package's notifications, in natural order.
          */
         @Override
@@ -1701,7 +1777,6 @@
                 return new StatusBarNotification(
                         sbn.getPackageName(),
                         sbn.getOpPkg(),
-                        sbn.getNotificationChannel(),
                         sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
                         sbn.getNotification().clone(),
                         sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
@@ -1910,31 +1985,16 @@
         }
 
         /**
-         * Allow an INotificationListener to snooze a single notification.
+         * Allows the notification assistant to un-snooze a single notification.
          *
-         * @param token The binder for the listener, to check that the caller is allowed
+         * @param token The binder for the assistant, to check that the caller is allowed
          */
         @Override
-        public void snoozeNotificationFromListener(INotificationListener token, String key) {
+        public void unsnoozeNotificationFromAssistant(INotificationListener token, String key) {
             long identity = Binder.clearCallingIdentity();
             try {
-                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, null, info);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        /**
-         * Allow an INotificationListener to un-snooze a single notification.
-         *
-         * @param token The binder for the listener, to check that the caller is allowed
-         */
-        @Override
-        public void unsnoozeNotificationFromListener(INotificationListener token, String key) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                final ManagedServiceInfo info =
+                        mNotificationAssistants.checkServiceTokenLocked(token);
                 unsnoozeNotificationInt(key, info);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -2005,6 +2065,36 @@
             }
         }
 
+        /**
+         * Allow an INotificationListener to request the list of outstanding snoozed notifications
+         * seen by the current user. Useful when starting up, after which point the listener
+         * callbacks should be used.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         * @returns The return value will contain the notifications specified in keys, in that
+         *      order, or if keys is null, all the notifications, in natural order.
+         */
+        @Override
+        public ParceledListSlice<StatusBarNotification> getSnoozedNotificationsFromListener(
+                INotificationListener token, int trim) {
+            synchronized (mNotificationLock) {
+                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                List<NotificationRecord> snoozedRecords = mSnoozeHelper.getSnoozed();
+                final int N = snoozedRecords.size();
+                final ArrayList<StatusBarNotification> list = new ArrayList<>(N);
+                for (int i=0; i < N; i++) {
+                    final NotificationRecord r = snoozedRecords.get(i);
+                    if (r == null) continue;
+                    StatusBarNotification sbn = r.sbn;
+                    if (!isVisibleToListener(sbn, info)) continue;
+                    StatusBarNotification sbnToSend =
+                            (trim == TRIM_FULL) ? sbn : sbn.cloneLight();
+                    list.add(sbnToSend);
+                }
+                return new ParceledListSlice<>(list);
+            }
+        }
+
         @Override
         public void requestHintsFromListener(INotificationListener token, int hints) {
             final long identity = Binder.clearCallingIdentity();
@@ -2495,15 +2585,9 @@
         public void updateNotificationChannelFromAssistant(INotificationListener token, String pkg,
                 NotificationChannel channel) throws RemoteException {
             ManagedServiceInfo info = mNotificationAssistants.checkServiceTokenLocked(token);
-            if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
-                // cancel
-                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
-                        info.userid, REASON_CHANNEL_BANNED, null);
-            }
+            Preconditions.checkNotNull(channel);
             int uid = mPackageManager.getPackageUid(pkg, 0, info.userid);
-            mRankingHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
-            mRankingHandler.requestSort(true);
-            savePolicyFile();
+            updateNotificationChannelInt(pkg, uid, channel, true);
         }
 
         @Override
@@ -2528,7 +2612,7 @@
             final ArrayList<SnoozeCriterion> snoozeCriterionList =
                     adjustment.getSignals().getParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA);
             if (!TextUtils.isEmpty(overrideChannelId)) {
-                n.setNotificationChannelOverride(mRankingHelper.getNotificationChannel(
+                n.updateNotificationChannel(mRankingHelper.getNotificationChannel(
                         n.sbn.getPackageName(), n.sbn.getUid(), overrideChannelId,
                         false /* includeDeleted */));
             }
@@ -2610,13 +2694,13 @@
                 final StatusBarNotification summarySbn =
                         new StatusBarNotification(adjustedSbn.getPackageName(),
                                 adjustedSbn.getOpPkg(),
-                                adjustedSbn.getNotificationChannel(),
                                 Integer.MAX_VALUE,
                                 GroupHelper.AUTOGROUP_KEY, adjustedSbn.getUid(),
                                 adjustedSbn.getInitialPid(), summaryNotification,
                                 adjustedSbn.getUser(), GroupHelper.AUTOGROUP_KEY,
                                 System.currentTimeMillis());
-                summaryRecord = new NotificationRecord(getContext(), summarySbn);
+                summaryRecord = new NotificationRecord(getContext(), summarySbn,
+                        notificationRecord.getChannel());
                 summaries.put(pkg, summarySbn.getKey());
             }
         }
@@ -2882,7 +2966,7 @@
         final NotificationChannel channel =  mRankingHelper.getNotificationChannelWithFallback(pkg,
                 callingUid, notification.getChannel(), false /* includeDeleted */);
         final StatusBarNotification n = new StatusBarNotification(
-                pkg, opPkg, channel, id, tag, callingUid, callingPid, notification,
+                pkg, opPkg, id, tag, callingUid, callingPid, notification,
                 user, null, System.currentTimeMillis());
 
         // Limit the number of notifications that any given package except the android
@@ -2946,10 +3030,7 @@
                 Notification.PRIORITY_MAX);
 
         // setup local book-keeping
-        final NotificationRecord r = new NotificationRecord(getContext(), n);
-        synchronized (mNotificationLock) {
-            mEnqueuedNotifications.add(r);
-        }
+        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
         mHandler.post(new EnqueueNotificationRunnable(userId, r));
 
         idOut[0] = id;
@@ -2967,6 +3048,9 @@
         @Override
         public void run() {
             synchronized (mNotificationLock) {
+                mEnqueuedNotifications.add(r);
+                scheduleTimeoutLocked(r);
+
                 if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) {
                     // TODO: log to event log
                     if (DBG) {
@@ -3196,6 +3280,22 @@
     }
 
     @VisibleForTesting
+    void scheduleTimeoutLocked(NotificationRecord record) {
+        if (record.getNotification().getTimeout() > System.currentTimeMillis()) {
+            final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
+                    REQUEST_CODE_TIMEOUT,
+                    new Intent(ACTION_NOTIFICATION_TIMEOUT)
+                            .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+                                    .appendPath(record.getKey()).build())
+                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                            .putExtra(EXTRA_KEY, record.getKey()),
+                    PendingIntent.FLAG_UPDATE_CURRENT);
+            mAlarmManager.setExactAndAllowWhileIdle(
+                    AlarmManager.RTC_WAKEUP, record.getNotification().getTimeout(), pi);
+        }
+    }
+
+    @VisibleForTesting
     void buzzBeepBlinkLocked(NotificationRecord record) {
         boolean buzz = false;
         boolean beep = false;
@@ -3490,14 +3590,18 @@
         boolean forceUpdate = ((Boolean) msg.obj == null) ? false : (boolean) msg.obj;
         synchronized (mNotificationLock) {
             final int N = mNotificationList.size();
+            // Any field that can change via one of the extractors or by the assistant
+            // needs to be added here.
             ArrayList<String> orderBefore = new ArrayList<String>(N);
             ArrayList<String> groupOverrideBefore = new ArrayList<>(N);
             int[] visibilities = new int[N];
+            boolean[] showBadges = new boolean[N];
             for (int i = 0; i < N; i++) {
                 final NotificationRecord r = mNotificationList.get(i);
                 orderBefore.add(r.getKey());
                 groupOverrideBefore.add(r.sbn.getGroupKey());
                 visibilities[i] = r.getPackageVisibilityOverride();
+                showBadges[i] = r.canShowBadge();
                 mRankingHelper.extractSignals(r);
             }
             mRankingHelper.sort(mNotificationList);
@@ -3506,7 +3610,8 @@
                 if (forceUpdate
                         || !orderBefore.get(i).equals(r.getKey())
                         || visibilities[i] != r.getPackageVisibilityOverride()
-                        || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())) {
+                        || !groupOverrideBefore.get(i).equals(r.sbn.getGroupKey())
+                        || showBadges[i] != r.canShowBadge()) {
                     scheduleSendRankingUpdate();
                     return;
                 }
@@ -3949,14 +4054,14 @@
     void snoozeNotificationInt(String key, long until, String snoozeCriterionId,
             ManagedServiceInfo listener) {
         String listenerName = listener == null ? null : listener.component.toShortString();
+        if (until < System.currentTimeMillis() && snoozeCriterionId == null) {
+            return;
+        }
         // TODO: write to event log
         if (DBG) {
             Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, until, snoozeCriterionId,
                     listenerName));
         }
-        if (until != SNOOZE_UNTIL_UNSPECIFIED && until < System.currentTimeMillis()) {
-            return;
-        }
         // Needs to post so that it can cancel notifications not yet enqueued.
         mHandler.post(new Runnable() {
             @Override
@@ -3969,8 +4074,6 @@
                         if (snoozeCriterionId != null) {
                             mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn,
                                     snoozeCriterionId);
-                        }
-                        if (until == SNOOZE_UNTIL_UNSPECIFIED) {
                             mSnoozeHelper.snooze(r);
                         } else {
                             mSnoozeHelper.snooze(r, until);
@@ -4246,9 +4349,10 @@
         Bundle visibilityOverrides = new Bundle();
         Bundle suppressedVisualEffects = new Bundle();
         Bundle explanation = new Bundle();
-        Bundle overrideChannels = new Bundle();
+        Bundle channels = new Bundle();
         Bundle overridePeople = new Bundle();
         Bundle snoozeCriteria = new Bundle();
+        Bundle showBadge = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -4270,9 +4374,10 @@
                 visibilityOverrides.putInt(key, record.getPackageVisibilityOverride());
             }
             overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey());
-            overrideChannels.putParcelable(key, record.getChannel());
+            channels.putParcelable(key, record.getChannel());
             overridePeople.putStringArrayList(key, record.getPeopleOverride());
             snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
+            showBadge.putBoolean(key, record.canShowBadge());
         }
         final int M = keys.size();
         String[] keysAr = keys.toArray(new String[M]);
@@ -4283,7 +4388,7 @@
         }
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
                 suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
-                overrideChannels, overridePeople, snoozeCriteria);
+                channels, overridePeople, snoozeCriteria, showBadge);
     }
 
     private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e8c3d97..8998128 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,7 +25,6 @@
 import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -42,6 +41,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
+import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.EventLogTags;
@@ -114,12 +114,14 @@
     private Uri mSound;
     private long[] mVibration;
     private AudioAttributes mAttributes;
-    private NotificationChannel mOverrideChannel;
+    private NotificationChannel mChannel;
     private ArrayList<String> mPeopleOverride;
     private ArrayList<SnoozeCriterion> mSnoozeCriteria;
+    private boolean mShowBadge;
 
     @VisibleForTesting
-    public NotificationRecord(Context context, StatusBarNotification sbn)
+    public NotificationRecord(Context context, StatusBarNotification sbn,
+            NotificationChannel channel)
     {
         this.sbn = sbn;
         mOriginalFlags = sbn.getNotification().flags;
@@ -128,6 +130,7 @@
         mUpdateTimeMs = mCreationTimeMs;
         mContext = context;
         stats = new NotificationUsageStats.SingleNotificationStats();
+        mChannel = channel;
         mPreChannelsNotification = isPreChannelsNotification();
         mSound = calculateSound();
         mVibration = calculateVibration();
@@ -154,7 +157,7 @@
     private Uri calculateSound() {
         final Notification n = sbn.getNotification();
 
-        Uri sound = sbn.getNotificationChannel().getSound();
+        Uri sound = mChannel.getSound();
         if (mPreChannelsNotification && (getChannel().getUserLockedFields()
                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
 
@@ -316,6 +319,7 @@
         pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
         pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
                 notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
+        pw.println(prefix + "  timeout=" + TimeUtils.formatForLogging(notification.getTimeout()));
         if (notification.actions != null && notification.actions.length > 0) {
             pw.println(prefix + "  actions={");
             final int N = notification.actions.length;
@@ -386,7 +390,8 @@
         pw.println(prefix + "  mSound= " + mSound);
         pw.println(prefix + "  mVibration= " + mVibration);
         pw.println(prefix + "  mAttributes= " + mAttributes);
-        pw.println(prefix + "  overrideChannel=" + getChannel());
+        pw.println(prefix + "  mShowBadge=" + mShowBadge);
+        pw.println(prefix + "  channel=" + getChannel());
         if (getPeopleOverride() != null) {
             pw.println(prefix + "  overridePeople= " + TextUtils.join(",", getPeopleOverride()));
         }
@@ -640,16 +645,24 @@
     }
 
     public NotificationChannel getChannel() {
-        return mOverrideChannel == null ? sbn.getNotificationChannel() : mOverrideChannel;
+        return mChannel;
     }
 
-    protected void setNotificationChannelOverride(NotificationChannel channel) {
-        mOverrideChannel = channel;
-        if (mOverrideChannel != null) {
+    protected void updateNotificationChannel(NotificationChannel channel) {
+        if (channel != null) {
+            mChannel = channel;
             calculateImportance();
         }
     }
 
+    public void setShowBadge(boolean showBadge) {
+        mShowBadge = showBadge;
+    }
+
+    public boolean canShowBadge() {
+        return mShowBadge;
+    }
+
     public Uri getSound() {
         return mSound;
     }
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index c2cef09..492d5c6 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -22,6 +22,8 @@
 
     void setImportance(String packageName, int uid, int importance);
     int getImportance(String packageName, int uid);
+    void setShowBadge(String packageName, int uid, boolean showBadge);
+    boolean canShowBadge(String packageName, int uid);
 
     void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index e44fb7f..1861bcb 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -67,10 +67,12 @@
     private static final String ATT_PRIORITY = "priority";
     private static final String ATT_VISIBILITY = "visibility";
     private static final String ATT_IMPORTANCE = "importance";
+    private static final String ATT_SHOW_BADGE = "show_badge";
 
     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
+    private static final boolean DEFAULT_SHOW_BADGE = true;
 
     private final NotificationSignalExtractor[] mSignalExtractors;
     private final NotificationComparator mPreliminaryComparator;
@@ -169,7 +171,8 @@
                         Record r = getOrCreateRecord(name, uid,
                                 safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
                                 safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
-                                safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
+                                safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+                                safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
 
                         // Channels
                         final int innerDepth = parser.getDepth();
@@ -215,11 +218,11 @@
 
     private Record getOrCreateRecord(String pkg, int uid) {
         return getOrCreateRecord(pkg, uid,
-                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY);
+                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
     }
 
     private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
-            int visibility) {
+            int visibility, boolean showBadge) {
         final String key = recordKey(pkg, uid);
         Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(key);
         if (r == null) {
@@ -229,6 +232,7 @@
             r.importance = importance;
             r.priority = priority;
             r.visibility = visibility;
+            r.showBadge = showBadge;
             createDefaultChannelIfMissing(r);
             if (r.uid == Record.UNKNOWN_UID) {
                 mRestoredWithoutUids.put(pkg, r);
@@ -298,7 +302,7 @@
             }
             final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
                     || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
-                    || r.channels.size() > 0;
+                    || r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0;
             if (hasNonDefaultSettings) {
                 out.startTag(null, TAG_PACKAGE);
                 out.attribute(null, ATT_NAME, r.pkg);
@@ -311,6 +315,7 @@
                 if (r.visibility != DEFAULT_VISIBILITY) {
                     out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
                 }
+                out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
 
                 if (!forBackup) {
                     out.attribute(null, ATT_UID, Integer.toString(r.uid));
@@ -396,6 +401,12 @@
         return Collections.binarySearch(notificationList, target, mFinalComparator);
     }
 
+    private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+        final String value = parser.getAttributeValue(null, att);
+        if (TextUtils.isEmpty(value)) return defValue;
+        return Boolean.parseBoolean(value);
+    }
+
     private static int safeInt(XmlPullParser parser, String att, int defValue) {
         final String val = parser.getAttributeValue(null, att);
         return tryParseInt(val, defValue);
@@ -419,6 +430,17 @@
     }
 
     @Override
+    public boolean canShowBadge(String packageName, int uid) {
+        return getOrCreateRecord(packageName, uid).showBadge;
+    }
+
+    @Override
+    public void setShowBadge(String packageName, int uid, boolean showBadge) {
+        getOrCreateRecord(packageName, uid).showBadge = showBadge;
+        updateConfig();
+    }
+
+    @Override
     public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp) {
         Preconditions.checkNotNull(pkg);
@@ -454,6 +476,9 @@
         if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
             channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
         }
+        if (!r.showBadge) {
+            channel.setShowBadge(false);
+        }
         r.channels.put(channel.getId(), channel);
         updateConfig();
     }
@@ -672,7 +697,7 @@
             final Record r = records.valueAt(i);
             if (filter == null || filter.matches(r.pkg)) {
                 pw.print(prefix);
-                pw.print("  ");
+                pw.print("  AppSettings: ");
                 pw.print(r.pkg);
                 pw.print(" (");
                 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
@@ -689,6 +714,8 @@
                     pw.print(" visibility=");
                     pw.print(Notification.visibilityToString(r.visibility));
                 }
+                pw.print(" showBadge=");
+                pw.print(Boolean.toString(r.showBadge));
                 pw.println();
                 for (NotificationChannel channel : r.channels.values()) {
                     pw.print(prefix);
@@ -725,6 +752,9 @@
                     if (r.visibility != DEFAULT_VISIBILITY) {
                         record.put("visibility", Notification.visibilityToString(r.visibility));
                     }
+                    if (r.showBadge != DEFAULT_SHOW_BADGE) {
+                        record.put("showBadge", Boolean.valueOf(r.showBadge));
+                    }
                     for (NotificationChannel channel : r.channels.values()) {
                         record.put("channel", channel.toJson());
                     }
@@ -838,6 +868,7 @@
         int importance = DEFAULT_IMPORTANCE;
         int priority = DEFAULT_PRIORITY;
         int visibility = DEFAULT_VISIBILITY;
+        boolean showBadge = DEFAULT_SHOW_BADGE;
 
         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
    }
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index e14700a..f2aff11 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -41,6 +41,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -98,6 +99,26 @@
         return Collections.EMPTY_LIST;
     }
 
+    protected List<NotificationRecord> getSnoozed() {
+        List<NotificationRecord> snoozedForUser = new ArrayList<>();
+        int[] userIds = mUserProfiles.getCurrentProfileIds();
+        final int N = userIds.length;
+        for (int i = 0; i < N; i++) {
+            final ArrayMap<String, ArrayMap<String, NotificationRecord>> snoozedPkgs =
+                    mSnoozedNotifications.get(userIds[i]);
+            if (snoozedPkgs != null) {
+                final int M = snoozedPkgs.size();
+                for (int j = 0; j < M; j++) {
+                    final ArrayMap<String, NotificationRecord> records = snoozedPkgs.valueAt(j);
+                    if (records != null) {
+                        snoozedForUser.addAll(records.values());
+                    }
+                }
+            }
+        }
+        return snoozedForUser;
+    }
+
     /**
      * Snoozes a notification and schedules an alarm to repost at that time.
      */
diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java
index d735e72..96a0d18 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -78,6 +78,7 @@
                     int sequence) {
                 final String packageName;
                 final String splitName;
+                final int versionCode;
                 if (ephemeralResolveInfo != null) {
                     final ArrayList<EphemeralResolveInfo> ephemeralResolveInfoList =
                             new ArrayList<EphemeralResolveInfo>(1);
@@ -91,13 +92,16 @@
                             && ephemeralIntentInfo.resolveInfo != null) {
                         packageName = ephemeralIntentInfo.resolveInfo.getPackageName();
                         splitName = ephemeralIntentInfo.splitName;
+                        versionCode = ephemeralIntentInfo.resolveInfo.getVersionCode();
                     } else {
                         packageName = null;
                         splitName = null;
+                        versionCode = -1;
                     }
                 } else {
                     packageName = null;
                     splitName = null;
+                    versionCode = -1;
                 }
                 final Intent installerIntent = buildEphemeralInstallerIntent(
                         requestObj.launchIntent,
@@ -107,6 +111,7 @@
                         requestObj.userId,
                         packageName,
                         splitName,
+                        versionCode,
                         requestObj.responseObj.token,
                         false /*needsPhaseTwo*/);
                 installerIntent.setComponent(new ComponentName(
@@ -123,7 +128,7 @@
      */
     public static Intent buildEphemeralInstallerIntent(Intent launchIntent, Intent origIntent,
             String callingPackage, String resolvedType, int userId, String ephemeralPackageName,
-            String ephemeralSplitName, String token, boolean needsPhaseTwo) {
+            String ephemeralSplitName, int versionCode, String token, boolean needsPhaseTwo) {
         // Construct the intent that launches the ephemeral installer
         int flags = launchIntent.getFlags();
         final Intent intent = new Intent();
@@ -181,6 +186,7 @@
 
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, ephemeralPackageName);
             intent.putExtra(Intent.EXTRA_SPLIT_NAME, ephemeralSplitName);
+            intent.putExtra(Intent.EXTRA_VERSION_CODE, versionCode);
         }
 
         return intent;
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 0130e30..fc66bb3 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -295,6 +295,15 @@
         }
     }
 
+    public void removeIdmap(String overlayApkPath) throws InstallerException {
+        if (!checkBeforeRemote()) return;
+        try {
+            mInstalld.removeIdmap(overlayApkPath);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     public void rmdex(String codePath, String instructionSet) throws InstallerException {
         assertValidInstructionSet(instructionSet);
         if (!checkBeforeRemote()) return;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a74e141..b6611eb 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -219,11 +219,12 @@
         /**
          * Checks if the caller is in the same group as the userToCheck.
          */
-        private void ensureInUserProfiles(UserHandle userToCheck, String message) {
-            ensureInUserProfiles(userToCheck.getIdentifier(), message);
+        private void ensureInUserProfiles(
+                String callingPackage, UserHandle userToCheck, String message) {
+            ensureInUserProfiles(callingPackage, userToCheck.getIdentifier(), message);
         }
 
-        private void ensureInUserProfiles(int targetUserId, String message) {
+        private void ensureInUserProfiles(String callingPackage, int targetUserId, String message) {
             final int callingUserId = injectCallingUserId();
 
             if (targetUserId == callingUserId) return;
@@ -232,7 +233,12 @@
             try {
                 UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
                 if (callingUserInfo.isManagedProfile()) {
-                    throw new SecurityException(message + " for another profile " + targetUserId);
+                    // TODO: Make it SecurityException.  See b/34650921
+                    // throw new SecurityException(message + " for another profile " + targetUserId);
+
+                    // TODO: Report caller package name.
+                    Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile "
+                            + targetUserId + " from " + callingUserId);
                 }
 
                 UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
@@ -281,9 +287,10 @@
         }
 
         @Override
-        public ParceledListSlice<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
+        public ParceledListSlice<ResolveInfo> getLauncherActivities(String callingPackage,
+                String packageName, UserHandle user)
                 throws RemoteException {
-            return queryActivitiesForUser(
+            return queryActivitiesForUser(callingPackage,
                     new Intent(Intent.ACTION_MAIN)
                             .addCategory(Intent.CATEGORY_LAUNCHER)
                             .setPackage(packageName),
@@ -291,9 +298,10 @@
         }
 
         @Override
-        public ActivityInfo resolveActivity(ComponentName component, UserHandle user)
+        public ActivityInfo resolveActivity(
+                String callingPackage, ComponentName component, UserHandle user)
                 throws RemoteException {
-            ensureInUserProfiles(user, "Cannot resolve activity");
+            ensureInUserProfiles(callingPackage, user, "Cannot resolve activity");
             if (!isUserEnabled(user)) {
                 return null;
             }
@@ -311,15 +319,16 @@
         }
 
         @Override
-        public ParceledListSlice getShortcutConfigActivities(String packageName, UserHandle user)
+        public ParceledListSlice getShortcutConfigActivities(
+                String callingPackage, String packageName, UserHandle user)
                 throws RemoteException {
-            return queryActivitiesForUser(
+            return queryActivitiesForUser(callingPackage,
                     new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName), user);
         }
 
-        private ParceledListSlice<ResolveInfo> queryActivitiesForUser(Intent intent,
-                UserHandle user) {
-            ensureInUserProfiles(user, "Cannot retrieve activities");
+        private ParceledListSlice<ResolveInfo> queryActivitiesForUser(String callingPackage,
+                Intent intent, UserHandle user) {
+            ensureInUserProfiles(callingPackage, user, "Cannot retrieve activities");
             if (!isUserEnabled(user)) {
                 return null;
             }
@@ -358,9 +367,9 @@
         }
 
         @Override
-        public boolean isPackageEnabled(String packageName, UserHandle user)
+        public boolean isPackageEnabled(String callingPackage, String packageName, UserHandle user)
                 throws RemoteException {
-            ensureInUserProfiles(user, "Cannot check package");
+            ensureInUserProfiles(callingPackage, user, "Cannot check package");
             if (!isUserEnabled(user)) {
                 return false;
             }
@@ -379,9 +388,10 @@
         }
 
         @Override
-        public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user)
+        public ApplicationInfo getApplicationInfo(
+                String callingPackage, String packageName, int flags, UserHandle user)
                 throws RemoteException {
-            ensureInUserProfiles(user, "Cannot check package");
+            ensureInUserProfiles(callingPackage, user, "Cannot check package");
             if (!isUserEnabled(user)) {
                 return null;
             }
@@ -403,7 +413,7 @@
 
         private void ensureShortcutPermission(@NonNull String callingPackage, int userId) {
             verifyCallingPackage(callingPackage);
-            ensureInUserProfiles(userId, "Cannot access shortcuts");
+            ensureInUserProfiles(callingPackage, userId, "Cannot access shortcuts");
 
             if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
                     callingPackage)) {
@@ -479,7 +489,7 @@
         public boolean startShortcut(String callingPackage, String packageName, String shortcutId,
                 Rect sourceBounds, Bundle startActivityOptions, int userId) {
             verifyCallingPackage(callingPackage);
-            ensureInUserProfiles(userId, "Cannot start activity");
+            ensureInUserProfiles(callingPackage, userId, "Cannot start activity");
 
             if (!isUserEnabled(userId)) {
                 throw new IllegalStateException("Cannot start a shortcut for disabled profile "
@@ -530,9 +540,10 @@
         }
 
         @Override
-        public boolean isActivityEnabled(ComponentName component, UserHandle user)
+        public boolean isActivityEnabled(
+                String callingPackage, ComponentName component, UserHandle user)
                 throws RemoteException {
-            ensureInUserProfiles(user, "Cannot check component");
+            ensureInUserProfiles(callingPackage , user, "Cannot check component");
             if (!isUserEnabled(user)) {
                 return false;
             }
@@ -551,9 +562,10 @@
         }
 
         @Override
-        public void startActivityAsUser(ComponentName component, Rect sourceBounds,
+        public void startActivityAsUser(String callingPackage,
+                ComponentName component, Rect sourceBounds,
                 Bundle opts, UserHandle user) throws RemoteException {
-            ensureInUserProfiles(user, "Cannot start activity");
+            ensureInUserProfiles(callingPackage, user, "Cannot start activity");
             if (!isUserEnabled(user)) {
                 throw new IllegalStateException("Cannot start activity for disabled profile "  + user);
             }
@@ -604,9 +616,9 @@
         }
 
         @Override
-        public void showAppDetailsAsUser(ComponentName component, Rect sourceBounds,
-                Bundle opts, UserHandle user) throws RemoteException {
-            ensureInUserProfiles(user, "Cannot show app details");
+        public void showAppDetailsAsUser(String callingPackage, ComponentName component,
+                Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException {
+            ensureInUserProfiles(callingPackage, user, "Cannot show app details");
             if (!isUserEnabled(user)) {
                 throw new IllegalStateException("Cannot show app details for disabled profile "
                         + user);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d1aed3e..067a136 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -894,7 +894,7 @@
 
         // This is kind of hacky; we're creating a half-parsed package that is
         // straddled between the inherited and staged APKs.
-        final PackageLite pkg = new PackageLite(null, baseApk, null,
+        final PackageLite pkg = new PackageLite(null, baseApk, null, null,
                 splitPaths.toArray(new String[splitPaths.size()]), null);
         final boolean isForwardLocked =
                 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 63a5d14..9899cd4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10101,8 +10101,10 @@
                 a.info.packageName = pkg.applicationInfo.packageName;
                 a.info.sourceDir = pkg.applicationInfo.sourceDir;
                 a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir;
+                a.info.splitNames = pkg.splitNames;
                 a.info.splitSourceDirs = pkg.applicationInfo.splitSourceDirs;
                 a.info.splitPublicSourceDirs = pkg.applicationInfo.splitPublicSourceDirs;
+                a.info.splitDependencies = pkg.applicationInfo.splitDependencies;
                 a.info.dataDir = pkg.applicationInfo.dataDir;
                 a.info.deviceProtectedDataDir = pkg.applicationInfo.deviceProtectedDataDir;
                 a.info.credentialProtectedDataDir = pkg.applicationInfo.credentialProtectedDataDir;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index aa421b1..2f8d749 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -162,7 +162,7 @@
             if (file.isFile()) {
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null);
+                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
                     params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
                             pkgLite, false, params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
@@ -587,6 +587,7 @@
         boolean listSystem = false, listThirdParty = false;
         boolean listInstaller = false;
         boolean showUid = false;
+        boolean showVersionCode = false;
         int uid = -1;
         int userId = UserHandle.USER_SYSTEM;
         try {
@@ -620,6 +621,9 @@
                     case "-3":
                         listThirdParty = true;
                         break;
+                    case "--show-versioncode":
+                        showVersionCode = true;
+                        break;
                     case "--user":
                         userId = UserHandle.parseUserArg(getNextArgRequired());
                         break;
@@ -664,8 +668,11 @@
                     pw.print(info.applicationInfo.sourceDir);
                     pw.print("=");
                 }
-                pw.print(info.packageName); pw.print( " versionCode:"
-                        + info.applicationInfo.versionCode);
+                pw.print(info.packageName);
+                if (showVersionCode) {
+                    pw.print(" versionCode:");
+                    pw.print(info.applicationInfo.versionCode);
+                }
                 if (listInstaller) {
                     pw.print("  installer=");
                     pw.print(mInterface.getInstallerPackageName(info.packageName));
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1eb8b94..984120f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -33,12 +33,10 @@
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
@@ -2347,6 +2345,12 @@
     }
 
     @Override
+    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+        checkManageOrCreateUsersPermission("Only the system can remove users");
+        return removeUserUnchecked(userHandle);
+    }
+
+    @Override
     public UserInfo createUser(String name, int flags) {
         checkManageOrCreateUsersPermission(flags);
         return createUserInternal(name, flags, UserHandle.USER_NULL);
@@ -3133,8 +3137,6 @@
                 applyUserRestrictionsLR(userId);
             }
         }
-
-        maybeInitializeDemoMode(userId);
     }
 
     /**
@@ -3173,29 +3175,6 @@
         scheduleWriteUser(userData);
     }
 
-    private void maybeInitializeDemoMode(int userId) {
-        if (UserManager.isDeviceInDemoMode(mContext) && userId != UserHandle.USER_SYSTEM) {
-            String demoLauncher =
-                    mContext.getResources().getString(
-                            com.android.internal.R.string.config_demoModeLauncherComponent);
-            if (!TextUtils.isEmpty(demoLauncher)) {
-                ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher);
-                String demoLauncherPkg = componentToEnable.getPackageName();
-                try {
-                    final IPackageManager iPm = AppGlobals.getPackageManager();
-                    iPm.setComponentEnabledSetting(componentToEnable,
-                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
-                            /* userId= */ userId);
-                    iPm.setApplicationEnabledSetting(demoLauncherPkg,
-                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
-                            /* userId= */ userId, null);
-                } catch (RemoteException re) {
-                    // Internal, shouldn't happen
-                }
-            }
-        }
-    }
-
     /**
      * Returns the next available user id, filling in any holes in the ids.
      */
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c25ad46..4350ed9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3563,6 +3563,9 @@
                 dispatchDirectAudioEvent(event);
                 return -1;
             }
+        } else if (keyCode == KeyEvent.KEYCODE_TAB && event.isMetaPressed()) {
+            // Pass through keyboard navigation keys.
+            return 0;
         }
 
         // Toggle Caps Lock on META-ALT.
@@ -5896,9 +5899,8 @@
                                 && (result & ACTION_PASS_TO_USER) == 0) {
                             // If we are in call but we decided not to pass the key to
                             // the application, just pass it to the session service.
-
-                            MediaSessionLegacyHelper.getHelper(mContext)
-                                    .sendVolumeKeyEvent(event, false);
+                            MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+                                    event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                             break;
                         }
                     }
@@ -5911,8 +5913,8 @@
                     // If we aren't passing to the user and no one else
                     // handled it send it to the session manager to
                     // figure out.
-                    MediaSessionLegacyHelper.getHelper(mContext)
-                            .sendVolumeKeyEvent(event, true);
+                    MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(
+                            event, AudioManager.USE_DEFAULT_STREAM_TYPE, false);
                 }
                 break;
             }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e1b6c87..238866a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -57,6 +57,7 @@
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.util.EventLog;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -522,6 +523,65 @@
     // True if we are currently in VR Mode.
     private boolean mIsVrModeEnabled;
 
+    /**
+     * All times are in milliseconds. These constants are kept synchronized with the system
+     * global Settings. Any access to this class or its fields should be done while
+     * holding the PowerManagerService.mLock lock.
+     */
+    private final class Constants extends ContentObserver {
+        // Key names stored in the settings value.
+        private static final String KEY_NO_CACHED_WAKE_LOCKS = "no_cached_wake_locks";
+
+        private static final boolean DEFAULT_NO_CACHED_WAKE_LOCKS = true;
+
+        // Prevent processes that are cached from holding wake locks?
+        public boolean NO_CACHED_WAKE_LOCKS = DEFAULT_NO_CACHED_WAKE_LOCKS;
+
+        private ContentResolver mResolver;
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+        public Constants(Handler handler) {
+            super(handler);
+        }
+
+        public void start(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.POWER_MANAGER_CONSTANTS), false, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            synchronized (mLock) {
+                try {
+                    mParser.setString(Settings.Global.getString(mResolver,
+                            Settings.Global.POWER_MANAGER_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad alarm manager settings", e);
+                }
+
+                NO_CACHED_WAKE_LOCKS = mParser.getBoolean(KEY_NO_CACHED_WAKE_LOCKS,
+                        DEFAULT_NO_CACHED_WAKE_LOCKS);
+            }
+        }
+
+        void dump(PrintWriter pw) {
+            pw.println("  Settings " + Settings.Global.POWER_MANAGER_CONSTANTS + ":");
+
+            pw.print("    "); pw.print(KEY_NO_CACHED_WAKE_LOCKS); pw.print("=");
+            pw.println(NO_CACHED_WAKE_LOCKS);
+        }
+    }
+
+    final Constants mConstants;
+
     private native void nativeInit();
 
     private static native void nativeAcquireSuspendBlocker(String name);
@@ -538,6 +598,7 @@
                 Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
         mHandlerThread.start();
         mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
+        mConstants = new Constants(mHandler);
 
         synchronized (mLock) {
             mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -616,6 +677,9 @@
                     mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"),
                     mPolicy);
 
+            final ContentResolver resolver = mContext.getContentResolver();
+            mConstants.start(resolver);
+
             mWirelessChargerDetector = new WirelessChargerDetector(sensorManager,
                     createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
                     mHandler);
@@ -629,7 +693,6 @@
                     mDisplayPowerCallbacks, mHandler, sensorManager);
 
             // Register for settings changes.
-            final ContentResolver resolver = mContext.getContentResolver();
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.SCREENSAVER_ENABLED),
                     false, mSettingsObserver, UserHandle.USER_ALL);
@@ -2795,7 +2858,7 @@
                             state.mProcState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
                         disabled = true;
                     }
-                } else {
+                } else if (mConstants.NO_CACHED_WAKE_LOCKS) {
                     disabled = !wakeLock.mUidState.mActive &&
                             wakeLock.mUidState.mProcState
                                     != ActivityManager.PROCESS_STATE_NONEXISTENT &&
@@ -3005,6 +3068,7 @@
         final WirelessChargerDetector wcd;
         synchronized (mLock) {
             pw.println("Power Manager State:");
+            mConstants.dump(pw);
             pw.println("  mDirty=0x" + Integer.toHexString(mDirty));
             pw.println("  mWakefulness=" + PowerManagerInternal.wakefulnessToString(mWakefulness));
             pw.println("  mWakefulnessChanging=" + mWakefulnessChanging);
diff --git a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
index 22299df..0094ab5 100644
--- a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
+++ b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
@@ -18,6 +18,7 @@
 
 import android.content.pm.PackageStats;
 import android.os.Environment;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -118,7 +119,7 @@
         long appSizeSum = 0L;
         long cacheSizeSum = 0L;
         boolean isExternal = Environment.isExternalStorageEmulated();
-        for (Map.Entry<String, PackageStats> entry : mergePackagesAcrossUsers().entrySet()) {
+        for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
             PackageStats stat = entry.getValue();
             long appSize = stat.codeSize + stat.dataSize;
             long cacheSize = stat.cacheSize;
@@ -141,14 +142,17 @@
     }
 
     /**
-     * A given package may exist for multiple users with distinct sizes. This function merges
-     * the duplicated packages together and sums up their sizes to get the actual totals for the
-     * package.
+     * A given package may exist for multiple users with distinct sizes. This function filters
+     * the packages that do not belong to user 0 out to ensure that we get good stats for a subset.
      * @return A mapping of package name to merged package stats.
      */
-    private ArrayMap<String, PackageStats> mergePackagesAcrossUsers() {
+    private ArrayMap<String, PackageStats> filterOnlyPrimaryUser() {
         ArrayMap<String, PackageStats> packageMap = new ArrayMap<>();
         for (PackageStats stat : mPackageStats) {
+            if (stat.userHandle != UserHandle.USER_SYSTEM) {
+                continue;
+            }
+
             PackageStats existingStats = packageMap.get(stat.packageName);
             if (existingStats != null) {
                 existingStats.cacheSize += stat.cacheSize;
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 4fd51b2..a0381f7 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -294,6 +294,11 @@
         WebViewZygote.setMultiprocessEnabled(enableMultiProcess);
     }
 
+    @Override
+    public boolean isMultiProcessDefaultEnabled() {
+        return true;
+    }
+
     // flags declaring we want extra info from the package manager for webview providers
     private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
             | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java
index b06f829..d57edcd 100644
--- a/services/core/java/com/android/server/webkit/SystemInterface.java
+++ b/services/core/java/com/android/server/webkit/SystemInterface.java
@@ -64,4 +64,5 @@
     public int getMultiProcessSetting(Context context);
     public void setMultiProcessSetting(Context context, int value);
     public void notifyZygote(boolean enableMultiProcess);
+    public boolean isMultiProcessDefaultEnabled();
 }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index f016830..fedd55a9 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -240,12 +240,31 @@
     }
 
     boolean isMultiProcessEnabled() {
-        return mSystemInterface.getMultiProcessSetting(mContext) != 0;
+        PackageInfo current = getCurrentWebViewPackage();
+        if (current == null) return false;
+        int currentVersion = current.versionCode;
+        int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
+        if (mSystemInterface.isMultiProcessDefaultEnabled()) {
+            // Multiprocess should be enabled unless the user has turned it off manually for this
+            // version or newer, as we want to re-enable it when it's updated to get fresh
+            // bug reports.
+            return settingValue > -currentVersion;
+        } else {
+            // Multiprocess should not be enabled, unless the user has turned it on manually for
+            // any version.
+            return settingValue > 0;
+        }
     }
 
     void enableMultiProcess(boolean enable) {
+        // The value we store for the setting is the version code of the current package, if it's
+        // enabled, or the negation of the version code of the current package, if it's disabled.
+        // Users who have a setting from before this scheme was implemented will have it set to 0 or
+        // 1 instead.
         PackageInfo current = getCurrentWebViewPackage();
-        mSystemInterface.setMultiProcessSetting(mContext, enable ? 1 : 0);
+        int currentVersion = current != null ? current.versionCode : 1;
+        mSystemInterface.setMultiProcessSetting(mContext,
+                                                enable ? currentVersion : -currentVersion);
         mSystemInterface.notifyZygote(enable);
         if (current != null) {
             mSystemInterface.killPackageDependents(current.packageName);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 914cc8d..3a74ded 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -57,6 +57,7 @@
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
@@ -69,6 +70,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
@@ -634,6 +636,13 @@
      */
     DisplayContent(Display display, WindowManagerService service,
             WindowLayersController layersController, WallpaperController wallpaperController) {
+
+        if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
+            throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+                    + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
+                    + " new=" + display);
+        }
+
         mDisplay = display;
         mDisplayId = display.getDisplayId();
         mLayersController = layersController;
@@ -957,75 +966,43 @@
         out.set(mContentRect);
     }
 
-    /**
-     * Adds the stack to this display.
-     * @see WindowManagerService#addStackToDisplay(int, int, boolean)
-     */
-    Rect addStackToDisplay(int stackId, boolean onTop) {
-        boolean attachedToDisplay = false;
-        TaskStack stack = mService.mStackIdToStack.get(stackId);
-        if (stack == null) {
-            if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
-                    + mDisplayId);
+    TaskStack addStackToDisplay(int stackId, boolean onTop) {
+        if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+                + mDisplayId);
 
-            stack = getStackById(stackId);
-            if (stack != null) {
-                // It's already attached to the display...clear mDeferRemoval and move stack to
-                // appropriate z-order on display as needed.
-                stack.mDeferRemoval = false;
-                // We're not moving the display to front when we're adding stacks, only when
-                // requested to change the position of stack explicitly.
-                mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
-                        false /* includingParents */);
-                attachedToDisplay = true;
-            } else {
-                stack = new TaskStack(mService, stackId);
-            }
-
-            mService.mStackIdToStack.put(stackId, stack);
-            if (stackId == DOCKED_STACK_ID) {
-                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
-            }
+        TaskStack stack = getStackById(stackId);
+        if (stack != null) {
+            // It's already attached to the display...clear mDeferRemoval and move stack to
+            // appropriate z-order on display as needed.
+            stack.mDeferRemoval = false;
+            // We're not moving the display to front when we're adding stacks, only when
+            // requested to change the position of stack explicitly.
+            mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
+                    false /* includingParents */);
         } else {
-            final DisplayContent currentDC = stack.getDisplayContent();
-            if (currentDC != null) {
-                throw new IllegalStateException("Trying to add stackId=" + stackId
-                        + "to displayId=" + mDisplayId + ", but it's already attached to displayId="
-                        + currentDC.getDisplayId());
-            }
-        }
-
-        if (!attachedToDisplay) {
+            stack = new TaskStack(mService, stackId);
             mTaskStackContainers.addStackToDisplay(stack, onTop);
         }
 
-        if (stack.getRawFullscreen()) {
-            return null;
+        if (stackId == DOCKED_STACK_ID) {
+            mDividerControllerLocked.notifyDockedStackExistsChanged(true);
         }
-        final Rect bounds = new Rect();
-        stack.getRawBounds(bounds);
-        return bounds;
+        return stack;
     }
 
-    /** Removes the stack from the display and prepares for changing the parent. */
-    private void removeStackFromDisplay(TaskStack stack) {
-        mTaskStackContainers.removeStackFromDisplay(stack);
-    }
-
-    /** Moves the stack to this display and returns the updated bounds. */
-    Rect moveStackToDisplay(TaskStack stack) {
-        final DisplayContent currentDisplayContent = stack.getDisplayContent();
-        if (currentDisplayContent == null) {
+    void moveStackToDisplay(TaskStack stack) {
+        final DisplayContent prevDc = stack.getDisplayContent();
+        if (prevDc == null) {
             throw new IllegalStateException("Trying to move stackId=" + stack.mStackId
                     + " which is not currently attached to any display");
         }
-        if (stack.getDisplayContent().getDisplayId() == mDisplayId) {
+        if (prevDc.getDisplayId() == mDisplayId) {
             throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId
                     + " to its current displayId=" + mDisplayId);
         }
 
-        currentDisplayContent.removeStackFromDisplay(stack);
-        return addStackToDisplay(stack.mStackId, true /* onTop */);
+        prevDc.mTaskStackContainers.removeStackFromDisplay(stack);
+        mTaskStackContainers.addStackToDisplay(stack, true /* onTop */);
     }
 
     @Override
@@ -1172,7 +1149,7 @@
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             mDimLayerController.close();
-            if (mDisplayId == DEFAULT_DISPLAY) {
+            if (mDisplayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) {
                 mService.unregisterPointerEventListener(mTapDetector);
                 mService.unregisterPointerEventListener(mService.mMousePositionTracker);
             }
@@ -1250,7 +1227,7 @@
         final WindowState imeWin = mService.mInputMethodWindow;
         final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
                 && !mDividerControllerLocked.isImeHideRequested();
-        final boolean dockVisible = mService.isStackVisibleLocked(DOCKED_STACK_ID);
+        final boolean dockVisible = isStackVisible(DOCKED_STACK_ID);
         final TaskStack imeTargetStack = mService.getImeFocusStackLocked();
         final int imeDockSide = (dockVisible && imeTargetStack != null) ?
                 imeTargetStack.getDockSide() : DOCKED_INVALID;
@@ -1447,7 +1424,7 @@
      * @return The docked stack, but only if it is visible, and {@code null} otherwise.
      */
     TaskStack getDockedStackLocked() {
-        final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack stack = getStackById(DOCKED_STACK_ID);
         return (stack != null && stack.isVisible()) ? stack : null;
     }
 
@@ -1456,7 +1433,7 @@
      * visible.
      */
     TaskStack getDockedStackIgnoringVisibility() {
-        return mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        return getStackById(DOCKED_STACK_ID);
     }
 
     /** Find the visible, touch-deliverable window under the given point */
@@ -1532,6 +1509,9 @@
         }
     }
 
+    // TODO: This should probably be called any time a visual change is made to the hierarchy like
+    // moving containers or resizing them. Need to investigate the best way to have it automatically
+    // happen so we don't run into issues with programmers forgetting to do it.
     void layoutAndAssignWindowLayersIfNeeded() {
         mService.mWindowsChanged = true;
         setLayoutNeeded();
@@ -2441,6 +2421,27 @@
         }
     }
 
+    void setExitingTokensHasVisible(boolean hasVisible) {
+        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+            mExitingTokens.get(i).hasVisible = hasVisible;
+        }
+
+        // Initialize state of exiting applications.
+        mTaskStackContainers.setExitingTokensHasVisible(hasVisible);
+    }
+
+    void removeExistingTokensIfPossible() {
+        for (int i = mExitingTokens.size() - 1; i >= 0; i--) {
+            final WindowToken token = mExitingTokens.get(i);
+            if (!token.hasVisible) {
+                mExitingTokens.remove(i);
+            }
+        }
+
+        // Time to remove any exiting applications?
+        mTaskStackContainers.removeExistingAppTokensIfPossible();
+    }
+
     static final class TaskForResizePointSearchResult {
         boolean searchDone;
         Task taskForResize;
@@ -2650,10 +2651,38 @@
             return false;
         }
 
+        void setExitingTokensHasVisible(boolean hasVisible) {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                for (int j = appTokens.size() - 1; j >= 0; --j) {
+                    appTokens.get(j).hasVisible = hasVisible;
+                }
+            }
+        }
+
+        void removeExistingAppTokensIfPossible() {
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+                for (int j = appTokens.size() - 1; j >= 0; --j) {
+                    final AppWindowToken token = appTokens.get(j);
+                    if (!token.hasVisible && !mService.mClosingApps.contains(token)
+                            && (!token.mIsExiting || token.isEmpty())) {
+                        // Make sure there is no animation running on this token, so any windows
+                        // associated with it will be removed as soon as their animations are
+                        // complete.
+                        token.mAppAnimator.clearAnimation();
+                        token.mAppAnimator.animating = false;
+                        if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+                                "performLayout: App token exiting now removed" + token);
+                        token.removeIfPossible();
+                    }
+                }
+            }
+        }
+
         @Override
         int getOrientation() {
-            if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
-                    || mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+            if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
                 // Apps and their containers are not allowed to specify an orientation while the
                 // docked or freeform stack is visible...except for the home stack/task if the
                 // docked stack is minimized and it actually set something.
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index ed1e2d9..aaf724e 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -457,8 +457,7 @@
     void registerDockedStackListener(IDockedStackListener listener) {
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
-        notifyDockedStackExistsChanged(
-                mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID) != null);
+        notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
         notifyDockedStackMinimizedChanged(mMinimizedDock, 0 /* animDuration */);
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
 
@@ -466,7 +465,7 @@
 
     void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
         mService.openSurfaceTransaction();
-        final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId);
+        final TaskStack stack = mDisplayContent.getStackById(targetStackId);
         final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
@@ -544,8 +543,8 @@
         if (homeTask == null || !isWithinDisplay(homeTask)) {
             return;
         }
-        final TaskStack fullscreenStack
-                = mService.mStackIdToStack.get(FULLSCREEN_WORKSPACE_STACK_ID);
+        final TaskStack fullscreenStack =
+                mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
         final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
         final boolean homeBehind = (fullscreenStack != null && fullscreenStack.isVisible())
                 || (homeStack.hasMultipleTaskWithHomeTaskNotTop());
@@ -739,7 +738,7 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 0cc6c70..22abf30 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -382,6 +382,17 @@
         }
     }
 
+    TaskStack getStackById(int stackId) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final DisplayContent dc = mChildren.get(i);
+            final TaskStack stack = dc.getStackById(stackId);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
     void setSecureSurfaceState(int userId, boolean disabled) {
         forAllWindows((w) -> {
             if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
@@ -535,18 +546,7 @@
         final int numDisplays = mChildren.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
-            for (i = displayContent.mExitingTokens.size() - 1; i >= 0; i--) {
-                displayContent.mExitingTokens.get(i).hasVisible = false;
-            }
-        }
-
-        for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) {
-            // Initialize state of exiting applications.
-            final AppTokenList exitingAppTokens =
-                    mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens;
-            for (int tokenNdx = exitingAppTokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
-                exitingAppTokens.get(tokenNdx).hasVisible = false;
-            }
+            displayContent.setExitingTokensHasVisible(false);
         }
 
         mHoldScreen = null;
@@ -681,33 +681,7 @@
         // Time to remove any exiting tokens?
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
             final DisplayContent displayContent = mChildren.get(displayNdx);
-            ArrayList<WindowToken> exitingTokens = displayContent.mExitingTokens;
-            for (i = exitingTokens.size() - 1; i >= 0; i--) {
-                WindowToken token = exitingTokens.get(i);
-                if (!token.hasVisible) {
-                    exitingTokens.remove(i);
-                }
-            }
-        }
-
-        // Time to remove any exiting applications?
-        for (int stackNdx = mService.mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) {
-            // Initialize state of exiting applications.
-            final AppTokenList exitingAppTokens =
-                    mService.mStackIdToStack.valueAt(stackNdx).mExitingAppTokens;
-            for (i = exitingAppTokens.size() - 1; i >= 0; i--) {
-                final AppWindowToken token = exitingAppTokens.get(i);
-                if (!token.hasVisible && !mService.mClosingApps.contains(token) &&
-                        (!token.mIsExiting || token.isEmpty())) {
-                    // Make sure there is no animation running on this token, so any windows
-                    // associated with it will be removed as soon as their animations are complete
-                    token.mAppAnimator.clearAnimation();
-                    token.mAppAnimator.animating = false;
-                    if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
-                            "performLayout: App token exiting now removed" + token);
-                    token.removeIfPossible();
-                }
-            }
+            displayContent.removeExistingTokensIfPossible();
         }
 
         if (wallpaperDestroyed) {
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
new file mode 100644
index 0000000..9a6f3eb5
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import android.app.RemoteAction;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.util.SparseArray;
+import com.android.server.UiThread;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+/**
+ * Controller for the stack container. This is created by activity manager to link activity stacks
+ * to the stack container they use in window manager.
+ *
+ * Test class: {@link StackWindowControllerTests}
+ */
+public class StackWindowController
+        extends WindowContainerController<TaskStack, StackWindowListener> {
+
+    final int mStackId;
+
+    private final H mHandler;
+
+    public StackWindowController(int stackId, StackWindowListener listener,
+            int displayId, boolean onTop, Rect outBounds) {
+        this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+    }
+
+    @VisibleForTesting
+    public StackWindowController(int stackId, StackWindowListener listener,
+            int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+        super(listener, service);
+        mStackId = stackId;
+        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
+
+        synchronized (mWindowMap) {
+            final DisplayContent dc = mRoot.getDisplayContent(displayId);
+            if (dc == null) {
+                throw new IllegalArgumentException("Trying to add stackId=" + stackId
+                        + " to unknown displayId=" + displayId);
+            }
+
+            final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
+            stack.setController(this);
+            getRawBounds(outBounds);
+        }
+    }
+
+    @Override
+    public void removeContainer() {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.removeIfPossible();
+                super.removeContainer();
+            }
+        }
+    }
+
+    public void reparent(int displayId, Rect outStackBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
+                        + " to displayId=" + displayId);
+            }
+
+            final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
+            if (targetDc == null) {
+                throw new IllegalArgumentException("Trying to move stackId=" + mStackId
+                        + " to unknown displayId=" + displayId);
+            }
+
+            targetDc.moveStackToDisplay(mContainer);
+            getRawBounds(outStackBounds);
+        }
+    }
+
+    public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
+            Configuration overrideConfig) {
+        synchronized (mWindowMap) {
+            if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
+                    + " at " + position);
+            if (child.mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM,
+                        "positionChildAt: could not find task=" + this);
+                return;
+            }
+            if (mContainer == null) {
+                if (DEBUG_STACK) Slog.i(TAG_WM,
+                        "positionChildAt: could not find stack for task=" + mContainer);
+                return;
+            }
+            child.mContainer.positionAt(position, bounds, overrideConfig);
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
+        if (child == null) {
+            // TODO: Fix the call-points that cause this to happen.
+            return;
+        }
+
+        synchronized(mWindowMap) {
+            final Task childTask = child.mContainer;
+            if (childTask == null) {
+                Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
+                return;
+            }
+            mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
+
+            if (mService.mAppTransition.isTransitionSet()) {
+                childTask.setSendingToBottom(false);
+            }
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    public void positionChildAtBottom(TaskWindowContainerController child) {
+        if (child == null) {
+            // TODO: Fix the call-points that cause this to happen.
+            return;
+        }
+
+        synchronized(mWindowMap) {
+            final Task childTask = child.mContainer;
+            if (childTask == null) {
+                Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
+                return;
+            }
+            mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+
+            if (mService.mAppTransition.isTransitionSet()) {
+                childTask.setSendingToBottom(true);
+            }
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
+        }
+    }
+
+    /**
+     * Re-sizes a stack and its containing tasks.
+     *
+     * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
+     * @param configs Configurations for tasks in the resized stack, keyed by task id.
+     * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
+     * @return True if the stack is now fullscreen.
+     */
+    public boolean resize(Rect bounds, SparseArray<Configuration> configs,
+            SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
+            }
+            // We might trigger a configuration change. Save the current task bounds for freezing.
+            mContainer.prepareFreezingTaskBounds();
+            if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+                    && mContainer.isVisible()) {
+                mContainer.getDisplayContent().setLayoutNeeded();
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            }
+            return mContainer.getRawFullscreen();
+        }
+    }
+
+    // TODO: This and similar methods should be separated into PinnedStackWindowContainerController
+    public void animateResizePinnedStack(final Rect bounds, final int animationDuration) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("Pinned stack container not found :(");
+            }
+            final Rect originalBounds = new Rect();
+            mContainer.getBounds(originalBounds);
+            mContainer.setAnimatingBounds(bounds);
+            UiThread.getHandler().post(new Runnable() {
+                @Override
+                public void run() {
+                    mService.mBoundsAnimationController.animateBounds(
+                            mContainer, originalBounds, bounds, animationDuration);
+                }
+            });
+        }
+    }
+
+    /** Sets the current picture-in-picture aspect ratio. */
+    public void setPictureInPictureAspectRatio(float aspectRatio) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return;
+            }
+
+            final int displayId = mContainer.getDisplayContent().getDisplayId();
+            final Rect toBounds = mService.getPictureInPictureBounds(displayId, aspectRatio);
+            animateResizePinnedStack(toBounds, -1 /* duration */);
+        }
+    }
+
+    public void getStackDockedModeBounds(Rect outBounds, boolean ignoreVisibility) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.getStackDockedModeBoundsLocked(outBounds, ignoreVisibility);
+                return;
+            }
+            outBounds.setEmpty();
+        }
+    }
+
+    public void prepareFreezingTaskBounds() {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
+                        + " not found.");
+            }
+            mContainer.prepareFreezingTaskBounds();
+        }
+    }
+
+    /** Sets the current picture-in-picture actions. */
+    public void setPictureInPictureActions(List<RemoteAction> actions) {
+        synchronized (mWindowMap) {
+            if (!mService.mSupportsPictureInPicture || mContainer == null) {
+                return;
+            }
+
+            mContainer.getDisplayContent().getPinnedStackController().setActions(actions);
+        }
+    }
+
+    private void getRawBounds(Rect outBounds) {
+        if (mContainer.getRawFullscreen()) {
+            outBounds.setEmpty();
+        } else {
+            mContainer.getRawBounds(outBounds);
+        }
+    }
+
+    public void getBounds(Rect outBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer != null) {
+                mContainer.getBounds(outBounds);
+                return;
+            }
+            outBounds.setEmpty();
+        }
+    }
+
+    public Rect getBoundsForNewConfiguration() {
+        synchronized(mWindowMap) {
+            final Rect outBounds = new Rect();
+            mContainer.getBoundsForNewConfiguration(outBounds);
+            return outBounds;
+        }
+    }
+
+    void requestResize(Rect bounds) {
+        mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
+    }
+
+    @Override
+    public String toString() {
+        return "{StackWindowController stackId=" + mStackId + "}";
+    }
+
+    private static final class H extends Handler {
+
+        static final int REQUEST_RESIZE = 0;
+
+        private final WeakReference<StackWindowController> mController;
+
+        H(WeakReference<StackWindowController> controller, Looper looper) {
+            super(looper);
+            mController = controller;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final StackWindowController controller = mController.get();
+            final StackWindowListener listener = (controller != null)
+                    ? controller.mListener : null;
+            if (listener == null) {
+                return;
+            }
+            switch (msg.what) {
+                case REQUEST_RESIZE:
+                    listener.requestResize((Rect) msg.obj);
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/StackWindowListener.java b/services/core/java/com/android/server/wm/StackWindowListener.java
new file mode 100644
index 0000000..c763c17
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackWindowListener.java
@@ -0,0 +1,29 @@
+/*
+ * 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.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Interface used by the creator of {@link StackWindowController} to listen to changes with
+ * the stack container.
+ */
+public interface StackWindowListener extends WindowContainerListener {
+
+    /** Called when the stack container would like its controller to resize. */
+    void requestResize(Rect bounds);
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3a3ec71..d96e1ef 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -27,7 +27,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
 
 import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskDescription;
@@ -82,6 +81,10 @@
     // Resize mode of the task. See {@link ActivityInfo#resizeMode}
     private int mResizeMode;
 
+    // Whether the task supports picture-in-picture.
+    // See {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE}
+    private boolean mSupportsPictureInPicture;
+
     // Whether the task is currently being drag-resized
     private boolean mDragResizing;
     private int mDragResizeMode;
@@ -94,14 +97,16 @@
     private TaskDescription mTaskDescription;
 
     Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
-            Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode, boolean homeTask,
-            TaskDescription taskDescription, TaskWindowContainerController controller) {
+            Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
+            boolean supportsPictureInPicture, boolean homeTask, TaskDescription taskDescription,
+            TaskWindowContainerController controller) {
         mTaskId = taskId;
         mStack = stack;
         mUserId = userId;
         mService = service;
         mIsOnTopLauncher = isOnTopLauncher;
         mResizeMode = resizeMode;
+        mSupportsPictureInPicture = supportsPictureInPicture;
         mHomeTask = homeTask;
         setController(controller);
         setBounds(bounds, overrideConfig);
@@ -327,7 +332,8 @@
     }
 
     boolean isResizeable() {
-        return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks;
+        return ActivityInfo.isResizeableMode(mResizeMode) || mSupportsPictureInPicture
+                || mService.mForceResizableTasks;
     }
 
     /**
@@ -560,11 +566,10 @@
 
         displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
-            // Post message to inform activity manager of the bounds change simulating a one-way
-            // call. We do this to prevent a deadlock between window manager lock and activity
-            // manager lock been held.
-            mService.mH.obtainMessage(RESIZE_TASK, mTaskId,
-                    RESIZE_MODE_SYSTEM_SCREEN_ROTATION, mBounds).sendToTarget();
+            final TaskWindowContainerController controller = getController();
+            if (controller != null) {
+                controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 53292bb..d3eae8c 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -35,17 +35,12 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.RESIZE_STACK;
 import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.app.ActivityManager.StackId;
-import android.app.IActivityManager;
 import android.content.res.Configuration;
-import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Debug;
 import android.os.RemoteException;
 import android.util.EventLog;
 import android.util.Slog;
@@ -53,11 +48,9 @@
 import android.view.DisplayInfo;
 import android.view.Surface;
 
-import android.view.WindowManagerPolicy;
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.internal.policy.DockedDividerUtils;
-import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
@@ -206,14 +199,6 @@
         }
     }
 
-    boolean isFullscreenBounds(Rect bounds) {
-        if (mDisplayContent == null || bounds == null) {
-            return true;
-        }
-        mDisplayContent.getLogicalDisplayRect(mTmpRect);
-        return mTmpRect.equals(bounds);
-    }
-
     /**
      * Overrides the adjusted bounds, i.e. sets temporary layout bounds which are different from
      * the normal task bounds.
@@ -658,7 +643,7 @@
 
         final Rect oldBounds = new Rect(mBounds);
         Rect bounds = null;
-        final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
         if (mStackId == DOCKED_STACK_ID
                 || (dockedStack != null && StackId.isResizeableByDockedStack(mStackId)
                         && !dockedStack.fillsParent())) {
@@ -697,7 +682,7 @@
             return;
         }
 
-        final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility();
         if (dockedStack == null) {
             // Not sure why you are calling this method when there is no docked stack...
             throw new IllegalStateException(
@@ -805,8 +790,12 @@
 
         final Rect bounds = new Rect();
         getStackDockedModeBoundsLocked(bounds, true /*ignoreVisibility*/);
-        mService.mH.obtainMessage(RESIZE_STACK, DOCKED_STACK_ID,
-                1 /*allowResizeInDockedMode*/, bounds).sendToTarget();
+        getController().requestResize(bounds);
+    }
+
+    @Override
+    StackWindowController getController() {
+        return (StackWindowController) super.getController();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 61a2cd9..11667c0 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -27,6 +27,8 @@
 import android.util.Slog;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.ref.WeakReference;
+
 import static com.android.server.EventLogTags.WM_TASK_CREATED;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
@@ -43,45 +45,40 @@
 public class TaskWindowContainerController
         extends WindowContainerController<Task, TaskWindowContainerListener> {
 
-    private static final int REPORT_SNAPSHOT_CHANGED = 0;
-
     private final int mTaskId;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case REPORT_SNAPSHOT_CHANGED:
-                    if (mListener != null) {
-                        mListener.onSnapshotChanged((TaskSnapshot) msg.obj);
-                    }
-                    break;
-            }
-        }
-    };
+    private final H mHandler;
 
     public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
-            int stackId, int userId, Rect bounds, Configuration overrideConfig, int resizeMode,
+            StackWindowController stackController, int userId, Rect bounds,
+            Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
             boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
             TaskDescription taskDescription) {
-        super(listener, WindowManagerService.getInstance());
+        this(taskId, listener, stackController, userId, bounds, overrideConfig, resizeMode,
+                supportsPictureInPicture, homeTask, isOnTopLauncher, toTop, showForAllUsers,
+                taskDescription, WindowManagerService.getInstance());
+    }
+
+    public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
+            StackWindowController stackController, int userId, Rect bounds,
+            Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+            boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+            TaskDescription taskDescription, WindowManagerService service) {
+        super(listener, service);
         mTaskId = taskId;
+        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
 
         synchronized(mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
-                    + " stackId=" + stackId + " bounds=" + bounds);
+                    + " stack=" + stackController + " bounds=" + bounds);
 
-            // TODO: Pass controller for the stack to get the container object when stack is
-            // switched to use controller.
-            final TaskStack stack = mService.mStackIdToStack.get(stackId);
+            final TaskStack stack = stackController.mContainer;
             if (stack == null) {
-                throw new IllegalArgumentException("TaskWindowContainerController: invalid stackId="
-                        + stackId);
+                throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
+                        + stackController);
             }
-            EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
+            EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
             final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
-                    homeTask, isOnTopLauncher, taskDescription);
+                    supportsPictureInPicture, homeTask, isOnTopLauncher, taskDescription);
             final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
             stack.addTask(task, position, showForAllUsers, true /* moveParents */);
         }
@@ -89,10 +86,10 @@
 
     @VisibleForTesting
     Task createTask(int taskId, TaskStack stack, int userId, Rect bounds,
-            Configuration overrideConfig, int resizeMode, boolean homeTask,
-            boolean isOnTopLauncher, TaskDescription taskDescription) {
+            Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+            boolean homeTask, boolean isOnTopLauncher, TaskDescription taskDescription) {
         return new Task(taskId, stack, userId, mService, bounds, overrideConfig, isOnTopLauncher,
-                resizeMode, homeTask, taskDescription, this);
+                resizeMode, supportsPictureInPicture, homeTask, taskDescription, this);
     }
 
     @Override
@@ -124,21 +121,22 @@
         }
     }
 
-    public void reparent(int stackId, int position) {
+    public void reparent(StackWindowController stackController, int position) {
         synchronized (mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG_WM, "reparent: moving taskId=" + mTaskId
-                    + " to stackId=" + stackId + " at " + position);
+                    + " to stack=" + stackController + " at " + position);
             if (mContainer == null) {
                 if (DEBUG_STACK) Slog.i(TAG_WM,
                         "reparent: could not find taskId=" + mTaskId);
                 return;
             }
-            final TaskStack stack = mService.mStackIdToStack.get(stackId);
+            final TaskStack stack = stackController.mContainer;
             if (stack == null) {
-                throw new IllegalArgumentException("reparent: could not find stackId=" + stackId);
+                throw new IllegalArgumentException("reparent: could not find stack="
+                        + stackController);
             }
             mContainer.reparent(stack, position);
-            mService.mWindowPlacerLocked.performSurfacePlacement();
+            mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
         }
     }
 
@@ -158,68 +156,11 @@
             }
 
             if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) {
-                mContainer.getDisplayContent().setLayoutNeeded();
-                mService.mWindowPlacerLocked.performSurfacePlacement();
+                mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
             }
         }
     }
 
-    // TODO: Move to positionChildAt() in stack controller once we have a stack controller.
-    public void positionAt(int position, Rect bounds, Configuration overrideConfig) {
-        synchronized (mWindowMap) {
-            if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning taskId=" + mTaskId
-                    + " at " + position);
-            if (mContainer == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM,
-                        "positionAt: could not find taskId=" + mTaskId);
-                return;
-            }
-            final TaskStack stack = mContainer.mStack;
-            if (stack == null) {
-                if (DEBUG_STACK) Slog.i(TAG_WM,
-                        "positionAt: could not find stack for task=" + mContainer);
-                return;
-            }
-            mContainer.positionAt(position, bounds, overrideConfig);
-            final DisplayContent displayContent = stack.getDisplayContent();
-            displayContent.setLayoutNeeded();
-            mService.mWindowPlacerLocked.performSurfacePlacement();
-        }
-    }
-
-    // TODO: Replace with moveChildToTop in stack controller?
-    public void moveToTop(boolean includingParents) {
-        synchronized(mWindowMap) {
-            if (mContainer == null) {
-                Slog.e(TAG_WM, "moveToTop: taskId=" + mTaskId + " not found");
-                return;
-            }
-            final TaskStack stack = mContainer.mStack;
-            stack.positionChildAt(POSITION_TOP, mContainer, includingParents);
-
-            if (mService.mAppTransition.isTransitionSet()) {
-                mContainer.setSendingToBottom(false);
-            }
-            stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
-        }
-    }
-
-    // TODO: Replace with moveChildToBottom in stack controller?
-    public void moveToBottom() {
-        synchronized(mWindowMap) {
-            if (mContainer == null) {
-                Slog.e(TAG_WM, "moveTaskToBottom: taskId=" + mTaskId + " not found");
-                return;
-            }
-            final TaskStack stack = mContainer.mStack;
-            stack.positionChildAt(POSITION_BOTTOM, mContainer, false /* includingParents */);
-            if (mService.mAppTransition.isTransitionSet()) {
-                mContainer.setSendingToBottom(true);
-            }
-            stack.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
-        }
-    }
-
     public void getBounds(Rect bounds) {
         synchronized (mWindowMap) {
             if (mContainer != null) {
@@ -276,11 +217,46 @@
     }
 
     void reportSnapshotChanged(TaskSnapshot snapshot) {
-        mHandler.obtainMessage(REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+        mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget();
+    }
+
+    void requestResize(Rect bounds, int resizeMode) {
+        mHandler.obtainMessage(H.REQUEST_RESIZE, resizeMode, 0, bounds).sendToTarget();
     }
 
     @Override
     public String toString() {
         return "{TaskWindowContainerController taskId=" + mTaskId + "}";
     }
+
+    private static final class H extends Handler {
+
+        static final int REPORT_SNAPSHOT_CHANGED = 0;
+        static final int REQUEST_RESIZE = 1;
+
+        private final WeakReference<TaskWindowContainerController> mController;
+
+        H(WeakReference<TaskWindowContainerController> controller, Looper looper) {
+            super(looper);
+            mController = controller;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final TaskWindowContainerController controller = mController.get();
+            final TaskWindowContainerListener listener = (controller != null)
+                    ? controller.mListener : null;
+            if (listener == null) {
+                return;
+            }
+            switch (msg.what) {
+                case REPORT_SNAPSHOT_CHANGED:
+                    listener.onSnapshotChanged((TaskSnapshot) msg.obj);
+                    break;
+                case REQUEST_RESIZE:
+                    listener.requestResize((Rect) msg.obj, msg.arg1);
+                    break;
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
index 61b202d..af67de3 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerListener.java
@@ -17,14 +17,17 @@
 package com.android.server.wm;
 
 import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Rect;
 
 /**
- * Interface used by the creator of the controller to listen to changes with the container.
+ * Interface used by the creator of {@link TaskWindowContainerController} to listen to changes with
+ * the task container.
  */
 public interface TaskWindowContainerListener extends WindowContainerListener {
 
-    /**
-     * Called when the snapshot of this task has changed.
-     */
+    /** Called when the snapshot of this task has changed. */
     void onSnapshotChanged(TaskSnapshot snapshot);
+
+    /** Called when the task container would like its controller to resize. */
+    void requestResize(Rect bounds, int resizeMode);
 }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index feceb8e..c32e689 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -447,7 +447,7 @@
 
     private void findWallpaperTarget(DisplayContent dc) {
         mFindResults.reset();
-        if (mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
+        if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
             // In freeform mode we set the wallpaper as its own target, so we don't need an
             // additional window to make it visible.
             mFindResults.setUseTopWallpaperAsTarget(true);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c8f4bd2..1987f90 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -706,12 +706,7 @@
 
     final WindowAnimator mAnimator;
 
-    private final BoundsAnimationController mBoundsAnimationController;
-
-    /** All of the TaskStacks in the window manager, unordered. For an ordered list call
-     * DisplayContent.getStacks(). */
-    // TODO: Don't believe this is needed with the WindowContainer model.
-    SparseArray<TaskStack> mStackIdToStack = new SparseArray<>();
+    final BoundsAnimationController mBoundsAnimationController;
 
     private final PointerEventDispatcher mPointerEventDispatcher;
 
@@ -2573,16 +2568,6 @@
         }
     }
 
-    @Override
-    public Rect getBoundsForNewConfiguration(int stackId) {
-        synchronized(mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            final Rect outBounds = new Rect();
-            stack.getBoundsForNewConfiguration(outBounds);
-            return outBounds;
-        }
-    }
-
     void setFocusTaskRegionLocked() {
         final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
         if (focusedTask != null) {
@@ -2827,11 +2812,6 @@
         }
     }
 
-    boolean isStackVisibleLocked(int stackId) {
-        final TaskStack stack = mStackIdToStack.get(stackId);
-        return (stack != null && stack.isVisible());
-    }
-
     public void setDockedStackCreateState(int mode, Rect bounds) {
         synchronized (mWindowMap) {
             setDockedStackCreateStateLocked(mode, bounds);
@@ -2880,17 +2860,19 @@
             }
 
             final Rect stackBounds;
-            final DisplayContent displayContent;
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                return null;
+            }
+
+            final TaskStack stack = displayContent.getStackById(PINNED_STACK_ID);
             if (stack != null) {
                 // If the stack exists, then use its final bounds to calculate the new aspect ratio
                 // bounds.
-                displayContent = stack.getDisplayContent();
                 stackBounds = new Rect();
                 stack.getAnimatingBounds(stackBounds);
             } else {
                 // Otherwise, just calculate the aspect ratio bounds from the default bounds
-                displayContent = mRoot.getDisplayContent(displayId);
                 stackBounds = displayContent.getPinnedStackController().getDefaultBounds();
             }
             return displayContent.getPinnedStackController().getAspectRatioBounds(stackBounds,
@@ -2898,119 +2880,10 @@
         }
     }
 
-    /**
-     * Sets the current picture-in-picture aspect ratio.
-     */
-    public void setPictureInPictureAspectRatio(float aspectRatio) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
-            if (!mSupportsPictureInPicture || stack == null) {
-                return;
-            }
-
-            final int displayId = stack.getDisplayContent().getDisplayId();
-            final Rect toBounds = getPictureInPictureBounds(displayId, aspectRatio);
-            animateResizePinnedStack(toBounds, -1 /* duration */);
-        }
-    }
-
-    /**
-     * Sets the current picture-in-picture actions.
-     */
-    public void setPictureInPictureActions(List<RemoteAction> actions) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
-            if (!mSupportsPictureInPicture || stack == null) {
-                return;
-            }
-
-            stack.getDisplayContent().getPinnedStackController().setActions(actions);
-        }
-    }
-
-    /**
-     * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
-     * specified stackId.
-     * @param stackId The unique identifier of the new stack.
-     * @param displayId The unique identifier of the DisplayContent.
-     * @param onTop If true the stack will be place at the top of the display,
-     *              else at the bottom.
-     * @return The bounds that the stack has after adding. null means fullscreen.
-     */
-    public Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mWindowMap) {
-                final DisplayContent dc = mRoot.getDisplayContent(displayId);
-                if (dc == null) {
-                    throw new IllegalArgumentException("Trying to add stackId=" + stackId
-                            + " to unknown displayId=" + displayId);
-                }
-
-                return dc.addStackToDisplay(stackId, onTop);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    /**
-     * Move a TaskStack from current DisplayContent to specified one.
-     * @param stackId The unique identifier of the new stack.
-     * @param displayId The unique identifier of the new display.
-     */
-    public Rect moveStackToDisplay(int stackId, int displayId) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mWindowMap) {
-                TaskStack stack = mStackIdToStack.get(stackId);
-                if (stack == null) {
-                    throw new IllegalArgumentException("Trying to move unknown stackId=" + stackId
-                            + " to displayId=" + displayId);
-                }
-
-                final DisplayContent targetDisplayContent = mRoot.getDisplayContent(displayId);
-                if (targetDisplayContent == null) {
-                    throw new IllegalArgumentException("Trying to move stackId=" + stackId
-                            + " to unknown displayId=" + displayId);
-                }
-
-                return targetDisplayContent.moveStackToDisplay(stack);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    /**
-     * Remove a TaskStack completely.
-     * @param stackId The unique identifier of the stack.
-     */
-    public void removeStack(int stackId) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack != null) {
-                stack.removeIfPossible();
-                mStackIdToStack.remove(stackId);
-            }
-        }
-    }
-
-    public void getStackDockedModeBounds(int stackId, Rect bounds, boolean ignoreVisibility) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack != null) {
-                stack.getStackDockedModeBoundsLocked(bounds, ignoreVisibility);
-                return;
-            }
-            bounds.setEmpty();
-        }
-    }
-
     @Override
     public void getStackBounds(int stackId, Rect bounds) {
         synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
+            final TaskStack stack = mRoot.getStackById(stackId);
             if (stack != null) {
                 stack.getBounds(bounds);
                 return;
@@ -3035,43 +2908,6 @@
     }
 
     /**
-     * Re-sizes a stack and its containing tasks.
-     * @param stackId Id of stack to resize.
-     * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
-     * @param configs Configurations for tasks in the resized stack, keyed by task id.
-     * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
-     * @return True if the stack is now fullscreen.
-     * */
-    public boolean resizeStack(int stackId, Rect bounds,
-            SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
-            SparseArray<Rect> taskTempInsetBounds) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack == null) {
-                throw new IllegalArgumentException("resizeStack: stackId " + stackId
-                        + " not found.");
-            }
-            if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
-                    && stack.isVisible()) {
-                stack.getDisplayContent().setLayoutNeeded();
-                mWindowPlacerLocked.performSurfacePlacement();
-            }
-            return stack.getRawFullscreen();
-        }
-    }
-
-    public void prepareFreezingTaskBounds(int stackId) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(stackId);
-            if (stack == null) {
-                throw new IllegalArgumentException("prepareFreezingTaskBounds: stackId " + stackId
-                        + " not found.");
-            }
-            stack.prepareFreezingTaskBounds();
-        }
-    }
-
-    /**
      * Starts deferring layout passes. Useful when doing multiple changes but to optimize
      * performance, only one layout pass should be done. This can be called multiple times, and
      * layouting will be resumed once the last caller has called {@link #continueSurfaceLayout}
@@ -3505,8 +3341,9 @@
 
             // Notify whether the docked stack exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
-            displayContent.mDividerControllerLocked
-                    .notifyDockedStackExistsChanged(hasDockedTasksForUser(newUserId));
+            final TaskStack stack = displayContent.getDockedStackIgnoringVisibility();
+            displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
+                    stack != null && stack.hasTaskForUser(newUserId));
 
             // If the display is already prepared, update the density.
             // Otherwise, we'll update it when it's prepared.
@@ -3519,15 +3356,6 @@
         }
     }
 
-    /** Returns whether there is a docked task for the current user. */
-    boolean hasDockedTasksForUser(int userId) {
-        final TaskStack stack = mStackIdToStack.get(DOCKED_STACK_ID);
-        if (stack == null) {
-            return false;
-        }
-        return stack.hasTaskForUser(userId);
-    }
-
     /* Called by WindowState */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUserId) return true;
@@ -5417,8 +5245,6 @@
 
         public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
 
-        public static final int RESIZE_STACK = 42;
-        public static final int RESIZE_TASK = 43;
         public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
 
         public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
@@ -5844,23 +5670,6 @@
                     }
                 }
                 break;
-                case RESIZE_TASK: {
-                    try {
-                        mActivityManager.resizeTask(msg.arg1, (Rect) msg.obj, msg.arg2);
-                    } catch (RemoteException e) {
-                        // This will not happen since we are in the same process.
-                    }
-                }
-                break;
-                case RESIZE_STACK: {
-                    try {
-                        mActivityManager.resizeStack(
-                                msg.arg1, (Rect) msg.obj, msg.arg2 == 1, false, false, -1);
-                    } catch (RemoteException e) {
-                        // This will not happen since we are in the same process.
-                    }
-                }
-                break;
                 case WINDOW_REPLACEMENT_TIMEOUT: {
                     synchronized (mWindowMap) {
                         for (int i = mWindowReplacementTimeouts.size() - 1; i >= 0; i--) {
@@ -7646,26 +7455,6 @@
         }
     }
 
-    public void animateResizePinnedStack(final Rect bounds, final int animationDuration) {
-        synchronized (mWindowMap) {
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
-            if (stack == null) {
-                Slog.w(TAG, "animateResizePinnedStack: stackId " + PINNED_STACK_ID + " not found.");
-                return;
-            }
-            final Rect originalBounds = new Rect();
-            stack.getBounds(originalBounds);
-            stack.setAnimatingBounds(bounds);
-            UiThread.getHandler().post(new Runnable() {
-                @Override
-                public void run() {
-                    mBoundsAnimationController.animateBounds(
-                            stack, originalBounds, bounds, animationDuration);
-                }
-            });
-        }
-    }
-
     public void setForceResizableTasks(boolean forceResizableTasks) {
         synchronized (mWindowMap) {
             mForceResizableTasks = forceResizableTasks;
@@ -8123,7 +7912,8 @@
         @Override
         public boolean isStackVisible(int stackId) {
             synchronized (mWindowMap) {
-                return WindowManagerService.this.isStackVisibleLocked(stackId);
+                final DisplayContent dc = getDefaultDisplayContentLocked();
+                return dc.isStackVisible(stackId);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 10aebe6..5e458d4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2304,6 +2304,7 @@
                     final WindowState win = mService.windowForClientLocked(mSession, mClient, false);
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
+                        final DisplayContent dc = getDisplayContent();
                         if (win.mAppToken != null && win.mAppToken.findMainWindow() == win) {
                             mService.mTaskSnapshotController.onAppDied(win.mAppToken);
                         }
@@ -2313,7 +2314,7 @@
                             // just in case they have the divider at an unstable position. Better
                             // also reset drag resizing state, because the owner can't do it
                             // anymore.
-                            final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+                            final TaskStack stack = dc.getDockedStackIgnoringVisibility();
                             if (stack != null) {
                                 stack.resetDockedStackToMiddle();
                             }
@@ -3198,8 +3199,10 @@
         if (task == null) {
             return false;
         }
+        if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+            return false;
+        }
         if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
-
             // Floating windows never enter drag resize mode.
             return false;
         }
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index b2372a3..fab309b 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -110,7 +110,7 @@
 static void nativeInit(JNIEnv* env, jobject obj) {
     gPowerManagerServiceObj = env->NewGlobalRef(obj);
 
-    gPowerHal = IPower::getService("power");
+    gPowerHal = IPower::getService();
     if (gPowerHal == nullptr) {
         ALOGE("Couldn't load PowerHAL module");
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f3b0131..5c15750 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -33,6 +33,13 @@
 import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
 import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
 import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED;
+import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
+import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -259,6 +266,17 @@
     private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
             = "application-restrictions-manager";
 
+    // Comprehensive list of delegations.
+    private static final String DELEGATIONS[] = {
+        DELEGATION_CERT_INSTALL,
+        DELEGATION_APP_RESTRICTIONS,
+        DELEGATION_BLOCK_UNINSTALL,
+        DELEGATION_ENABLE_SYSTEM_APP,
+        DELEGATION_KEEP_UNINSTALLED_PACKAGES,
+        DELEGATION_PACKAGE_ACCESS,
+        DELEGATION_PERMISSION_GRANT
+    };
+
     /**
      *  System property whose value is either "true" or "false", indicating whether
      *  device owner is present.
@@ -468,12 +486,11 @@
 
         ComponentName mRestrictionsProvider;
 
-        String mDelegatedCertInstallerPackage;
+        // Map of delegate package to delegation scopes
+        final ArrayMap<String, List<String>> mDelegationMap = new ArrayMap<>();
 
         boolean doNotAskCredentialsOnBoot = false;
 
-        String mApplicationRestrictionsManagingPackage;
-
         Set<String> mAffiliationIds = new ArraySet<>();
 
         long mLastSecurityLogRetrievalTime = -1;
@@ -1397,7 +1414,7 @@
     }
 
     private void handlePackagesChanged(String packageName, int userHandle) {
-        boolean removed = false;
+        boolean removedAdmin = false;
         if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
         DevicePolicyData policy = getUserData(userHandle);
         synchronized (this) {
@@ -1413,32 +1430,36 @@
                                         PackageManager.MATCH_DIRECT_BOOT_AWARE
                                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                                         userHandle) == null) {
-                            removed = true;
+                            removedAdmin = true;
                             policy.mAdminList.remove(i);
                             policy.mAdminMap.remove(aa.info.getComponent());
                         }
                     }
                 } catch (RemoteException re) {
-                    // Shouldn't happen
+                    // Shouldn't happen.
                 }
             }
-            if (removed) {
+            if (removedAdmin) {
                 validatePasswordOwnerLocked(policy);
-                saveSettingsLocked(policy.mUserHandle);
             }
 
-            // Check if delegated cert installer or app restrictions managing packages are removed.
-            if (isRemovedPackage(packageName, policy.mDelegatedCertInstallerPackage, userHandle)) {
-                policy.mDelegatedCertInstallerPackage = null;
-                saveSettingsLocked(policy.mUserHandle);
+            boolean removedDelegate = false;
+
+            // Check if a delegate was removed.
+            for (int i = policy.mDelegationMap.size() - 1; i >= 0; i--) {
+                final String delegatePackage = policy.mDelegationMap.keyAt(i);
+                if (isRemovedPackage(packageName, delegatePackage, userHandle)) {
+                    policy.mDelegationMap.removeAt(i);
+                    removedDelegate = true;
+                }
             }
-            if (isRemovedPackage(
-                    packageName, policy.mApplicationRestrictionsManagingPackage, userHandle)) {
-                policy.mApplicationRestrictionsManagingPackage = null;
+
+            // Persist updates if the removed package was an admin or delegate.
+            if (removedAdmin || removedDelegate) {
                 saveSettingsLocked(policy.mUserHandle);
             }
         }
-        if (removed) {
+        if (removedAdmin) {
             // The removed admin might have disabled camera, so update user restrictions.
             pushUserRestrictions(userHandle);
         }
@@ -1609,6 +1630,11 @@
             mContext.getSystemService(PowerManager.class).reboot(reason);
         }
 
+        void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
+                throws IOException {
+            RecoverySystem.rebootWipeUserData(mContext, shutdown, reason, force);
+        }
+
         boolean systemPropertiesGetBoolean(String key, boolean def) {
             return SystemProperties.getBoolean(key, def);
         }
@@ -2372,13 +2398,19 @@
                 out.attribute(null, ATTR_PERMISSION_POLICY,
                         Integer.toString(policy.mPermissionPolicy));
             }
-            if (policy.mDelegatedCertInstallerPackage != null) {
-                out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER,
-                        policy.mDelegatedCertInstallerPackage);
-            }
-            if (policy.mApplicationRestrictionsManagingPackage != null) {
-                out.attribute(null, ATTR_APPLICATION_RESTRICTIONS_MANAGER,
-                        policy.mApplicationRestrictionsManagingPackage);
+
+            // Serialize delegations.
+            for (int i = 0; i < policy.mDelegationMap.size(); ++i) {
+                final String delegatePackage = policy.mDelegationMap.keyAt(i);
+                final List<String> scopes = policy.mDelegationMap.valueAt(i);
+
+                // Every "delegation" tag serializes the information of one delegate-scope pair.
+                for (String scope : scopes) {
+                    out.startTag(null, "delegation");
+                    out.attribute(null, "delegatePackage", delegatePackage);
+                    out.attribute(null, "scope", scope);
+                    out.endTag(null, "delegation");
+                }
             }
 
             final int N = policy.mAdminList.size();
@@ -2562,10 +2594,36 @@
             if (!TextUtils.isEmpty(permissionPolicy)) {
                 policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
             }
-            policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null,
-                    ATTR_DELEGATED_CERT_INSTALLER);
-            policy.mApplicationRestrictionsManagingPackage = parser.getAttributeValue(null,
-                    ATTR_APPLICATION_RESTRICTIONS_MANAGER);
+            // Check for delegation compatibility with pre-O.
+            // TODO(edmanp) remove in P.
+            {
+                final String certDelegate = parser.getAttributeValue(null,
+                        ATTR_DELEGATED_CERT_INSTALLER);
+                if (certDelegate != null) {
+                    List<String> scopes = policy.mDelegationMap.get(certDelegate);
+                    if (scopes == null) {
+                        scopes = new ArrayList<>();
+                        policy.mDelegationMap.put(certDelegate, scopes);
+                    }
+                    if (!scopes.contains(DELEGATION_CERT_INSTALL)) {
+                        scopes.add(DELEGATION_CERT_INSTALL);
+                        needsRewrite = true;
+                    }
+                }
+                final String appRestrictionsDelegate = parser.getAttributeValue(null,
+                        ATTR_APPLICATION_RESTRICTIONS_MANAGER);
+                if (appRestrictionsDelegate != null) {
+                    List<String> scopes = policy.mDelegationMap.get(appRestrictionsDelegate);
+                    if (scopes == null) {
+                        scopes = new ArrayList<>();
+                        policy.mDelegationMap.put(appRestrictionsDelegate, scopes);
+                    }
+                    if (!scopes.contains(DELEGATION_APP_RESTRICTIONS)) {
+                        scopes.add(DELEGATION_APP_RESTRICTIONS);
+                        needsRewrite = true;
+                    }
+                }
+            }
 
             type = parser.next();
             int outerDepth = parser.getDepth();
@@ -2600,6 +2658,23 @@
                     } catch (RuntimeException e) {
                         Slog.w(LOG_TAG, "Failed loading admin " + name, e);
                     }
+                } else if ("delegation".equals(tag)) {
+                    // Parse delegation info.
+                    final String delegatePackage = parser.getAttributeValue(null,
+                            "delegatePackage");
+                    final String scope = parser.getAttributeValue(null, "scope");
+
+                    // Get a reference to the scopes list for the delegatePackage.
+                    List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+                    // Or make a new list if none was found.
+                    if (scopes == null) {
+                        scopes = new ArrayList<>();
+                        policy.mDelegationMap.put(delegatePackage, scopes);
+                    }
+                    // Add the new scope to the list of delegatePackage if it's not already there.
+                    if (!scopes.contains(scope)) {
+                        scopes.add(scope);
+                    }
                 } else if ("failed-password-attempts".equals(tag)) {
                     policy.mFailedPasswordAttempts = Integer.parseInt(
                             parser.getAttributeValue(null, "value"));
@@ -4522,9 +4597,9 @@
     }
 
     @Override
-    public void enforceCanManageCaCerts(ComponentName who) {
+    public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
         if (who == null) {
-            if (!isCallerDelegatedCertInstaller()) {
+            if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
                 mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
             }
         } else {
@@ -4538,35 +4613,6 @@
         }
     }
 
-    private void enforceCanManageInstalledKeys(ComponentName who) {
-        if (who == null) {
-            if (!isCallerDelegatedCertInstaller()) {
-                throw new SecurityException("who == null, but caller is not cert installer");
-            }
-        } else {
-            enforceProfileOrDeviceOwner(who);
-        }
-    }
-
-    private boolean isCallerDelegatedCertInstaller() {
-        final int callingUid = mInjector.binderGetCallingUid();
-        final int userHandle = UserHandle.getUserId(callingUid);
-        synchronized (this) {
-            final DevicePolicyData policy = getUserData(userHandle);
-            if (policy.mDelegatedCertInstallerPackage == null) {
-                return false;
-            }
-
-            try {
-                int uid = mInjector.getPackageManager().getPackageUidAsUser(
-                        policy.mDelegatedCertInstallerPackage, userHandle);
-                return uid == callingUid;
-            } catch (NameNotFoundException e) {
-                return false;
-            }
-        }
-    }
-
     @Override
     public boolean approveCaCert(String alias, int userId, boolean approval) {
         enforceManageUsers();
@@ -4608,8 +4654,9 @@
     }
 
     @Override
-    public boolean installCaCert(ComponentName admin, byte[] certBuffer) throws RemoteException {
-        enforceCanManageCaCerts(admin);
+    public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
+            throws RemoteException {
+        enforceCanManageCaCerts(admin, callerPackage);
 
         byte[] pemCert;
         try {
@@ -4651,8 +4698,8 @@
     }
 
     @Override
-    public void uninstallCaCerts(ComponentName admin, String[] aliases) {
-        enforceCanManageCaCerts(admin);
+    public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
+        enforceCanManageCaCerts(admin, callerPackage);
 
         final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
         final long id = mInjector.binderClearCallingIdentity();
@@ -4676,9 +4723,11 @@
     }
 
     @Override
-    public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, byte[] chain,
-            String alias, boolean requestAccess) {
-        enforceCanManageInstalledKeys(who);
+    public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
+            byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+        enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                DELEGATION_CERT_INSTALL);
+
 
         final int callingUid = mInjector.binderGetCallingUid();
         final long id = mInjector.binderClearCallingIdentity();
@@ -4709,8 +4758,9 @@
     }
 
     @Override
-    public boolean removeKeyPair(ComponentName who, String alias) {
-        enforceCanManageInstalledKeys(who);
+    public boolean removeKeyPair(ComponentName who, String callerPackage, String alias) {
+        enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                DELEGATION_CERT_INSTALL);
 
         final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
         final long id = Binder.clearCallingIdentity();
@@ -4795,33 +4845,267 @@
         }.execute();
     }
 
+    /**
+     * Set the scopes of a device owner or profile owner delegate.
+     *
+     * @param who the device owner or profile owner.
+     * @param delegatePackage the name of the delegate package.
+     * @param scopes the list of delegation scopes to be given to the delegate package.
+     */
     @Override
-    public void setCertInstallerPackage(ComponentName who, String installerPackage)
-            throws SecurityException {
-        int userHandle = UserHandle.getCallingUserId();
+    public void setDelegatedScopes(ComponentName who, String delegatePackage,
+            List<String> scopes) throws SecurityException {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty");
+        Preconditions.checkCollectionElementsNotNull(scopes, "Scopes");
+        // Remove possible duplicates.
+        scopes = new ArrayList(new ArraySet(scopes));
+        // Ensure given scopes are valid.
+        if (scopes.retainAll(Arrays.asList(DELEGATIONS))) {
+            throw new IllegalArgumentException("Unexpected delegation scopes");
+        }
+
+        // Retrieve the user ID of the calling process.
+        final int userId = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
+            // Ensure calling process is device/profile owner.
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (getTargetSdk(who.getPackageName(), userHandle) >= Build.VERSION_CODES.N) {
-                if (installerPackage != null &&
-                        !isPackageInstalledForUser(installerPackage, userHandle)) {
-                    throw new IllegalArgumentException("Package " + installerPackage
+            // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N).
+            if (scopes.size() == 1 && scopes.get(0).equals(DELEGATION_CERT_INSTALL) ||
+                    getTargetSdk(who.getPackageName(), userId) >= Build.VERSION_CODES.N) {
+                // Throw when the delegate package is not installed.
+                if (!isPackageInstalledForUser(delegatePackage, userId)) {
+                    throw new IllegalArgumentException("Package " + delegatePackage
                             + " is not installed on the current user");
                 }
             }
-            DevicePolicyData policy = getUserData(userHandle);
-            policy.mDelegatedCertInstallerPackage = installerPackage;
-            saveSettingsLocked(userHandle);
+
+            // Set the new delegate in user policies.
+            final DevicePolicyData policy = getUserData(userId);
+            if (!scopes.isEmpty()) {
+                policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes));
+            } else {
+                // Remove any delegation info if the given scopes list is empty.
+                policy.mDelegationMap.remove(delegatePackage);
+            }
+
+            // Notify delegate package of updates.
+            final Intent intent = new Intent(
+                    DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
+            // Only call receivers registered in the manifest (don’t wake app if not running).
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            // Limit components this intent resolves to to the delegate package.
+            intent.setPackage(delegatePackage);
+            // Include the list of delegated scopes as an extra.
+            intent.putExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes.toArray());
+            // Send the broadcast.
+            mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+
+            // Persist updates.
+            saveSettingsLocked(userId);
+        }
+    }
+
+    /**
+     * Get the delegation scopes given to a delegate package by a device owner or profile owner.
+     *
+     * A DO/PO can get the scopes of any package. A non DO/PO package can get its own scopes by
+     * passing in {@code null} as the {@code who} parameter and its own name as the
+     * {@code delegatepackage}.
+     *
+     * @param who the device owner or profile owner, or {@code null} if the caller is
+     *            {@code delegatePackage}.
+     * @param delegatePackage the name of the delegate package whose scopes are to be retrieved.
+     * @return a list of the delegation scopes currently given to {@code delegatePackage}.
+     */
+    @Override
+    @NonNull
+    public List<String> getDelegatedScopes(ComponentName who,
+            String delegatePackage) throws SecurityException {
+        Preconditions.checkNotNull(delegatePackage, "Delegate package is null");
+
+        // Retrieve the user ID of the calling process.
+        final int callingUid = mInjector.binderGetCallingUid();
+        final int userId = UserHandle.getUserId(callingUid);
+        synchronized (this) {
+            // Ensure calling process is device/profile owner.
+            if (who != null) {
+                getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Or ensure calling process is delegatePackage itself.
+            } else {
+                int uid = 0;
+                try {
+                  uid = mInjector.getPackageManager()
+                          .getPackageUidAsUser(delegatePackage, userId);
+                } catch(NameNotFoundException e) {
+                }
+                if (uid != callingUid) {
+                    throw new SecurityException("Caller with uid " + callingUid + " is not "
+                            + delegatePackage);
+                }
+            }
+            final DevicePolicyData policy = getUserData(userId);
+            // Retrieve the scopes assigned to delegatePackage, or null if no scope was given.
+            final List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+            return scopes == null ? Collections.EMPTY_LIST : scopes;
+        }
+    }
+
+    /**
+     * Get a list of  packages that were given a specific delegation scopes by a device owner or
+     * profile owner.
+     *
+     * @param who the device owner or profile owner.
+     * @param scope the scope whose delegates are to be retrieved.
+     * @return a list of the delegate packages currently given the {@code scope} delegation.
+     */
+    @NonNull
+    public List<String> getDelegatePackages(ComponentName who, String scope)
+            throws SecurityException {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(scope, "Scope is null");
+        if (!Arrays.asList(DELEGATIONS).contains(scope)) {
+            throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
+        }
+
+        // Retrieve the user ID of the calling process.
+        final int userId = mInjector.userHandleGetCallingUserId();
+        synchronized (this) {
+            // Ensure calling process is device/profile owner.
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final DevicePolicyData policy = getUserData(userId);
+
+            // Create a list to hold the resulting delegate packages.
+            final List<String> delegatePackagesWithScope = new ArrayList<>();
+            // Add all delegations containing scope to the result list.
+            for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+                if (policy.mDelegationMap.valueAt(i).contains(scope)) {
+                    delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i));
+                }
+            }
+            return delegatePackagesWithScope;
+        }
+    }
+
+    /**
+     * Check whether a caller application has been delegated a given scope via
+     * {@link #setDelegatedScopes} to access privileged APIs on the behalf of a profile owner or
+     * device owner.
+     * <p>
+     * This is done by checking that {@code callerPackage} was granted {@code scope} delegation and
+     * then comparing the calling UID with the UID of {@code callerPackage} as reported by
+     * {@link PackageManager#getPackageUidAsUser}.
+     *
+     * @param callerPackage the name of the package that is trying to invoke a function in the DPMS.
+     * @param scope the delegation scope to be checked.
+     * @return {@code true} if the calling process is a delegate of {@code scope}.
+     */
+    private boolean isCallerDelegate(String callerPackage, String scope) {
+        Preconditions.checkNotNull(callerPackage, "callerPackage is null");
+        if (!Arrays.asList(DELEGATIONS).contains(scope)) {
+            throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
+        }
+
+        // Retrieve the UID and user ID of the calling process.
+        final int callingUid = mInjector.binderGetCallingUid();
+        final int userId = UserHandle.getUserId(callingUid);
+        synchronized (this) {
+            // Retrieve user policy data.
+            final DevicePolicyData policy = getUserData(userId);
+            // Retrieve the list of delegation scopes granted to callerPackage.
+            final List<String> scopes = policy.mDelegationMap.get(callerPackage);
+            // Check callingUid only if callerPackage has the required scope delegation.
+            if (scopes != null && scopes.contains(scope)) {
+                try {
+                    // Retrieve the expected UID for callerPackage.
+                    final int uid = mInjector.getPackageManager()
+                            .getPackageUidAsUser(callerPackage, userId);
+                    // Return true if the caller is actually callerPackage.
+                    return uid == callingUid;
+                } catch (NameNotFoundException e) {
+                    // Ignore.
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Throw a security exception if a ComponentName is given and it is not a device/profile owner
+     * or if the calling process is not a delegate of the given scope.
+     *
+     * @param who the device owner of profile owner, or null if {@code callerPackage} is a
+     *            {@code scope} delegate.
+     * @param callerPackage the name of the calling package. Required if {@code who} is
+     *            {@code null}.
+     * @param reqPolicy the policy used in the API whose access permission is being checked.
+     * @param scoppe the delegation scope corresponding to the API being checked.
+     * @throws SecurityException if {@code who} is given and is not an owner for {@code reqPolicy};
+     *            or when {@code who} is {@code null} and {@code callerPackage} is not a delegate
+     *            of {@code scope}.
+     */
+    private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy,
+            String scope) {
+        // If a ComponentName is given ensure it is a device or profile owner according to policy.
+        if (who != null) {
+            synchronized (this) {
+                getActiveAdminForCallerLocked(who, reqPolicy);
+            }
+        // If no ComponentName is given ensure calling process has scope delegation.
+        } else if (!isCallerDelegate(callerPackage, scope)) {
+            throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
+                    + " is not a delegate of scope " + scope + ".");
+        }
+    }
+
+    /**
+     * Helper function to preserve delegation behavior pre-O when using the deprecated functions
+     * {@code #setCertInstallerPackage} and {@code #setApplicationRestrictionsManagingPackage}.
+     */
+    private void setDelegatedScopePreO(ComponentName who,
+            String delegatePackage, String scope) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        final int userId = mInjector.userHandleGetCallingUserId();
+        synchronized(this) {
+            // Ensure calling process is device/profile owner.
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final DevicePolicyData policy = getUserData(userId);
+
+            if (delegatePackage != null) {
+                // Set package as a delegate for scope if it is not already one.
+                List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+                if (scopes == null) {
+                    scopes = new ArrayList<>();
+                }
+                if (!scopes.contains(scope)) {
+                    scopes.add(scope);
+                    setDelegatedScopes(who, delegatePackage, scopes);
+                }
+            }
+
+            // Clear any existing scope delegates.
+            for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+                final String currentPackage = policy.mDelegationMap.keyAt(i);
+                final List<String> currentScopes = policy.mDelegationMap.valueAt(i);
+
+                if (!currentPackage.equals(delegatePackage) && currentScopes.remove(scope)) {
+                    setDelegatedScopes(who, currentPackage, currentScopes);
+                }
+            }
         }
     }
 
     @Override
+    public void setCertInstallerPackage(ComponentName who, String installerPackage)
+            throws SecurityException {
+        setDelegatedScopePreO(who, installerPackage, DELEGATION_CERT_INSTALL);
+    }
+
+    @Override
     public String getCertInstallerPackage(ComponentName who) throws SecurityException {
-        int userHandle = UserHandle.getCallingUserId();
-        synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            DevicePolicyData policy = getUserData(userHandle);
-            return policy.mDelegatedCertInstallerPackage;
-        }
+        final List<String> delegatePackages = getDelegatePackages(who, DELEGATION_CERT_INSTALL);
+        return delegatePackages.size() > 0 ? delegatePackages.get(0) : null;
     }
 
     /**
@@ -4869,7 +5153,7 @@
         }
     }
 
-    private void wipeDataNoLock(boolean wipeExtRequested, String reason, boolean force) {
+    private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) {
         wtfIfInLock();
 
         if (wipeExtRequested) {
@@ -4878,94 +5162,87 @@
             sm.wipeAdoptableDisks();
         }
         try {
-            RecoverySystem.rebootWipeUserData(mContext, false /* shutdown */, reason, force);
+            mInjector.recoverySystemRebootWipeUserData(
+                    /*shutdown=*/ false, reason, /*force=*/ true);
         } catch (IOException | SecurityException e) {
             Slog.w(LOG_TAG, "Failed requesting data wipe", e);
         }
     }
 
+    private void forceWipeUser(int userId) {
+        try {
+            IActivityManager am = mInjector.getIActivityManager();
+            if (am.getCurrentUser().id == userId) {
+                am.switchUser(UserHandle.USER_SYSTEM);
+            }
+
+            boolean userRemoved = mUserManagerInternal.removeUserEvenWhenDisallowed(userId);
+            if (!userRemoved) {
+                Slog.w(LOG_TAG, "Couldn't remove user " + userId);
+            } else if (isManagedProfile(userId)) {
+                sendWipeProfileNotification();
+            }
+        } catch (RemoteException re) {
+            // Shouldn't happen
+        }
+    }
+
     @Override
     public void wipeData(int flags) {
         if (!mHasFeature) {
             return;
         }
-        final int userHandle = mInjector.userHandleGetCallingUserId();
-        enforceFullCrossUsersPermission(userHandle);
+        enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
 
-        final String source;
+        final ActiveAdmin admin;
         synchronized (this) {
-            // This API can only be called by an active device admin,
-            // so try to retrieve it to check that the caller is one.
-            final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
-                    DeviceAdminInfo.USES_POLICY_WIPE_DATA);
-            source = admin.info.getComponent().flattenToShortString();
-
-            long ident = mInjector.binderClearCallingIdentity();
-            try {
-                final String restriction;
-                if (userHandle == UserHandle.USER_SYSTEM) {
-                    restriction = UserManager.DISALLOW_FACTORY_RESET;
-                } else if (isManagedProfile(userHandle)) {
-                    restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
-                } else {
-                    restriction = UserManager.DISALLOW_REMOVE_USER;
-                }
-                if (isAdminAffectedByRestriction(
-                        admin.info.getComponent(), restriction, userHandle)) {
-                    throw new SecurityException("Cannot wipe data. " + restriction
-                            + " restriction is set for user " + userHandle);
-                }
-
-                if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
-                    if (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
-                        throw new SecurityException(
-                               "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
-                    }
-                    PersistentDataBlockManager manager = (PersistentDataBlockManager)
-                            mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
-                    if (manager != null) {
-                        manager.wipe();
-                    }
-                }
-
-            } finally {
-                mInjector.binderRestoreCallingIdentity(ident);
-            }
+            admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
         }
-        final boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0;
-        wipeDeviceNoLock(wipeExtRequested, userHandle,
-                "DevicePolicyManager.wipeData() from " + source, /*force=*/ true);
+        String reason = "DevicePolicyManager.wipeData() from "
+                + admin.info.getComponent().flattenToShortString();
+        wipeDataNoLock(
+                admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier());
     }
 
-    private void wipeDeviceNoLock(
-            boolean wipeExtRequested, final int userHandle, String reason, boolean force) {
+    private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) {
         wtfIfInLock();
 
         long ident = mInjector.binderClearCallingIdentity();
         try {
-            // TODO If split user is enabled and the device owner is set in the primary user (rather
-            // than system), we should probably trigger factory reset. Current code just remove
-            // that user (but still clears FRP...)
-            if (userHandle == UserHandle.USER_SYSTEM) {
-                wipeDataNoLock(wipeExtRequested, reason, force);
+            // First check whether the admin is allowed to wipe the device/user/profile.
+            final String restriction;
+            if (userId == UserHandle.USER_SYSTEM) {
+                restriction = UserManager.DISALLOW_FACTORY_RESET;
+            } else if (isManagedProfile(userId)) {
+                restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
             } else {
-                try {
-                    IActivityManager am = mInjector.getIActivityManager();
-                    if (am.getCurrentUser().id == userHandle) {
-                        am.switchUser(UserHandle.USER_SYSTEM);
-                    }
+                restriction = UserManager.DISALLOW_REMOVE_USER;
+            }
+            if (isAdminAffectedByRestriction(admin, restriction, userId)) {
+                throw new SecurityException("Cannot wipe data. " + restriction
+                        + " restriction is set for user " + userId);
+            }
 
-                    boolean userRemoved = force
-                            ? mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle)
-                            : mUserManager.removeUser(userHandle);
-                    if (!userRemoved) {
-                        Slog.w(LOG_TAG, "Couldn't remove user " + userHandle);
-                    } else if (isManagedProfile(userHandle)) {
-                        sendWipeProfileNotification();
-                    }
-                } catch (RemoteException re) {
-                    // Shouldn't happen
+            if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
+                if (!isDeviceOwner(admin, userId)) {
+                    throw new SecurityException(
+                            "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
                 }
+                PersistentDataBlockManager manager = (PersistentDataBlockManager)
+                        mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+                if (manager != null) {
+                    manager.wipe();
+                }
+            }
+
+            // TODO If split user is enabled and the device owner is set in the primary user
+            // (rather than system), we should probably trigger factory reset. Current code just
+            // removes that user (but still clears FRP...)
+            if (userId == UserHandle.USER_SYSTEM) {
+                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
+                        reason);
+            } else {
+                forceWipeUser(userId);
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
@@ -5105,25 +5382,21 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BIND_DEVICE_ADMIN, null);
 
+        boolean wipeData = false;
+        ActiveAdmin strictestAdmin = null;
         final long ident = mInjector.binderClearCallingIdentity();
         try {
-            boolean wipeData = false;
-            int identifier = 0;
             synchronized (this) {
                 DevicePolicyData policy = getUserData(userHandle);
                 policy.mFailedPasswordAttempts++;
                 saveSettingsLocked(userHandle);
                 if (mHasFeature) {
-                    ActiveAdmin strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
+                    strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
                             userHandle, /* parent */ false);
                     int max = strictestAdmin != null
                             ? strictestAdmin.maximumFailedPasswordsForWipe : 0;
                     if (max > 0 && policy.mFailedPasswordAttempts >= max) {
-                        // Wipe the user/profile associated with the policy that was violated. This
-                        // is not necessarily calling user: if the policy that fired was from a
-                        // managed profile rather than the main user profile, we wipe former only.
                         wipeData = true;
-                        identifier = strictestAdmin.getUserHandle().getIdentifier();
                     }
 
                     sendAdminCommandForLockscreenPoliciesLocked(
@@ -5131,14 +5404,33 @@
                             DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle);
                 }
             }
-            if (wipeData) {
-                // Call without holding lock.
-                wipeDeviceNoLock(false, identifier, "reportFailedPasswordAttempt()", false);
-            }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
 
+        if (wipeData && strictestAdmin != null) {
+            final int userId = strictestAdmin.getUserHandle().getIdentifier();
+            Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: "
+                    + strictestAdmin.info.getComponent().flattenToShortString()
+                    + ". Calling wipeData for user " + userId);
+
+            // Attempt to wipe the device/user/profile associated with the admin, as if the
+            // admin had called wipeData(). That way we can check whether the admin is actually
+            // allowed to wipe the device (e.g. a regular device admin shouldn't be able to wipe the
+            // device if the device owner has set DISALLOW_FACTORY_RESET, but the DO should be
+            // able to do so).
+            // IMPORTANT: Call without holding the lock to prevent deadlock.
+            try {
+                wipeDataNoLock(strictestAdmin.info.getComponent(),
+                        /*flags=*/ 0,
+                        /*reason=*/ "reportFailedPasswordAttempt()",
+                        userId);
+            } catch (SecurityException e) {
+                Slog.w(LOG_TAG, "Failed to wipe user " + userId
+                        + " after max failed password attempts reached.", e);
+            }
+        }
+
         if (mInjector.securityLogIsLoggingEnabled()) {
             SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
                     /*method strength*/ 1);
@@ -6016,32 +6308,38 @@
     }
 
     @Override
-    public void setKeepUninstalledPackages(ComponentName who, List<String> packageList) {
+    public void setKeepUninstalledPackages(ComponentName who, String callerPackage,
+            List<String> packageList) {
         if (!mHasFeature) {
             return;
         }
-        Preconditions.checkNotNull(who, "ComponentName is null");
         Preconditions.checkNotNull(packageList, "packageList is null");
         final int userHandle = UserHandle.getCallingUserId();
         synchronized (this) {
-            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-            admin.keepUninstalledPackages = packageList;
+            // Ensure the caller is a DO or a keep uninstalled packages delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+                    DELEGATION_KEEP_UNINSTALLED_PACKAGES);
+            // Get the device owner
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+            // Set list of packages to be kept even if uninstalled.
+            deviceOwner.keepUninstalledPackages = packageList;
+            // Save settings.
             saveSettingsLocked(userHandle);
+            // Notify package manager.
             mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
         }
     }
 
     @Override
-    public List<String> getKeepUninstalledPackages(ComponentName who) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+    public List<String> getKeepUninstalledPackages(ComponentName who, String callerPackage) {
         if (!mHasFeature) {
             return null;
         }
         // TODO In split system user mode, allow apps on user 0 to query the list
         synchronized (this) {
-            // Check if this is the device owner who is calling
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            // Ensure the caller is a DO or a keep uninstalled packages delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+                    DELEGATION_KEEP_UNINSTALLED_PACKAGES);
             return getKeepUninstalledPackagesLocked();
         }
     }
@@ -6110,7 +6408,8 @@
             try {
                 // TODO Send to system too?
                 mContext.sendBroadcastAsUser(
-                        new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+                        new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED)
+                                .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND),
                         UserHandle.of(userId));
             } finally {
                 mInjector.binderRestoreCallingIdentity(ident);
@@ -6146,6 +6445,13 @@
         }
     }
 
+    private boolean isProfileOwnerPackage(String packageName, int userId) {
+        synchronized (this) {
+            return mOwners.hasProfileOwner(userId)
+                    && mOwners.getProfileOwnerPackage(userId).equals(packageName);
+        }
+    }
+
     public boolean isProfileOwner(ComponentName who, int userId) {
         final ComponentName profileOwner = getProfileOwner(userId);
         return who != null && who.equals(profileOwner);
@@ -6253,6 +6559,7 @@
                 clearDeviceOwnerLocked(admin, deviceOwnerUserId);
                 removeActiveAdminLocked(deviceOwnerComponent, deviceOwnerUserId);
                 Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
+                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
                 mContext.sendBroadcastAsUser(intent, UserHandle.of(deviceOwnerUserId));
             } finally {
                 mInjector.binderRestoreCallingIdentity(ident);
@@ -6387,11 +6694,11 @@
     }
 
     private void clearUserPoliciesLocked(int userId) {
-        // Reset some of the user-specific policies
-        DevicePolicyData policy = getUserData(userId);
+        // Reset some of the user-specific policies.
+        final DevicePolicyData policy = getUserData(userId);
         policy.mPermissionPolicy = DevicePolicyManager.PERMISSION_POLICY_PROMPT;
-        policy.mDelegatedCertInstallerPackage = null;
-        policy.mApplicationRestrictionsManagingPackage = null;
+        // Clear delegations.
+        policy.mDelegationMap.clear();
         policy.mStatusBarDisabled = false;
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         saveSettingsLocked(userId);
@@ -6975,66 +7282,31 @@
     @Override
     public boolean setApplicationRestrictionsManagingPackage(ComponentName admin,
             String packageName) {
-        Preconditions.checkNotNull(admin, "ComponentName is null");
-
-        final int userHandle = mInjector.userHandleGetCallingUserId();
-        synchronized (this) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            if (packageName != null && !isPackageInstalledForUser(packageName, userHandle)) {
-                return false;
-            }
-            DevicePolicyData policy = getUserData(userHandle);
-            policy.mApplicationRestrictionsManagingPackage = packageName;
-            saveSettingsLocked(userHandle);
-            return true;
+        try {
+            setDelegatedScopePreO(admin, packageName, DELEGATION_APP_RESTRICTIONS);
+        } catch (IllegalArgumentException e) {
+            return false;
         }
+        return true;
     }
 
     @Override
     public String getApplicationRestrictionsManagingPackage(ComponentName admin) {
-        Preconditions.checkNotNull(admin, "ComponentName is null");
-
-        final int userHandle = mInjector.userHandleGetCallingUserId();
-        synchronized (this) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            DevicePolicyData policy = getUserData(userHandle);
-            return policy.mApplicationRestrictionsManagingPackage;
-        }
+        final List<String> delegatePackages = getDelegatePackages(admin,
+                DELEGATION_APP_RESTRICTIONS);
+        return delegatePackages.size() > 0 ? delegatePackages.get(0) : null;
     }
 
     @Override
-    public boolean isCallerApplicationRestrictionsManagingPackage() {
-        final int callingUid = mInjector.binderGetCallingUid();
-        final int userHandle = UserHandle.getUserId(callingUid);
-        synchronized (this) {
-            final DevicePolicyData policy = getUserData(userHandle);
-            if (policy.mApplicationRestrictionsManagingPackage == null) {
-                return false;
-            }
-
-            try {
-                int uid = mInjector.getPackageManager().getPackageUidAsUser(
-                        policy.mApplicationRestrictionsManagingPackage, userHandle);
-                return uid == callingUid;
-            } catch (NameNotFoundException e) {
-                return false;
-            }
-        }
-    }
-
-    private void enforceCanManageApplicationRestrictions(ComponentName who) {
-        if (who != null) {
-            enforceProfileOrDeviceOwner(who);
-        } else if (!isCallerApplicationRestrictionsManagingPackage()) {
-            throw new SecurityException(
-                    "No admin component given, and caller cannot manage application restrictions "
-                    + "for other apps.");
-        }
+    public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) {
+        return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS);
     }
 
     @Override
-    public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) {
-        enforceCanManageApplicationRestrictions(who);
+    public void setApplicationRestrictions(ComponentName who, String callerPackage,
+            String packageName, Bundle settings) {
+        enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                DELEGATION_APP_RESTRICTIONS);
 
         final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
         final long id = mInjector.binderClearCallingIdentity();
@@ -7724,8 +7996,10 @@
     }
 
     @Override
-    public Bundle getApplicationRestrictions(ComponentName who, String packageName) {
-        enforceCanManageApplicationRestrictions(who);
+    public Bundle getApplicationRestrictions(ComponentName who, String callerPackage,
+            String packageName) {
+        enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                DELEGATION_APP_RESTRICTIONS);
 
         final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
         final long id = mInjector.binderClearCallingIdentity();
@@ -7740,12 +8014,13 @@
     }
 
     @Override
-    public String[] setPackagesSuspended(ComponentName who, String[] packageNames,
-            boolean suspended) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+    public String[] setPackagesSuspended(ComponentName who, String callerPackage,
+            String[] packageNames, boolean suspended) {
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or a package access delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_PACKAGE_ACCESS);
 
             long id = mInjector.binderClearCallingIdentity();
             try {
@@ -7762,10 +8037,12 @@
     }
 
     @Override
-    public boolean isPackageSuspended(ComponentName who, String packageName) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+    public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
+            // Ensure the caller is a DO/PO or a package access delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_PACKAGE_ACCESS);
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
 
             long id = mInjector.binderClearCallingIdentity();
@@ -7877,12 +8154,13 @@
     }
 
     @Override
-    public boolean setApplicationHidden(ComponentName who, String packageName,
+    public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName,
             boolean hidden) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or a package access delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_PACKAGE_ACCESS);
 
             long id = mInjector.binderClearCallingIdentity();
             try {
@@ -7899,11 +8177,13 @@
     }
 
     @Override
-    public boolean isApplicationHidden(ComponentName who, String packageName) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+    public boolean isApplicationHidden(ComponentName who, String callerPackage,
+            String packageName) {
         int callingUserId = UserHandle.getCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or a package access delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_PACKAGE_ACCESS);
 
             long id = mInjector.binderClearCallingIdentity();
             try {
@@ -7920,12 +8200,11 @@
     }
 
     @Override
-    public void enableSystemApp(ComponentName who, String packageName) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+    public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
         synchronized (this) {
-            // This API can only be called by an active device admin,
-            // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or an enable system app delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_ENABLE_SYSTEM_APP);
 
             int userId = UserHandle.getCallingUserId();
             long id = mInjector.binderClearCallingIdentity();
@@ -7955,12 +8234,11 @@
     }
 
     @Override
-    public int enableSystemAppWithIntent(ComponentName who, Intent intent) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
+    public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
         synchronized (this) {
-            // This API can only be called by an active device admin,
-            // so try to retrieve it to check that the caller is one.
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or an enable system app delegate.
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_ENABLE_SYSTEM_APP);
 
             int userId = UserHandle.getCallingUserId();
             long id = mInjector.binderClearCallingIdentity();
@@ -8059,12 +8337,13 @@
     }
 
     @Override
-    public void setUninstallBlocked(ComponentName who, String packageName,
+    public void setUninstallBlocked(ComponentName who, String callerPackage, String packageName,
             boolean uninstallBlocked) {
-        Preconditions.checkNotNull(who, "ComponentName is null");
         final int userId = UserHandle.getCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or a block uninstall delegate
+            enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_BLOCK_UNINSTALL);
 
             long id = mInjector.binderClearCallingIdentity();
             try {
@@ -8678,18 +8957,20 @@
 
             ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
             if (profileOwner != null) {
-                return createShowAdminSupportIntent(profileOwner, userId);
+                return DevicePolicyManagerService.this
+                        .createShowAdminSupportIntent(profileOwner, userId);
             }
 
             final Pair<Integer, ComponentName> deviceOwner =
                     mOwners.getDeviceOwnerUserIdAndComponent();
             if (deviceOwner != null && deviceOwner.first == userId) {
-                return createShowAdminSupportIntent(deviceOwner.second, userId);
+                return DevicePolicyManagerService.this
+                        .createShowAdminSupportIntent(deviceOwner.second, userId);
             }
 
             // We're not specifying the device admin because there isn't one.
             if (useDefaultIfNoAdmin) {
-                return createShowAdminSupportIntent(null, userId);
+                return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
             }
             return null;
         }
@@ -8717,11 +8998,12 @@
             if (enforcedByDo && enforcedByPo) {
                 // In this case, we'll show an admin support dialog that does not
                 // specify the admin.
-                return createShowAdminSupportIntent(null, userId);
+                return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
             } else if (enforcedByPo) {
                 final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
                 if (profileOwner != null) {
-                    return createShowAdminSupportIntent(profileOwner, userId);
+                    return DevicePolicyManagerService.this
+                            .createShowAdminSupportIntent(profileOwner, userId);
                 }
                 // This could happen if another thread has changed the profile owner since we called
                 // getUserRestrictionSource
@@ -8730,7 +9012,8 @@
                 final Pair<Integer, ComponentName> deviceOwner
                         = mOwners.getDeviceOwnerUserIdAndComponent();
                 if (deviceOwner != null) {
-                    return createShowAdminSupportIntent(deviceOwner.second, deviceOwner.first);
+                    return DevicePolicyManagerService.this
+                            .createShowAdminSupportIntent(deviceOwner.second, deviceOwner.first);
                 }
                 // This could happen if another thread has changed the device owner since we called
                 // getUserRestrictionSource
@@ -8738,15 +9021,57 @@
             }
             return null;
         }
+    }
 
-        private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
-            // This method is called with AMS lock held, so don't take DPMS lock
-            final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
-            intent.putExtra(Intent.EXTRA_USER_ID, userId);
-            intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            return intent;
+    private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
+        // This method is called with AMS lock held, so don't take DPMS lock
+        final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
+        intent.putExtra(Intent.EXTRA_USER_ID, userId);
+        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return intent;
+    }
+
+    @Override
+    public Intent createAdminSupportIntent(String restriction) {
+        Preconditions.checkNotNull(restriction);
+        final int uid = mInjector.binderGetCallingUid();
+        final int userId = UserHandle.getUserId(uid);
+        Intent intent = null;
+        if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction) ||
+                DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+            synchronized(this) {
+                final DevicePolicyData policy = getUserData(userId);
+                final int N = policy.mAdminList.size();
+                for (int i = 0; i < N; i++) {
+                    final ActiveAdmin admin = policy.mAdminList.get(i);
+                    if ((admin.disableCamera &&
+                                DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) ||
+                        (admin.disableScreenCapture && DevicePolicyManager
+                                .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+                        intent = createShowAdminSupportIntent(admin.info.getComponent(), userId);
+                        break;
+                    }
+                }
+                // For the camera, a device owner on a different user can disable it globally,
+                // so we need an additional check.
+                if (intent == null
+                        && DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+                    final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+                    if (admin != null && admin.disableCamera) {
+                        intent = createShowAdminSupportIntent(admin.info.getComponent(),
+                                mOwners.getDeviceOwnerUserId());
+                    }
+                }
+            }
+        } else {
+            // if valid, |restriction| can only be a user restriction
+            intent = mLocalService.createUserRestrictionSupportIntent(userId, restriction);
         }
+        if (intent != null) {
+            intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, restriction);
+        }
+        return intent;
     }
 
     /**
@@ -8891,10 +9216,13 @@
     }
 
     @Override
-    public void setPermissionPolicy(ComponentName admin, int policy) throws RemoteException {
+    public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy)
+            throws RemoteException {
         int userId = UserHandle.getCallingUserId();
         synchronized (this) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or a permission grant state delegate.
+            enforceCanManageScope(admin, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_PERMISSION_GRANT);
             DevicePolicyData userPolicy = getUserData(userId);
             if (userPolicy.mPermissionPolicy != policy) {
                 userPolicy.mPermissionPolicy = policy;
@@ -8913,11 +9241,13 @@
     }
 
     @Override
-    public boolean setPermissionGrantState(ComponentName admin, String packageName,
-            String permission, int grantState) throws RemoteException {
+    public boolean setPermissionGrantState(ComponentName admin, String callerPackage,
+            String packageName, String permission, int grantState) throws RemoteException {
         UserHandle user = mInjector.binderGetCallingUserHandle();
         synchronized (this) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            // Ensure the caller is a DO/PO or a permission grant state delegate.
+            enforceCanManageScope(admin, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_PERMISSION_GRANT);
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 if (getTargetSdk(packageName, user.getIdentifier())
@@ -8957,13 +9287,16 @@
     }
 
     @Override
-    public int getPermissionGrantState(ComponentName admin, String packageName,
-            String permission) throws RemoteException {
+    public int getPermissionGrantState(ComponentName admin, String callerPackage,
+            String packageName, String permission) throws RemoteException {
         PackageManager packageManager = mInjector.getPackageManager();
 
         UserHandle user = mInjector.binderGetCallingUserHandle();
         enforceProfileOwnerOrSystemUser(admin);
         synchronized (this) {
+            // Ensure the caller is a DO/PO or a permission grant state delegate.
+            enforceCanManageScope(admin, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                    DELEGATION_PERMISSION_GRANT);
             long ident = mInjector.binderClearCallingIdentity();
             try {
                 int granted = mIPackageManager.checkPermission(permission,
@@ -9119,21 +9452,25 @@
         final long ident = mInjector.binderClearCallingIdentity();
         try {
             final UserHandle callingUserHandle = UserHandle.of(callingUserId);
-            if (mUserManager.hasUserRestriction(
-                    UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle)) {
-                // The DO can initiate provisioning if the restriction was set by the DO.
-                if (!isDeviceOwnerPackage(packageName, callingUserId)
-                        || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(),
-                                UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
-                    // Caller is not DO or the restriction was set by the system.
+            final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId);
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+                    callingUserHandle)) {
+                // An admin can initiate provisioning if it has set the restriction.
+                if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
+                        UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
                     return CODE_ADD_MANAGED_PROFILE_DISALLOWED;
                 }
             }
-
-            // TODO: Allow it if the caller is the DO? DO could just call removeUser() before
-            // provisioning, so not strictly required...
-            boolean canRemoveProfile = !mUserManager.hasUserRestriction(
-                        UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle);
+            boolean canRemoveProfile = true;
+            if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                    callingUserHandle)) {
+                // We can remove a profile if the admin itself has set the restriction.
+                if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
+                        UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                        callingUserId)) {
+                    canRemoveProfile = false;
+                }
+            }
             if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
                 return CODE_CANNOT_ADD_MANAGED_PROFILE;
             }
@@ -9143,6 +9480,16 @@
         return CODE_OK;
     }
 
+    private ComponentName getOwnerComponent(String packageName, int userId) {
+        if (isDeviceOwnerPackage(packageName, userId)) {
+            return mOwners.getDeviceOwnerComponent();
+        }
+        if (isProfileOwnerPackage(packageName, userId)) {
+            return mOwners.getProfileOwnerComponent(userId);
+        }
+        return null;
+    }
+
     private int checkManagedUserProvisioningPreCondition(int callingUserId) {
         if (!hasFeatureManagedUsers()) {
             return CODE_MANAGED_USERS_NOT_SUPPORTED;
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 785c3fa..af19acb 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -58,6 +58,7 @@
 import android.provider.CallLog;
 import android.provider.MediaStore;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
 import android.util.Slog;
 import com.android.internal.os.BackgroundThread;
@@ -105,7 +106,8 @@
     private static final String DEMO_SESSION_COUNT = "retail_demo_session_count";
     private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration";
 
-    boolean mDeviceInDemoMode = false;
+    boolean mDeviceInDemoMode;
+    boolean mIsCarrierDemoMode;
     int mCurrentUserId = UserHandle.USER_SYSTEM;
     long mUserInactivityTimeout;
     long mWarningDialogTimeout;
@@ -135,7 +137,8 @@
             if (!mDeviceInDemoMode) {
                 return;
             }
-            switch (intent.getAction()) {
+            final String action = intent.getAction();
+            switch (action) {
                 case Intent.ACTION_SCREEN_OFF:
                     mHandler.removeMessages(MSG_TURN_SCREEN_ON);
                     mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
@@ -166,7 +169,7 @@
                     mInjector.acquireWakeLock();
                     break;
                 case MSG_INACTIVITY_TIME_OUT:
-                    if (isDemoLauncherDisabled()) {
+                    if (!mIsCarrierDemoMode && isDemoLauncherDisabled()) {
                         Slog.i(TAG, "User inactivity timeout reached");
                         showInactivityCountdownDialog();
                     }
@@ -177,12 +180,30 @@
                     }
                     removeMessages(MSG_START_NEW_SESSION);
                     removeMessages(MSG_INACTIVITY_TIME_OUT);
-                    if (mCurrentUserId != UserHandle.USER_SYSTEM) {
+                    if (!mIsCarrierDemoMode && mCurrentUserId != UserHandle.USER_SYSTEM) {
                         logSessionDuration();
                     }
-                    final UserInfo demoUser = mInjector.getUserManager().createUser(DEMO_USER_NAME,
-                            UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
-                    if (demoUser != null) {
+
+                    final UserManager um = mInjector.getUserManager();
+                    UserInfo demoUser = null;
+                    if (mIsCarrierDemoMode) {
+                        // Re-use the existing demo user in carrier demo mode.
+                        for (UserInfo user : um.getUsers()) {
+                            if (user.isDemo()) {
+                                demoUser = user;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (demoUser == null) {
+                        // User in carrier demo mode should survive reboots.
+                        final int flags = UserInfo.FLAG_DEMO
+                                | (mIsCarrierDemoMode ? 0 : UserInfo.FLAG_EPHEMERAL);
+                        demoUser = um.createUser(DEMO_USER_NAME, flags);
+                    }
+
+                    if (demoUser != null && mCurrentUserId != demoUser.id) {
                         setupDemoUser(demoUser);
                         mInjector.switchUser(demoUser.id);
                     }
@@ -211,7 +232,7 @@
         }
 
         public void register() {
-            ContentResolver cr = mInjector.getContentResolver();
+            final ContentResolver cr = mInjector.getContentResolver();
             cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM);
             cr.registerContentObserver(mRetailDemoConstantsUri, false, this,
@@ -224,30 +245,31 @@
                 refreshTimeoutConstants();
                 return;
             }
-            if (mDeviceDemoModeUri.equals(uri)) {
-                mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
-                if (mDeviceInDemoMode) {
+
+            // If device is provisioned and left demo mode - run the cleanup in demo folder
+            if (isDeviceProvisioned()) {
+                if (UserManager.isDeviceInDemoMode(getContext())) {
                     startDemoMode();
                 } else {
                     mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
+
+                    // Run on the bg thread to not block the fg thread
+                    BackgroundThread.getHandler().post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (!deletePreloadsFolderContents()) {
+                                Slog.w(TAG, "Failed to delete preloads folder contents");
+                            }
+                        }
+                    });
+
+                    stopDemoMode();
+
                     if (mInjector.isWakeLockHeld()) {
                         mInjector.releaseWakeLock();
                     }
                 }
             }
-            // If device is provisioned and left demo mode - run the cleanup in demo folder
-            if (!mDeviceInDemoMode && isDeviceProvisioned()) {
-                // Run on the bg thread to not block the fg thread
-                BackgroundThread.getHandler().post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!deletePreloadsFolderContents()) {
-                            Slog.w(TAG, "Failed to delete preloads folder contents");
-                        }
-                    }
-                });
-                stopDemoMode();
-            }
         }
 
         private void refreshTimeoutConstants() {
@@ -300,23 +322,22 @@
     }
 
     boolean isDemoLauncherDisabled() {
-        IPackageManager pm = mInjector.getIPackageManager();
         int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
-        String demoLauncherComponent = getContext().getResources()
-                .getString(R.string.config_demoModeLauncherComponent);
         try {
-            enabledState = pm.getComponentEnabledSetting(
-                    ComponentName.unflattenFromString(demoLauncherComponent),
-                    mCurrentUserId);
-        } catch (RemoteException exc) {
-            Slog.e(TAG, "Unable to talk to Package Manager", exc);
+            final IPackageManager iPm = mInjector.getIPackageManager();
+            final String demoLauncherComponent =
+                    getContext().getString(R.string.config_demoModeLauncherComponent);
+            enabledState = iPm.getComponentEnabledSetting(
+                    ComponentName.unflattenFromString(demoLauncherComponent), mCurrentUserId);
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Error retrieving demo launcher enabled setting", re);
         }
         return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
     }
 
     private void setupDemoUser(UserInfo userInfo) {
-        UserManager um = mInjector.getUserManager();
-        UserHandle user = UserHandle.of(userInfo.id);
+        final UserManager um = mInjector.getUserManager();
+        final UserHandle user = UserHandle.of(userInfo.id);
         um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
         um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
         um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
@@ -327,6 +348,7 @@
         um.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, user);
         // Disallow rebooting in safe mode - controlled by user 0
         um.setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM);
+
         Settings.Secure.putIntForUser(mInjector.getContentResolver(),
                 Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
         Settings.Global.putInt(mInjector.getContentResolver(),
@@ -334,6 +356,47 @@
 
         grantRuntimePermissionToCamera(user);
         clearPrimaryCallLog();
+
+        if (!mIsCarrierDemoMode) {
+            // Enable demo launcher.
+            final String demoLauncher = getContext().getString(
+                    R.string.config_demoModeLauncherComponent);
+            if (!TextUtils.isEmpty(demoLauncher)) {
+                final ComponentName componentToEnable =
+                        ComponentName.unflattenFromString(demoLauncher);
+                final String packageName = componentToEnable.getPackageName();
+                try {
+                    final IPackageManager iPm = AppGlobals.getPackageManager();
+                    iPm.setComponentEnabledSetting(componentToEnable,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id);
+                    iPm.setApplicationEnabledSetting(packageName,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
+                } catch (RemoteException re) {
+                    // Internal, shouldn't happen
+                }
+            }
+        } else {
+            // Set the carrier demo mode setting for the demo user.
+            final String carrierDemoModeSetting = getContext().getString(
+                    R.string.config_carrierDemoModeSetting);
+            Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                    carrierDemoModeSetting, 1, userInfo.id);
+
+            // Enable packages for carrier demo mode.
+            final String packageList = getContext().getString(
+                    R.string.config_carrierDemoModePackages);
+            final String[] packageNames = packageList == null ? new String[0]
+                    : TextUtils.split(packageList, ",");
+            final IPackageManager iPm = AppGlobals.getPackageManager();
+            for (String packageName : packageNames) {
+                try {
+                    iPm.setApplicationEnabledSetting(packageName,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
+                } catch (RemoteException re) {
+                    Slog.e(TAG, "Error enabling application: " + packageName, re);
+                }
+            }
+        }
     }
 
     private void grantRuntimePermissionToCamera(UserHandle user) {
@@ -385,13 +448,17 @@
     }
 
     private void registerBroadcastReceiver() {
-        if (mBroadcastReceiver == null) {
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(Intent.ACTION_SCREEN_OFF);
-            filter.addAction(ACTION_RESET_DEMO);
-            mBroadcastReceiver = new IntentReceiver();
-            getContext().registerReceiver(mBroadcastReceiver, filter);
+        if (mBroadcastReceiver != null) {
+            return;
         }
+
+        final IntentFilter filter = new IntentFilter();
+        if (!mIsCarrierDemoMode) {
+            filter.addAction(Intent.ACTION_SCREEN_OFF);
+        }
+        filter.addAction(ACTION_RESET_DEMO);
+        mBroadcastReceiver = new IntentReceiver();
+        getContext().registerReceiver(mBroadcastReceiver, filter);
     }
 
     private void unregisterBroadcastReceiver() {
@@ -427,6 +494,8 @@
     }
 
     private void startDemoMode() {
+        mDeviceInDemoMode = true;
+
         mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
         mInjector.initializeWakeLock();
         if (mCameraIdsWithFlash == null) {
@@ -434,6 +503,12 @@
         }
         registerBroadcastReceiver();
 
+        final String carrierDemoModeSetting =
+                getContext().getString(R.string.config_carrierDemoModeSetting);
+        mIsCarrierDemoMode = !TextUtils.isEmpty(carrierDemoModeSetting)
+                && (Settings.Secure.getInt(getContext().getContentResolver(),
+                        carrierDemoModeSetting, 0) == 1);
+
         mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
         mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
 
@@ -471,13 +546,12 @@
     public void onBootPhase(int bootPhase) {
         switch (bootPhase) {
             case PHASE_THIRD_PARTY_APPS_CAN_START:
-                SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+                final SettingsObserver settingsObserver = new SettingsObserver(mHandler);
                 settingsObserver.register();
                 settingsObserver.refreshTimeoutConstants();
                 break;
             case PHASE_BOOT_COMPLETED:
                 if (UserManager.isDeviceInDemoMode(getContext())) {
-                    mDeviceInDemoMode = true;
                     startDemoMode();
                 }
                 break;
@@ -497,33 +571,39 @@
             Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
             return;
         }
-        if (!mInjector.isWakeLockHeld()) {
+        if (!mIsCarrierDemoMode && !mInjector.isWakeLockHeld()) {
             mInjector.acquireWakeLock();
         }
         mCurrentUserId = userId;
         mInjector.getActivityManagerInternal().updatePersistentConfigurationForUser(
                 mInjector.getSystemUsersConfiguration(), userId);
+
         mInjector.turnOffAllFlashLights(mCameraIdsWithFlash);
         muteVolumeStreams();
         if (!mInjector.getWifiManager().isWifiEnabled()) {
             mInjector.getWifiManager().setWifiEnabled(true);
         }
+
         // Disable lock screen for demo users.
         mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId);
-        mInjector.getNotificationManager().notifyAsUser(TAG,
-                1, mInjector.createResetNotification(), UserHandle.of(userId));
 
-        synchronized (mActivityLock) {
-            mUserUntouched = true;
-        }
-        mInjector.logSessionCount(1);
-        mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mPreloadAppsInstaller.installApps(userId);
+        if (!mIsCarrierDemoMode) {
+            // Show reset notification (except in carrier demo mode).
+            mInjector.getNotificationManager().notifyAsUser(TAG,
+                    1, mInjector.createResetNotification(), UserHandle.of(userId));
+
+            synchronized (mActivityLock) {
+                mUserUntouched = true;
             }
-        });
+            mInjector.logSessionCount(1);
+            mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mPreloadAppsInstaller.installApps(userId);
+                }
+            });
+        }
     }
 
     private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
@@ -531,7 +611,7 @@
 
         @Override
         public void onUserActivity() {
-            if (!mDeviceInDemoMode) {
+            if (!mDeviceInDemoMode || mIsCarrierDemoMode) {
                 return;
             }
             long timeOfActivity = SystemClock.uptimeMillis();
@@ -682,7 +762,7 @@
         }
 
         boolean isWakeLockHeld() {
-            return mWakeLock.isHeld();
+            return mWakeLock != null && mWakeLock.isHeld();
         }
 
         void acquireWakeLock() {
diff --git a/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
new file mode 100644
index 0000000..b26bac3
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/BadgeExtractorTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BadgeExtractorTest {
+
+    @Mock RankingConfig mConfig;
+
+    private String mPkg = "com.android.server.notification";
+    private int mId = 1001;
+    private String mTag = null;
+    private int mUid = 1000;
+    private int mPid = 2000;
+    private UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    private NotificationRecord getNotificationRecord(NotificationChannel channel) {
+        final Builder builder = new Builder(getContext())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setDefaults(Notification.DEFAULT_SOUND);
+
+        Notification n = builder.build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
+                mPid, n, mUser, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+        return r;
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    //
+    // Tests
+    //
+
+    @Test
+    public void testAppYesChannelNo() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+        NotificationChannel channel =
+                new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+        channel.setShowBadge(false);
+
+        NotificationRecord r = getNotificationRecord(channel);
+
+        extractor.process(r);
+
+        assertFalse(r.canShowBadge());
+    }
+
+    @Test
+    public void testAppNoChannelYes() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+        NotificationChannel channel =
+                new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_HIGH);
+        channel.setShowBadge(true);
+
+        NotificationRecord r = getNotificationRecord(channel);
+
+        extractor.process(r);
+
+        assertFalse(r.canShowBadge());
+    }
+
+    @Test
+    public void testAppYesChannelYes() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(true);
+        NotificationChannel channel =
+                new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+        channel.setShowBadge(true);
+
+        NotificationRecord r = getNotificationRecord(channel);
+
+        extractor.process(r);
+
+        assertTrue(r.canShowBadge());
+    }
+
+    @Test
+    public void testAppNoChannelNo() throws Exception {
+        BadgeExtractor extractor = new BadgeExtractor();
+        extractor.setConfig(mConfig);
+
+        when(mConfig.canShowBadge(mPkg, mUid)).thenReturn(false);
+        NotificationChannel channel =
+                new NotificationChannel("a", "a", NotificationManager.IMPORTANCE_UNSPECIFIED);
+        channel.setShowBadge(false);
+
+        NotificationRecord r = getNotificationRecord(channel);
+
+        extractor.process(r);
+
+        assertFalse(r.canShowBadge());
+    }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ad436724a..468a26b 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -230,9 +230,9 @@
             n.flags |= Notification.FLAG_INSISTENT;
         }
 
-        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, id, mTag, mUid,
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
                 mPid, n, mUser, null, System.currentTimeMillis());
-        NotificationRecord r = new NotificationRecord(getContext(), sbn);
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
         mService.addNotification(r);
         return r;
     }
diff --git a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
index f48d785..936531b 100644
--- a/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/GroupHelperTest.java
@@ -70,9 +70,7 @@
         if (groupKey != null) {
             nb.setGroup(groupKey);
         }
-        NotificationChannel channel =
-                new NotificationChannel("test", "test", NotificationManager.IMPORTANCE_LOW);
-        return new StatusBarNotification(pkg, pkg, channel, id, tag, 0, 0, nb.build(), user, null,
+        return new StatusBarNotification(pkg, pkg, id, tag, 0, 0, nb.build(), user, null,
                 System.currentTimeMillis());
     }
 
diff --git a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
index eee9cf1..f8a32bb 100644
--- a/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ImportanceExtractorTest.java
@@ -69,9 +69,9 @@
                 .setDefaults(Notification.DEFAULT_SOUND);
 
         Notification n = builder.build();
-        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, channel, mId, mTag, mUid,
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, mId, mTag, mUid,
                 mPid, n, mUser, null, System.currentTimeMillis());
-        NotificationRecord r = new NotificationRecord(getContext(), sbn);
+        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
         return r;
     }
 
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
index 403b65c..aa08b41 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
@@ -105,8 +105,8 @@
                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
                 .build();
         mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
-                callPkg, getDefaultChannel(), 1, "minCall", callUid, callUid, n1,
-                new UserHandle(userId), "", 2000));
+                callPkg, 1, "minCall", callUid, callUid, n1,
+                new UserHandle(userId), "", 2000), getDefaultChannel());
         mRecordMinCall.setUserImportance(NotificationManager.IMPORTANCE_MIN);
 
         Notification n2 = new Notification.Builder(mContext)
@@ -114,8 +114,8 @@
                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
                 .build();
         mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
-                callPkg, getDefaultChannel(), 1, "highcall", callUid, callUid, n2,
-                new UserHandle(userId), "", 1999));
+                callPkg, 1, "highcall", callUid, callUid, n2,
+                new UserHandle(userId), "", 1999), getDefaultChannel());
         mRecordHighCall.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
 
         Notification n3 = new Notification.Builder(mContext)
@@ -124,43 +124,43 @@
                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
                 .build();
         mRecordDefaultMedia = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, getDefaultChannel(), 1, "media", uid2, uid2, n3, new UserHandle(userId),
-                "", 1499));
+                pkg2, 1, "media", uid2, uid2, n3, new UserHandle(userId),
+                "", 1499), getDefaultChannel());
         mRecordDefaultMedia.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
 
         Notification n4 = new Notification.Builder(mContext)
                 .setStyle(new Notification.MessagingStyle("sender!")).build();
         mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, getDefaultChannel(), 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
-                "", 1599));
+                pkg2, 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId),
+                "", 1599), getDefaultChannel());
         mRecordInlineReply.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
         mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX);
 
         Notification n5 = new Notification.Builder(mContext)
                 .setCategory(Notification.CATEGORY_MESSAGE).build();
         mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg,
-                smsPkg, getDefaultChannel(), 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
-                "", 1299));
+                smsPkg, 1, "sms", smsUid, smsUid, n5, new UserHandle(userId),
+                "", 1299), getDefaultChannel());
         mRecordSms.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
 
         Notification n6 = new Notification.Builder(mContext).build();
         mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, getDefaultChannel(), 1, "starred", uid2, uid2, n6, new UserHandle(userId),
-                "", 1259));
+                pkg2, 1, "starred", uid2, uid2, n6, new UserHandle(userId),
+                "", 1259), getDefaultChannel());
         mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT);
         mRecordStarredContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
 
         Notification n7 = new Notification.Builder(mContext).build();
         mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, getDefaultChannel(), 1, "contact", uid2, uid2, n7, new UserHandle(userId),
-                "", 1259));
+                pkg2, 1, "contact", uid2, uid2, n7, new UserHandle(userId),
+                "", 1259), getDefaultChannel());
         mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT);
         mRecordContact.setUserImportance(NotificationManager.IMPORTANCE_DEFAULT);
 
         Notification n8 = new Notification.Builder(mContext).build();
         mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, getDefaultChannel(), 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
-                "", 1258));
+                pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId),
+                "", 1258), getDefaultChannel());
         mRecordUrgent.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
 
         Notification n9 = new Notification.Builder(mContext)
@@ -169,15 +169,15 @@
                         |Notification.FLAG_FOREGROUND_SERVICE, true)
                 .build();
         mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, getDefaultChannel(), 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
-                "", 9258));
+                pkg2, 1, "cheater", uid2, uid2, n9, new UserHandle(userId),
+                "", 9258), getDefaultChannel());
         mRecordCheater.setUserImportance(NotificationManager.IMPORTANCE_LOW);
 
         Notification n10 = new Notification.Builder(mContext)
                 .setStyle(new Notification.InboxStyle().setSummaryText("message!")).build();
         mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2,
-                pkg2, getDefaultChannel(), 1, "email", uid2, uid2, n10, new UserHandle(userId),
-                "", 1599));
+                pkg2, 1, "email", uid2, uid2, n10, new UserHandle(userId),
+                "", 1599), getDefaultChannel());
         mRecordEmail.setUserImportance(NotificationManager.IMPORTANCE_HIGH);
     }
 
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
index b6166f6..f0f4c4d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -59,6 +59,7 @@
             assertEquals(getChannel(key, i), ranking.getChannel());
             assertEquals(getPeople(key, i), ranking.getAdditionalPeople());
             assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria());
+            assertEquals(getShowBadge(i), ranking.canShowBadge());
         }
     }
 
@@ -68,9 +69,10 @@
         Bundle overrideGroupKeys = new Bundle();
         Bundle suppressedVisualEffects = new Bundle();
         Bundle explanation = new Bundle();
-        Bundle overrideChannels = new Bundle();
+        Bundle channels = new Bundle();
         Bundle overridePeople = new Bundle();
         Bundle snoozeCriteria = new Bundle();
+        Bundle showBadge = new Bundle();
         int[] importance = new int[mKeys.length];
 
         for (int i = 0; i < mKeys.length; i++) {
@@ -83,14 +85,15 @@
             suppressedVisualEffects.putInt(key, getSuppressedVisualEffects(i));
             importance[i] = getImportance(i);
             explanation.putString(key, getExplanation(key));
-            overrideChannels.putParcelable(key, getChannel(key, i));
+            channels.putParcelable(key, getChannel(key, i));
             overridePeople.putStringArrayList(key, getPeople(key, i));
             snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i));
+            showBadge.putBoolean(key, getShowBadge(i));
         }
         NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
                 interceptedKeys.toArray(new String[0]), visibilityOverrides,
                 suppressedVisualEffects, importance, explanation, overrideGroupKeys,
-                overrideChannels, overridePeople, snoozeCriteria);
+                channels, overridePeople, snoozeCriteria, showBadge);
         return update;
     }
 
@@ -122,6 +125,10 @@
         return new NotificationChannel(key, key, getImportance(index));
     }
 
+    private boolean getShowBadge(int index) {
+        return index % 3 == 0;
+    }
+
     private ArrayList<String> getPeople(String key, int index) {
         ArrayList<String> people = new ArrayList<>();
         for (int i = 0; i < index; i++) {
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9b74fcc..250aab8 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -132,9 +132,9 @@
                 .setPriority(Notification.PRIORITY_HIGH)
                 .build();
         StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
-                mContext.getPackageName(), channel, 1, "tag", uid, 0,
+                mContext.getPackageName(), 1, "tag", uid, 0,
                 n, new UserHandle(uid), null, 0);
-        return new NotificationRecord(mContext, sbn);
+        return new NotificationRecord(mContext, sbn, channel);
     }
 
     @Test
@@ -361,17 +361,4 @@
                 mBinderService.getActiveNotifications(sbn.getPackageName());
         assertEquals(1, notifs.length);
     }
-
-    @Test
-    @UiThreadTest
-    public void testSnoozeNotificationImmediatelyAfterEnqueue() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
-                sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
-        mBinderService.snoozeNotificationFromListener(null, sbn.getKey());
-        waitForIdle();
-        StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(sbn.getPackageName());
-        assertEquals(0, notifs.length);
-    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index fc94271f..15dcc26 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -136,10 +136,10 @@
 
         Notification n = builder.build();
         if (preO) {
-            return new StatusBarNotification(pkg, pkg, defaultChannel, id1, tag1, uid, uid, n,
+            return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n,
                     mUser, null, uid);
         } else {
-            return new StatusBarNotification(pkg2, pkg2, channel, id2, tag2, uid2, uid2, n,
+            return new StatusBarNotification(pkg2, pkg2, id2, tag2, uid2, uid2, n,
                     mUser, null, uid2);
         }
     }
@@ -155,7 +155,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
     }
 
@@ -166,7 +166,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
                 false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(CUSTOM_SOUND, record.getSound());
     }
 
@@ -178,7 +178,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(CUSTOM_SOUND, record.getSound());
     }
 
@@ -189,7 +189,7 @@
         StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
         assertEquals(CUSTOM_SOUND, record.getSound());
     }
 
@@ -200,7 +200,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
                 false /* defaultSound */, true /* buzzy */, true /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertNotNull(record.getVibration());
     }
 
@@ -211,7 +211,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
                 false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(CUSTOM_VIBRATION, record.getVibration());
     }
 
@@ -223,7 +223,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, false /* noisy */,
                 false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertTrue(!Objects.equals(CUSTOM_VIBRATION, record.getVibration()));
     }
 
@@ -234,7 +234,7 @@
         StatusBarNotification sbn = getNotification(false /*preO */, false /* noisy */,
                 false /* defaultSound */, true /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
         assertEquals(CUSTOM_CHANNEL_VIBRATION, record.getVibration());
     }
 
@@ -245,7 +245,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
                 false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
     }
 
@@ -256,7 +256,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
                 false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
     }
 
@@ -264,7 +264,7 @@
     public void testImportance_preUpgrade() throws Exception {
         StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(NotificationManager.IMPORTANCE_HIGH, record.getImportance());
     }
 
@@ -275,7 +275,7 @@
         StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
 
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
         assertEquals(NotificationManager.IMPORTANCE_LOW, record.getImportance());
     }
 
@@ -283,7 +283,7 @@
     public void testImportance_upgrade() throws Exception {
         StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
                 true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
         assertEquals(NotificationManager.IMPORTANCE_DEFAULT, record.getImportance());
     }
 }
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 59ac427..0320d8a 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -105,8 +105,8 @@
                 .setWhen(1205)
                 .build();
         mRecordGroupGSortA = new NotificationRecord(getContext(), new StatusBarNotification(
-                "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortA, user,
-                null, System.currentTimeMillis()));
+                "package", "package", 1, null, 0, 0, mNotiGroupGSortA, user,
+                null, System.currentTimeMillis()), getDefaultChannel());
 
         mNotiGroupGSortB = new Notification.Builder(getContext())
                 .setContentTitle("B")
@@ -115,24 +115,24 @@
                 .setWhen(1200)
                 .build();
         mRecordGroupGSortB = new NotificationRecord(getContext(), new StatusBarNotification(
-                "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiGroupGSortB, user,
-                null, System.currentTimeMillis()));
+                "package", "package", 1, null, 0, 0, mNotiGroupGSortB, user,
+                null, System.currentTimeMillis()), getDefaultChannel());
 
         mNotiNoGroup = new Notification.Builder(getContext())
                 .setContentTitle("C")
                 .setWhen(1201)
                 .build();
         mRecordNoGroup = new NotificationRecord(getContext(), new StatusBarNotification(
-                "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup, user,
-                null, System.currentTimeMillis()));
+                "package", "package", 1, null, 0, 0, mNotiNoGroup, user,
+                null, System.currentTimeMillis()), getDefaultChannel());
 
         mNotiNoGroup2 = new Notification.Builder(getContext())
                 .setContentTitle("D")
                 .setWhen(1202)
                 .build();
         mRecordNoGroup2 = new NotificationRecord(getContext(), new StatusBarNotification(
-                "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroup2, user,
-                null, System.currentTimeMillis()));
+                "package", "package", 1, null, 0, 0, mNotiNoGroup2, user,
+                null, System.currentTimeMillis()), getDefaultChannel());
 
         mNotiNoGroupSortA = new Notification.Builder(getContext())
                 .setContentTitle("E")
@@ -140,8 +140,8 @@
                 .setSortKey("A")
                 .build();
         mRecordNoGroupSortA = new NotificationRecord(getContext(), new StatusBarNotification(
-                "package", "package", getDefaultChannel(), 1, null, 0, 0, mNotiNoGroupSortA, user,
-                null, System.currentTimeMillis()));
+                "package", "package", 1, null, 0, 0, mNotiNoGroupSortA, user,
+                null, System.currentTimeMillis()), getDefaultChannel());
 
         final ApplicationInfo legacy = new ApplicationInfo();
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
@@ -254,8 +254,12 @@
         mHelper.createNotificationChannel(pkg, uid, channel1, true);
         mHelper.createNotificationChannel(pkg, uid, channel2, false);
 
+        mHelper.setShowBadge(pkg, uid, true);
+        mHelper.setShowBadge(pkg2, uid2, false);
+
         ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, channel1.getId(), channel2.getId(),
                 NotificationChannel.DEFAULT_CHANNEL_ID);
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
 
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -263,6 +267,8 @@
         parser.nextTag();
         mHelper.readXml(parser, false);
 
+        assertFalse(mHelper.canShowBadge(pkg2, uid2));
+        assertTrue(mHelper.canShowBadge(pkg, uid));
         assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
         compareChannels(channel2,
                 mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
@@ -758,4 +764,11 @@
         mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
         assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
     }
+
+    @Test
+    public void testRecordDefaults() throws Exception {
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+        assertEquals(true, mHelper.canShowBadge(pkg, uid));
+        assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
index 460fcdf..69724f4 100644
--- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
@@ -33,6 +33,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static org.mockito.Matchers.any;
@@ -42,6 +43,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 
 @SmallTest
@@ -190,6 +192,39 @@
         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
     }
 
+    @Test
+    public void testGetSnoozedByUser() throws Exception {
+        NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+        NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+        NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+        NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
+        mSnoozeHelper.snooze(r, 1000);
+        mSnoozeHelper.snooze(r2, 1000);
+        mSnoozeHelper.snooze(r3, 1000);
+        mSnoozeHelper.snooze(r4, 1000);
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+                new int[] {UserHandle.USER_SYSTEM});
+        assertEquals(3, mSnoozeHelper.getSnoozed().size());
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+                new int[] {UserHandle.USER_CURRENT});
+        assertEquals(1, mSnoozeHelper.getSnoozed().size());
+    }
+
+    @Test
+    public void testGetSnoozedByUser_managedProfiles() throws Exception {
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(
+                new int[] {UserHandle.USER_SYSTEM, UserHandle.USER_CURRENT});
+        NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+        NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+        NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+        NotificationRecord r4 = getNotificationRecord("pkg2", 3, "three", UserHandle.CURRENT);
+        mSnoozeHelper.snooze(r, 1000);
+        mSnoozeHelper.snooze(r2, 1000);
+        mSnoozeHelper.snooze(r3, 1000);
+        mSnoozeHelper.snooze(r4, 1000);
+        assertEquals(4, mSnoozeHelper.getSnoozed().size());
+    }
+
     private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
             UserHandle user) {
         Notification n = new Notification.Builder(getContext())
@@ -199,8 +234,8 @@
                 .setWhen(1205)
                 .build();
         return new NotificationRecord(getContext(), new StatusBarNotification(
-                pkg, pkg, getDefaultChannel(), id, tag, 0, 0, n, user, null,
-                System.currentTimeMillis()));
+                pkg, pkg, id, tag, 0, 0, n, user, null,
+                System.currentTimeMillis()), getDefaultChannel());
     }
 
     private NotificationChannel getDefaultChannel() {
diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
new file mode 100644
index 0000000..c89d35c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java
@@ -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
+ */
+
+package com.android.server;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IProgressListener;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.os.storage.IStorageManager;
+import android.security.KeyStore;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.test.AndroidTestCase;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LockSettingsService.SynchronizedStrongAuthTracker;
+import com.android.server.LockSettingsStorage.CredentialHash;
+import com.android.server.MockGateKeeperService.AuthToken;
+import com.android.server.MockGateKeeperService.VerifyHandle;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+public class BaseLockSettingsServiceTests extends AndroidTestCase {
+    protected static final int PRIMARY_USER_ID = 0;
+    protected static final int MANAGED_PROFILE_USER_ID = 12;
+    protected static final int SECONDARY_USER_ID = 20;
+
+    private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
+            UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+    private static final UserInfo MANAGED_PROFILE_INFO = new UserInfo(MANAGED_PROFILE_USER_ID, null,
+            null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+    private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
+            UserInfo.FLAG_INITIALIZED);
+
+    LockSettingsService mService;
+
+    MockLockSettingsContext mContext;
+    LockSettingsStorageTestable mStorage;
+
+    LockPatternUtils mLockPatternUtils;
+    MockGateKeeperService mGateKeeperService;
+    NotificationManager mNotificationManager;
+    UserManager mUserManager;
+    MockStorageManager mStorageManager;
+    IActivityManager mActivityManager;
+
+    KeyStore mKeyStore;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mLockPatternUtils = mock(LockPatternUtils.class);
+        mGateKeeperService = new MockGateKeeperService();
+        mNotificationManager = mock(NotificationManager.class);
+        mUserManager = mock(UserManager.class);
+        mStorageManager = new MockStorageManager();
+        mActivityManager = mock(IActivityManager.class);
+        mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager);
+        mStorage = new LockSettingsStorageTestable(mContext,
+                new File(getContext().getFilesDir(), "locksettings"));
+        File storageDir = mStorage.mStorageDir;
+        if (storageDir.exists()) {
+            FileUtils.deleteContents(storageDir);
+        } else {
+            storageDir.mkdirs();
+        }
+
+        mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils,
+                mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager);
+        when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
+        when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(Arrays.asList(
+                new UserInfo[] {PRIMARY_USER_INFO, MANAGED_PROFILE_INFO}));
+        when(mUserManager.getUserInfo(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
+                MANAGED_PROFILE_INFO);
+        when(mUserManager.getProfileParent(eq(MANAGED_PROFILE_USER_ID))).thenReturn(
+                PRIMARY_USER_INFO);
+        when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
+
+        when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer(
+                new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                mStorageManager.unlockUser((int)args[0], (byte[])args[2],
+                        (IProgressListener) args[3]);
+                return true;
+            }
+        });
+
+        when(mLockPatternUtils.getLockSettings()).thenReturn(mService);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mStorage.closeDatabase();
+        File db = getContext().getDatabasePath("locksettings.db");
+        assertTrue(!db.exists() || db.delete());
+
+        File storageDir = mStorage.mStorageDir;
+        assertTrue(FileUtils.deleteContents(storageDir));
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
new file mode 100644
index 0000000..613ec0b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.IActivityManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.storage.IStorageManager;
+import android.security.KeyStore;
+import android.service.gatekeeper.IGateKeeperService;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.FileNotFoundException;
+
+public class LockSettingsServiceTestable extends LockSettingsService {
+
+    private static class MockInjector extends LockSettingsService.Injector {
+
+        private LockSettingsStorage mLockSettingsStorage;
+        private KeyStore mKeyStore;
+        private IActivityManager mActivityManager;
+        private LockPatternUtils mLockPatternUtils;
+        private IStorageManager mStorageManager;
+
+        public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
+                IActivityManager activityManager, LockPatternUtils lockPatternUtils,
+                IStorageManager storageManager) {
+            super(context);
+            mLockSettingsStorage = storage;
+            mKeyStore = keyStore;
+            mActivityManager = activityManager;
+            mLockPatternUtils = lockPatternUtils;
+            mStorageManager = storageManager;
+        }
+
+        @Override
+        public Handler getHandler() {
+            return mock(Handler.class);
+        }
+
+        @Override
+        public LockSettingsStorage getStorage() {
+            return mLockSettingsStorage;
+        }
+
+        @Override
+        public LockSettingsStrongAuth getStrongAuth() {
+            return mock(LockSettingsStrongAuth.class);
+        }
+
+        @Override
+        public SynchronizedStrongAuthTracker getStrongAuthTracker() {
+            return mock(SynchronizedStrongAuthTracker.class);
+        }
+
+        @Override
+        public IActivityManager getActivityManager() {
+            return mActivityManager;
+        }
+
+        @Override
+        public LockPatternUtils getLockPatternUtils() {
+            return mLockPatternUtils;
+        }
+
+        @Override
+        public KeyStore getKeyStore() {
+            return mKeyStore;
+        }
+
+        @Override
+        public IStorageManager getStorageManager() {
+            return mStorageManager;
+        }
+    }
+
+    protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
+            LockSettingsStorage storage, IGateKeeperService gatekeeper, KeyStore keystore,
+            IStorageManager storageManager, IActivityManager mActivityManager) {
+        super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
+                storageManager));
+        mGateKeeperService = gatekeeper;
+    }
+
+    @Override
+    protected void tieProfileLockToParent(int userId, String password) {
+        mStorage.writeChildProfileLock(userId, password.getBytes());
+    }
+
+    @Override
+    protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException {
+        byte[] storedData = mStorage.readChildProfileLock(userId);
+        if (storedData == null) {
+            throw new FileNotFoundException("Child profile lock file not found");
+        }
+        return new String(storedData);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
new file mode 100644
index 0000000..4c2e171
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.LockSettingsStorage.CredentialHash;
+import com.android.server.MockGateKeeperService.VerifyHandle;
+
+/**
+ * runtest frameworks-services -c com.android.server.LockSettingsServiceTests
+ */
+public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testCreatePasswordPrimaryUser() throws RemoteException {
+        testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD);
+    }
+
+    public void testCreatePatternPrimaryUser() throws RemoteException {
+        testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN);
+    }
+
+    public void testChangePasswordPrimaryUser() throws RemoteException {
+        testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
+                "asdfghjk", CREDENTIAL_TYPE_PASSWORD);
+    }
+
+    public void testChangePatternPrimaryUser() throws RemoteException {
+        testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
+                "1596321", CREDENTIAL_TYPE_PATTERN);
+    }
+
+    public void testChangePasswordFailPrimaryUser() throws RemoteException {
+        final long sid = 1234;
+        final String FAILED_MESSAGE = "Failed to enroll password";
+        initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+
+        try {
+            mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd",
+                    PRIMARY_USER_ID);
+            fail("Did not fail when enrolling using incorrect credential");
+        } catch (RemoteException expected) {
+            assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+        }
+        try {
+            mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID);
+            fail("Did not fail when enrolling using incorrect credential");
+        } catch (RemoteException expected) {
+            assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
+        }
+        assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+    }
+
+    public void testClearPasswordPrimaryUser() throws RemoteException {
+        final String PASSWORD = "password";
+        initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
+        mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID);
+        assertFalse(mService.havePassword(PRIMARY_USER_ID));
+        assertFalse(mService.havePattern(PRIMARY_USER_ID));
+        assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+    }
+
+    public void testManagedProfileUnifiedChallenge() throws RemoteException {
+        final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd";
+        mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PRIMARY_USER_ID);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+        final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+        assertTrue(primarySid != 0);
+        assertTrue(profileSid != 0);
+        assertTrue(profileSid != primarySid);
+
+        // clear auth token and wait for verify challenge from primary user to re-generate it.
+        mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+        // verify credential
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                .getResponseCode());
+
+        // Verify that we have a new auth token for the profile
+        assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+        assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+        /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+         * credential as part of verifyCredential() before the new credential is committed in
+         * StorageManager. So we relax the check in our mock StorageManager to allow that.
+         */
+        mStorageManager.setIgnoreBadUnlock(true);
+        // Change primary password and verify that profile SID remains
+        mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                UnifiedPassword, PRIMARY_USER_ID);
+        mStorageManager.setIgnoreBadUnlock(false);
+        assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+    }
+
+    public void testManagedProfileSeparateChallenge() throws RemoteException {
+        final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
+        final String profilePassword = "testManagedProfileSeparateChallenge-profile";
+        mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                PRIMARY_USER_ID);
+        /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
+         * credential as part of verifyCredential() before the new credential is committed in
+         * StorageManager. So we relax the check in our mock StorageManager to allow that.
+         */
+        mStorageManager.setIgnoreBadUnlock(true);
+        mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
+                MANAGED_PROFILE_USER_ID);
+        mStorageManager.setIgnoreBadUnlock(false);
+
+        final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
+        final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
+        assertTrue(primarySid != 0);
+        assertTrue(profileSid != 0);
+        assertTrue(profileSid != primarySid);
+
+        // clear auth token and make sure verify challenge from primary user does not regenerate it.
+        mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
+        // verify primary credential
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                .getResponseCode());
+        assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+
+        // verify profile credential
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+                MANAGED_PROFILE_USER_ID).getResponseCode());
+        assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
+        assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+
+        // Change primary credential and make sure we don't affect profile
+        mStorageManager.setIgnoreBadUnlock(true);
+        mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                primaryPassword, PRIMARY_USER_ID);
+        mStorageManager.setIgnoreBadUnlock(false);
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
+                MANAGED_PROFILE_USER_ID).getResponseCode());
+        assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
+    }
+
+    private void testCreateCredential(int userId, String credential, int type)
+            throws RemoteException {
+        mService.setLockCredential(credential, type, null, userId);
+        assertVerifyCredentials(userId, credential, type, -1);
+    }
+
+    private void testChangeCredentials(int userId, String newCredential, int newType,
+            String oldCredential, int oldType) throws RemoteException {
+        final long sid = 1234;
+        initializeStorageWithCredential(userId, oldCredential, oldType, sid);
+        mService.setLockCredential(newCredential, newType, oldCredential, userId);
+        assertVerifyCredentials(userId, newCredential, newType, sid);
+    }
+
+    private void assertVerifyCredentials(int userId, String credential, int type, long sid)
+            throws RemoteException{
+        final long challenge = 54321;
+        VerifyCredentialResponse response = mService.verifyCredential(credential, type, challenge,
+                userId);
+
+        assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
+        if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
+        final int incorrectType;
+        if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+            assertTrue(mService.havePassword(userId));
+            assertFalse(mService.havePattern(userId));
+            incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+        } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
+            assertFalse(mService.havePassword(userId));
+            assertTrue(mService.havePattern(userId));
+            incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+        } else {
+            assertFalse(mService.havePassword(userId));
+            assertFalse(mService.havePassword(userId));
+            incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+        }
+        // check for bad type
+        assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(credential,
+                incorrectType, challenge, userId).getResponseCode());
+        // check for bad credential
+        assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential("0" + credential,
+                type, challenge, userId).getResponseCode());
+    }
+
+    private void initializeStorageWithCredential(int userId, String credential, int type, long sid) {
+        byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
+        if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+            mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+                    LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
+        } else {
+            mStorage.writeCredentialHash(CredentialHash.create(oldHash,
+                    LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
new file mode 100644
index 0000000..e81b02f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java
@@ -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
+ */
+
+package com.android.server;
+
+import android.content.Context;
+
+import java.io.File;
+
+public class LockSettingsStorageTestable extends LockSettingsStorage {
+
+    public File mStorageDir;
+
+    public LockSettingsStorageTestable(Context context, File storageDir) {
+        super(context);
+        mStorageDir = storageDir;
+    }
+
+    @Override
+    String getLockPatternFilename(int userId) {
+        return new File(mStorageDir,
+                super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
+    }
+
+    @Override
+    String getLockPasswordFilename(int userId) {
+        return new File(mStorageDir,
+                super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
+    }
+
+    @Override
+    String getChildProfileLockFile(int userId) {
+        return new File(mStorageDir,
+                super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
index 9d52153..d110fea 100644
--- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
@@ -60,46 +61,22 @@
         assertTrue(FileUtils.deleteContents(mStorageDir));
         assertTrue(!mDb.exists() || mDb.delete());
 
-        final Context ctx = getContext();
         final UserManager mockUserManager = mock(UserManager.class);
         // User 2 is a profile of user 1.
         when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0));
         // User 3 is a profile of user 0.
         when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
-        setContext(new ContextWrapper(ctx) {
-            @Override
-            public Object getSystemService(String name) {
-                if (USER_SERVICE.equals(name)) {
-                    return mockUserManager;
-                }
-                return super.getSystemService(name);
-            }
-        });
 
-        mStorage = new LockSettingsStorage(getContext(), new LockSettingsStorage.Callback() {
-            @Override
-            public void initialize(SQLiteDatabase db) {
-                mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
-            }
-        }) {
-            @Override
-            String getLockPatternFilename(int userId) {
-                return new File(mStorageDir,
-                        super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath();
-            }
-
-            @Override
-            String getLockPasswordFilename(int userId) {
-                return new File(mStorageDir,
-                        super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath();
-            }
-
-            @Override
-            String getChildProfileLockFile(int userId) {
-                return new File(mStorageDir,
-                        super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath();
-            }
-        };
+        MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager,
+                mock(NotificationManager.class));
+        mStorage = new LockSettingsStorageTestable(context,
+                new File(getContext().getFilesDir(), "locksettings"));
+        mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() {
+                    @Override
+                    public void initialize(SQLiteDatabase db) {
+                        mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0);
+                    }
+                });
     }
 
     @Override
@@ -323,7 +300,7 @@
     }
 
     public void testFileLocation_Owner() {
-        LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+        LockSettingsStorage storage = new LockSettingsStorage(getContext());
 
         assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0));
         assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0));
@@ -332,21 +309,21 @@
     }
 
     public void testFileLocation_SecondaryUser() {
-        LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+        LockSettingsStorage storage = new LockSettingsStorage(getContext());
 
         assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
         assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
     }
 
     public void testFileLocation_ProfileToSecondary() {
-        LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+        LockSettingsStorage storage = new LockSettingsStorage(getContext());
 
         assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
         assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
     }
 
     public void testFileLocation_ProfileToOwner() {
-        LockSettingsStorage storage = new LockSettingsStorage(getContext(), null);
+        LockSettingsStorage storage = new LockSettingsStorage(getContext());
 
         assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
         assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
new file mode 100644
index 0000000..15983ca
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java
@@ -0,0 +1,185 @@
+/*
+ * 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;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.gatekeeper.GateKeeperResponse;
+import android.service.gatekeeper.IGateKeeperService;
+import android.util.ArrayMap;
+
+import junit.framework.AssertionFailedError;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+public class MockGateKeeperService implements IGateKeeperService {
+    static class VerifyHandle {
+        public byte[] password;
+        public long sid;
+
+        public VerifyHandle(byte[] password, long sid) {
+            this.password = password;
+            this.sid = sid;
+        }
+
+        public VerifyHandle(byte[] handle) {
+            ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+            buffer.put(handle, 0, handle.length);
+            buffer.flip();
+            int version = buffer.get();
+            sid = buffer.getLong();
+            password = new byte[buffer.remaining()];
+            buffer.get(password);
+        }
+
+        public byte[] toBytes() {
+            ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length);
+            buffer.put((byte)0);
+            buffer.putLong(sid);
+            buffer.put(password);
+            return buffer.array();
+        }
+    }
+
+    static class AuthToken {
+        public long challenge;
+        public long sid;
+
+        public AuthToken(long challenge, long sid) {
+            this.challenge = challenge;
+            this.sid = sid;
+        }
+
+        public AuthToken(byte[] handle) {
+            ByteBuffer buffer = ByteBuffer.allocate(handle.length);
+            buffer.put(handle, 0, handle.length);
+            buffer.flip();
+            int version = buffer.get();
+            challenge = buffer.getLong();
+            sid = buffer.getLong();
+        }
+
+        public byte[] toBytes() {
+            ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES);
+            buffer.put((byte)0);
+            buffer.putLong(challenge);
+            buffer.putLong(sid);
+            return buffer.array();
+        }
+    }
+
+    private ArrayMap<Integer, Long> sidMap = new ArrayMap<>();
+    private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>();
+
+    private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>();
+
+    @Override
+    public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword,
+            byte[] desiredPassword) throws android.os.RemoteException {
+
+        if (currentPasswordHandle != null) {
+            VerifyHandle handle = new VerifyHandle(currentPasswordHandle);
+            if (Arrays.equals(currentPassword, handle.password)) {
+                // Trusted enroll
+                VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid);
+                refreshSid(uid, handle.sid, false);
+                handleMap.put(uid, newHandle.toBytes());
+                return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+            } else {
+                return null;
+            }
+        } else {
+            // Untrusted enroll
+            long newSid = new Random().nextLong();
+            VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid);
+            refreshSid(uid, newSid, true);
+            handleMap.put(uid, newHandle.toBytes());
+            return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false);
+        }
+    }
+
+    @Override
+    public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle,
+            byte[] providedPassword) throws android.os.RemoteException {
+        return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword);
+    }
+
+    @Override
+    public GateKeeperResponse verifyChallenge(int uid, long challenge,
+            byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException {
+
+        VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle);
+        if (Arrays.equals(handle.password, providedPassword)) {
+            byte[] knownHandle = handleMap.get(uid);
+            if (knownHandle != null) {
+                if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) {
+                    throw new AssertionFailedError("Got correct but obsolete handle");
+                }
+            }
+            refreshSid(uid, handle.sid, false);
+            AuthToken token = new AuthToken(challenge, handle.sid);
+            refreshAuthToken(uid, token);
+            return GateKeeperResponse.createOkResponse(token.toBytes(), false);
+        } else {
+            return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR);
+        }
+    }
+
+    private void refreshAuthToken(int uid, AuthToken token) {
+        authTokenMap.put(uid, token);
+    }
+
+    public AuthToken getAuthToken(int uid) {
+        return authTokenMap.get(uid);
+    }
+
+    public void clearAuthToken(int uid) {
+        authTokenMap.remove(uid);
+    }
+
+    @Override
+    public IBinder asBinder() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearSecureUserId(int userId) throws RemoteException {
+        sidMap.remove(userId);
+    }
+
+    @Override
+    public long getSecureUserId(int userId) throws RemoteException {
+        if (sidMap.containsKey(userId)) {
+            return sidMap.get(userId);
+        } else {
+            return 0L;
+        }
+    }
+
+    private void refreshSid(int uid, long sid, boolean force) {
+        if (!sidMap.containsKey(uid) || force) {
+            sidMap.put(uid, sid);
+        } else{
+            if (sidMap.get(uid) != sid) {
+                throw new AssertionFailedError("Inconsistent SID");
+            }
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
new file mode 100644
index 0000000..b63936f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.UserManager;
+
+public class MockLockSettingsContext extends ContextWrapper {
+
+    private UserManager mUserManager;
+    private NotificationManager mNotificationManager;
+
+    public MockLockSettingsContext(Context base, UserManager userManager,
+            NotificationManager notificationManager) {
+        super(base);
+        mUserManager = userManager;
+        mNotificationManager = notificationManager;
+    }
+
+    @Override
+    public Object getSystemService(String name) {
+        if (USER_SERVICE.equals(name)) {
+            return mUserManager;
+        } else if (NOTIFICATION_SERVICE.equals(name)) {
+            return mNotificationManager;
+        } else {
+            throw new RuntimeException("System service not mocked: " + name);
+        }
+    }
+
+    @Override
+    public void enforceCallingOrSelfPermission(String permission, String message) {
+        // Skip permission checks for unit tests.
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
new file mode 100644
index 0000000..031a3b3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
@@ -0,0 +1,493 @@
+/*
+ * 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;
+
+import android.content.pm.IPackageMoveObserver;
+import android.os.IBinder;
+import android.os.IProgressListener;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.storage.DiskInfo;
+import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
+import android.os.storage.IStorageManager;
+import android.os.storage.IStorageShutdownObserver;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.os.storage.VolumeRecord;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.internal.os.AppFuseMount;
+
+import junit.framework.AssertionFailedError;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockStorageManager implements IStorageManager {
+
+    private ArrayMap<Integer, ArrayList<Pair<byte[], byte[]>>> mAuth = new ArrayMap<>();
+    private boolean mIgnoreBadUnlock;
+
+    @Override
+    public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret)
+            throws RemoteException {
+        getUserAuth(userId).add(new Pair<>(token, secret));
+    }
+
+    @Override
+    public void fixateNewestUserKeyAuth(int userId) throws RemoteException {
+        ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId);
+        Pair<byte[], byte[]> latest = auths.get(auths.size() - 1);
+        auths.clear();
+        auths.add(latest);
+    }
+
+    private ArrayList<Pair<byte[], byte[]>> getUserAuth(int userId) {
+        if (!mAuth.containsKey(userId)) {
+            ArrayList<Pair<byte[], byte[]>> auths = new ArrayList<Pair<byte[], byte[]>>();
+            auths.add(new Pair(null, null));
+            mAuth.put(userId,  auths);
+        }
+        return mAuth.get(userId);
+    }
+
+    public byte[] getUserUnlockToken(int userId) {
+        ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+        if (auths.size() != 1) {
+            throw new AssertionFailedError("More than one secret exists");
+        }
+        return auths.get(0).second;
+    }
+
+    public void unlockUser(int userId, byte[] secret, IProgressListener listener)
+            throws RemoteException {
+        listener.onStarted(userId, null);
+        listener.onFinished(userId, null);
+        ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId);
+        if (secret != null) {
+            if (auths.size() > 1) {
+                throw new AssertionFailedError("More than one secret exists");
+            }
+            Pair<byte[], byte[]> auth = auths.get(0);
+            if ((!mIgnoreBadUnlock) && auth.second != null && !Arrays.equals(secret, auth.second)) {
+                throw new AssertionFailedError("Invalid secret to unlock user");
+            }
+        } else {
+            if (auths != null && auths.size() > 0) {
+                throw new AssertionFailedError("Cannot unlock encrypted user with empty token");
+            }
+        }
+    }
+
+    public void setIgnoreBadUnlock(boolean ignore) {
+        mIgnoreBadUnlock = ignore;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void registerListener(IStorageEventListener listener) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unregisterListener(IStorageEventListener listener) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isUsbMassStorageConnected() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setUsbMassStorageEnabled(boolean enable) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isUsbMassStorageEnabled() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int mountVolume(String mountPoint) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unmountVolume(String mountPoint, boolean force, boolean removeEncryption)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+
+    }
+
+    @Override
+    public int formatVolume(String mountPoint) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int[] getStorageUsers(String path) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getVolumeState(String mountPoint) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid,
+            boolean external) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int finalizeSecureContainer(String id) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int destroySecureContainer(String id, boolean force) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int unmountSecureContainer(String id, boolean force) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isSecureContainerMounted(String id) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int renameSecureContainer(String oldId, String newId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getSecureContainerPath(String id) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getSecureContainerList() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void shutdown(IStorageShutdownObserver observer) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void finishMediaUpdate() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void mountObb(String rawPath, String canonicalPath, String key, IObbActionListener token,
+            int nonce) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isObbMounted(String rawPath) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getMountedObbPath(String rawPath) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isExternalStorageEmulated() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int decryptStorage(String password) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int encryptStorage(int type, String password) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int changeEncryptionPassword(int type, String password) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StorageVolume[] getVolumeList(int uid, String packageName, int flags)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getSecureContainerFilesystemPath(String cid) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getEncryptionState() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int verifyEncryptionPassword(String password) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int fixPermissionsSecureContainer(String id, int gid, String filename)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int mkdirs(String callingPkg, String path) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getPasswordType() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getPassword() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearPassword() throws RemoteException {
+        throw new UnsupportedOperationException();
+
+    }
+
+    @Override
+    public void setField(String field, String contents) throws RemoteException {
+        throw new UnsupportedOperationException();
+
+    }
+
+    @Override
+    public String getField(String field) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long lastMaintenance() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void runMaintenance() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void waitForAsecScan() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DiskInfo[] getDisks() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public VolumeInfo[] getVolumes(int flags) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void mount(String volId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unmount(String volId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void format(String volId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void partitionPublic(String diskId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void partitionPrivate(String diskId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void partitionMixed(String diskId, int ratio) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forgetVolume(String fsUuid) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forgetAllVolumes() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getPrimaryStorageUuid() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long benchmark(String volId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setDebugFlags(int flags, int mask) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createUserKey(int userId, int serialNumber, boolean ephemeral)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void destroyUserKey(int userId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void lockUserKey(int userId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isUserKeyUnlocked(int userId) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void destroyUserStorage(String volumeUuid, int userId, int flags)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isConvertibleToFBE() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void fstrim(int flags) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getCacheQuotaBytes(String volumeUuid, int uid) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getCacheSizeBytes(String volumeUuid, int uid) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 43c8957..4ca29cd 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -63,7 +63,10 @@
 import android.net.ScoredNetwork;
 import android.net.Uri;
 import android.net.WifiKey;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -82,6 +85,8 @@
 
 import com.android.server.devicepolicy.MockUtils;
 
+import com.google.android.collect.Lists;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -96,9 +101,12 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 
 /**
  * Tests for {@link NetworkScoreService}.
@@ -106,19 +114,27 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class NetworkScoreServiceTest {
+    private static final String SSID = "ssid";
+    private static final String SSID_2 = "ssid_2";
+    private static final String SSID_3 = "ssid_3";
     private static final ScoredNetwork SCORED_NETWORK =
-            new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
+            new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID), "00:00:00:00:00:00")),
+                    null /* rssiCurve*/);
+    private static final ScoredNetwork SCORED_NETWORK_2 =
+            new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")),
                     null /* rssiCurve*/);
     private static final NetworkScorerAppData NEW_SCORER =
         new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass");
 
-    @Mock private PackageManager mPackageManager;
     @Mock private NetworkScorerAppManager mNetworkScorerAppManager;
     @Mock private Context mContext;
     @Mock private Resources mResources;
     @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
     @Mock private IBinder mIBinder, mIBinder2;
     @Mock private INetworkRecommendationProvider mRecommendationProvider;
+    @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
+    @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+    @Mock private WifiInfo mWifiInfo;
     @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
 
     private ContentResolver mContentResolver;
@@ -127,6 +143,11 @@
     private RemoteCallback mRemoteCallback;
     private OnResultListener mOnResultListener;
     private HandlerThread mHandlerThread;
+    private List<ScanResult> mScanResults;
+
+    private static String quote(String str) {
+        return String.format("\"%s\"", str);
+    }
 
     @Before
     public void setUp() throws Exception {
@@ -136,6 +157,8 @@
         mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getResources()).thenReturn(mResources);
+        when(mWifiInfo.getSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.ssid);
+        when(mWifiInfo.getBSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.bssid);
         mHandlerThread = new HandlerThread("NetworkScoreServiceTest");
         mHandlerThread.start();
         mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager,
@@ -150,6 +173,21 @@
         Settings.Global.putLong(mContentResolver,
                 Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L);
         mNetworkScoreService.refreshRecommendationRequestTimeoutMs();
+        populateScanResults();
+    }
+
+    private void populateScanResults() {
+        mScanResults = new ArrayList<>();
+        mScanResults.add(createScanResult(SSID, SCORED_NETWORK.networkKey.wifiKey.bssid));
+        mScanResults.add(createScanResult(SSID_2, SCORED_NETWORK_2.networkKey.wifiKey.bssid));
+        mScanResults.add(createScanResult(SSID_3, "10:10:00:00:10:10"));
+    }
+
+    private ScanResult createScanResult(String ssid, String bssid) {
+        ScanResult result = new ScanResult();
+        result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+        result.BSSID = bssid;
+        return result;
     }
 
     @After
@@ -622,6 +660,173 @@
         assertEquals(NEW_SCORER.packageName, mNetworkScoreService.getActiveScorerPackage());
     }
 
+    @Test
+    public void testCacheUpdatingConsumer_nullFilter() throws Exception {
+        List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+        NetworkScoreService.FilteringCacheUpdatingConsumer consumer =
+                new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+                        new ArrayList<>(scoredNetworkList), NetworkKey.TYPE_WIFI,
+                        mCurrentNetworkFilter, mScanResultsFilter);
+
+        consumer.accept(mNetworkScoreCache, null /*cookie*/);
+
+        verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+        verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+    }
+
+    @Test
+    public void testCacheUpdatingConsumer_noneFilter() throws Exception {
+        List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+        NetworkScoreService.FilteringCacheUpdatingConsumer
+                consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+                new ArrayList<>(scoredNetworkList),
+                NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+
+        verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+        verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+    }
+
+    @Test
+    public void testCacheUpdatingConsumer_unknownFilter() throws Exception {
+        List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+        NetworkScoreService.FilteringCacheUpdatingConsumer
+                consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+                new ArrayList<>(scoredNetworkList),
+                NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+        consumer.accept(mNetworkScoreCache, -1 /*cookie*/);
+
+        verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+        verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+    }
+
+    @Test
+    public void testCacheUpdatingConsumer_nonIntFilter() throws Exception {
+        List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+        NetworkScoreService.FilteringCacheUpdatingConsumer
+                consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+                new ArrayList<>(scoredNetworkList),
+                NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+        consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/);
+
+        verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+        verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+    }
+
+    @Test
+    public void testCacheUpdatingConsumer_emptyScoreList() throws Exception {
+        NetworkScoreService.FilteringCacheUpdatingConsumer
+                consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+                Collections.emptyList(),
+                NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+
+        verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
+    }
+
+    @Test
+    public void testCacheUpdatingConsumer_currentNetworkFilter() throws Exception {
+        List<ScoredNetwork> scoredNetworkList =
+                Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+        NetworkScoreService.FilteringCacheUpdatingConsumer
+                consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+                new ArrayList<>(scoredNetworkList),
+                NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+        List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
+        filteredList.remove(SCORED_NETWORK);
+        when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList);
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+
+        verify(mNetworkScoreCache).updateScores(filteredList);
+        verifyZeroInteractions(mScanResultsFilter);
+    }
+
+    @Test
+    public void testCacheUpdatingConsumer_scanResultsFilter() throws Exception {
+        List<ScoredNetwork> scoredNetworkList =
+                Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+        NetworkScoreService.FilteringCacheUpdatingConsumer
+                consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+                new ArrayList<>(scoredNetworkList),
+                NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+        List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
+        filteredList.remove(SCORED_NETWORK);
+        when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList);
+        consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+
+        verify(mNetworkScoreCache).updateScores(filteredList);
+        verifyZeroInteractions(mCurrentNetworkFilter);
+    }
+
+    @Test
+    public void testCurrentNetworkScoreCacheFilter_nullWifiInfo() throws Exception {
+        NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+                new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> null /*WifiInfo*/);
+
+        List<ScoredNetwork> actualList =
+                cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+        assertTrue(actualList.isEmpty());
+    }
+
+    @Test
+    public void testCurrentNetworkScoreCacheFilter_scoreFiltered() throws Exception {
+        NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+                new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+        List<ScoredNetwork> actualList =
+                cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+        List<ScoredNetwork> expectedList = Collections.singletonList(SCORED_NETWORK);
+        assertEquals(expectedList, actualList);
+    }
+
+    @Test
+    public void testCurrentNetworkScoreCacheFilter_currentNetworkNotInList() throws Exception {
+        when(mWifiInfo.getSSID()).thenReturn("\"notInList\"");
+        NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+                new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+        List<ScoredNetwork> actualList =
+                cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+        assertTrue(actualList.isEmpty());
+    }
+
+    @Test
+    public void testScanResultsScoreCacheFilter_emptyScanResults() throws Exception {
+        NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter =
+                new NetworkScoreService.ScanResultsScoreCacheFilter(Collections::emptyList);
+
+        List<ScoredNetwork> actualList =
+                cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+        assertTrue(actualList.isEmpty());
+    }
+
+    @Test
+    public void testScanResultsScoreCacheFilter_scoresFiltered() throws Exception {
+        NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter =
+                new NetworkScoreService.ScanResultsScoreCacheFilter(() -> mScanResults);
+
+        ScoredNetwork unmatchedScore =
+                new ScoredNetwork(new NetworkKey(new WifiKey(quote("newSsid"),
+                        "00:00:00:00:00:00")), null /* rssiCurve*/);
+
+        List<ScoredNetwork> actualList =
+                cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2,
+                        unmatchedScore));
+
+        List<ScoredNetwork> expectedList = Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+        assertEquals(expectedList, actualList);
+    }
+
     // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
     private void injectProvider() {
         final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index a600e69..969cb36 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -45,9 +45,11 @@
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.RegisteredServicesCacheListener;
 import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.pm.RegisteredServicesCache.ServiceInfo;
 import android.database.Cursor;
@@ -81,11 +83,13 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 public class AccountManagerServiceTest extends AndroidTestCase {
     private static final String TAG = AccountManagerServiceTest.class.getSimpleName();
+    private static final long ONE_DAY_IN_MILLISECOND = 86400000;
 
     @Mock private Context mMockContext;
     @Mock private AppOpsManager mMockAppOpsManager;
@@ -104,6 +108,7 @@
     private static final String PREN_DB = "pren.db";
     private static final String DE_DB = "de.db";
     private static final String CE_DB = "ce.db";
+    private PackageInfo mPackageInfo;
     private AccountManagerService mAms;
     private TestInjector mTestInjector;
 
@@ -115,7 +120,16 @@
                     .thenReturn(PackageManager.SIGNATURE_MATCH);
         final UserInfo ui = new UserInfo(UserHandle.USER_SYSTEM, "user0", 0);
         when(mMockUserManager.getUserInfo(eq(ui.id))).thenReturn(ui);
+        when(mMockContext.createPackageContextAsUser(
+                 anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+        mPackageInfo = new PackageInfo();
+        mPackageInfo.signatures = new Signature[1];
+        mPackageInfo.signatures[0] = new Signature(new byte[] {'a', 'b', 'c', 'd'});
+        mPackageInfo.applicationInfo = new ApplicationInfo();
+        mPackageInfo.applicationInfo.privateFlags = ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+        when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
         when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
         when(mMockContext.getSystemServiceName(AppOpsManager.class)).thenReturn(
@@ -366,8 +380,7 @@
                 null); // optionsIn
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -384,8 +397,7 @@
                     null); // optionsIn
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -664,8 +676,7 @@
                 null); // optionsIn
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -681,8 +692,7 @@
                 null); // optionsIn
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -849,8 +859,7 @@
                 UserHandle.USER_SYSTEM);
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -866,8 +875,7 @@
                 UserHandle.USER_SYSTEM);
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -1142,8 +1150,7 @@
                 AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -1157,8 +1164,7 @@
                 AccountManagerServiceTestFixtures.ACCOUNT_STATUS_TOKEN);
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -1172,8 +1178,7 @@
                 null);
             fail("IllegalArgumentException expected. But no exception was thrown.");
         } catch (IllegalArgumentException e) {
-        } catch(Exception e){
-            fail(String.format("Expect IllegalArgumentException, but got %s.", e));
+            // IllegalArgumentException is expected.
         }
     }
 
@@ -1212,6 +1217,1246 @@
         assertTrue(needUpdate);
     }
 
+    @SmallTest
+    public void testHasFeaturesWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.hasFeatures(
+                null, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                new String[] {"feature1", "feature2"}, // features
+                "testPackage"); // opPackageName
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testHasFeaturesWithNullAccount() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.hasFeatures(
+                mMockAccountManagerResponse, // response
+                null, // account
+                new String[] {"feature1", "feature2"}, // features
+                "testPackage"); // opPackageName
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testHasFeaturesWithNullFeature() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.hasFeatures(
+                    mMockAccountManagerResponse, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
+                    null, // features
+                    "testPackage"); // opPackageName
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testHasFeaturesReadAccountsNotPermitted() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+                    .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+        try {
+            mAms.hasFeatures(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
+                new String[] {"feature1", "feature2"}, // features
+                "testPackage"); // opPackageName
+            fail("SecurityException expected. But no exception was thrown.");
+        } catch (SecurityException e) {
+            // SecurityException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testHasFeaturesReturnNullResult() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.hasFeatures(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_ERROR, // account
+                AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+                "testPackage"); // opPackageName
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), anyString());
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+    }
+
+    @SmallTest
+    public void testHasFeaturesSuccess() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.hasFeatures(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, // account
+                AccountManagerServiceTestFixtures.ACCOUNT_FEATURES, // features
+                "testPackage"); // opPackageName
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        boolean hasFeatures = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+        assertTrue(hasFeatures);
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.removeAccountAsUser(
+                null, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserWithNullAccount() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.removeAccountAsUser(
+                mMockAccountManagerResponse, // response
+                null, // account
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserAccountNotManagedByCaller() throws Exception {
+        unlockSystemUser();
+        when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+                    .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+        try {
+            mAms.removeAccountAsUser(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+            fail("SecurityException expected. But no exception was thrown.");
+        } catch (SecurityException e) {
+            // SecurityException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserUserCannotModifyAccount() throws Exception {
+        unlockSystemUser();
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+        when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.removeAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserUserCannotModifyAccountType() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+                mMockDevicePolicyManager);
+        when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+                .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.removeAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserRemovalAllowed() throws Exception {
+        unlockSystemUser();
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p1", null);
+        Account[] addedAccounts =
+                mAms.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName());
+        assertEquals(1, addedAccounts.length);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.removeAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+        assertTrue(allowed);
+        Account[] accounts = mAms.getAccounts(UserHandle.USER_SYSTEM, mContext.getOpPackageName());
+        assertEquals(0, accounts.length);
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserRemovalNotAllowed() throws Exception {
+        unlockSystemUser();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.removeAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+        assertFalse(allowed);
+    }
+
+    @SmallTest
+    public void testRemoveAccountAsUserReturnWithValidIntent() throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.removeAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+        assertNotNull(intent);
+    }
+
+    @SmallTest
+    public void testGetAuthTokenLabelWithNullAccountType() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.getAuthTokenLabel(
+                mMockAccountManagerResponse, // response
+                null, // accountType
+                "authTokenType");
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testGetAuthTokenLabelWithNullAuthTokenType() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.getAuthTokenLabel(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                null); // authTokenType
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testGetAuthTokenWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.getAuthToken(
+                    null, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                    "authTokenType", // authTokenType
+                    true, // notifyOnAuthFailure
+                    true, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testGetAuthTokenWithNullAccount() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAuthToken(
+                    response, // response
+                    null, // account
+                    "authTokenType", // authTokenType
+                    true, // notifyOnAuthFailure
+                    true, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString());
+    }
+
+    @SmallTest
+    public void testGetAuthTokenWithNullAuthTokenType() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAuthToken(
+                    response, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                    null, // authTokenType
+                    true, // notifyOnAuthFailure
+                    true, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_BAD_ARGUMENTS), anyString());
+    }
+
+    @SmallTest
+    public void testGetAuthTokenWithInvalidPackage() throws Exception {
+        unlockSystemUser();
+        String[] list = new String[]{"test"};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+        try {
+            mAms.getAuthToken(
+                    mMockAccountManagerResponse, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                    "authTokenType", // authTokenType
+                    true, // notifyOnAuthFailure
+                    true, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+            fail("SecurityException expected. But no exception was thrown.");
+        } catch (SecurityException e) {
+            // SecurityException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testGetAuthTokenFromInternal() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.createPackageContextAsUser(
+                 anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+
+        mAms.setAuthToken(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                "authTokenType", AccountManagerServiceTestFixtures.AUTH_TOKEN);
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAuthToken(
+                    response, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                    "authTokenType", // authTokenType
+                    true, // notifyOnAuthFailure
+                    true, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        assertEquals(result.getString(AccountManager.KEY_AUTHTOKEN),
+                AccountManagerServiceTestFixtures.AUTH_TOKEN);
+        assertEquals(result.getString(AccountManager.KEY_ACCOUNT_NAME),
+                AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+        assertEquals(result.getString(AccountManager.KEY_ACCOUNT_TYPE),
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+    }
+
+    @SmallTest
+    public void testGetAuthTokenSuccess() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.createPackageContextAsUser(
+                 anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAuthToken(
+                    response, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                    "authTokenType", // authTokenType
+                    true, // notifyOnAuthFailure
+                    false, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        assertEquals(result.getString(AccountManager.KEY_AUTHTOKEN),
+                AccountManagerServiceTestFixtures.AUTH_TOKEN);
+        assertEquals(result.getString(AccountManager.KEY_ACCOUNT_NAME),
+                AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+        assertEquals(result.getString(AccountManager.KEY_ACCOUNT_TYPE),
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+    }
+
+    @SmallTest
+    public void testGetAuthTokenReturnWithInvalidIntent() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.createPackageContextAsUser(
+                 anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAuthToken(
+                    response, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+                    "authTokenType", // authTokenType
+                    true, // notifyOnAuthFailure
+                    false, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+    }
+
+    @SmallTest
+    public void testGetAuthTokenReturnWithValidIntent() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.createPackageContextAsUser(
+                 anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAuthToken(
+                    response, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+                    "authTokenType", // authTokenType
+                    false, // notifyOnAuthFailure
+                    true, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+        assertNotNull(intent);
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+    }
+
+    @SmallTest
+    public void testGetAuthTokenError() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.createPackageContextAsUser(
+                 anyString(), anyInt(), any(UserHandle.class))).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
+        when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAuthToken(
+                    response, // response
+                    AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+                    "authTokenType", // authTokenType
+                    true, // notifyOnAuthFailure
+                    false, // expectActivityLaunch
+                    createGetAuthTokenOptions());
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.addAccountAsUser(
+                null, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                null, // optionsIn
+                UserHandle.USER_SYSTEM);
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserWithNullAccountType() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.addAccountAsUser(
+                mMockAccountManagerResponse, // response
+                null, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                null, // optionsIn
+                UserHandle.USER_SYSTEM);
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserUserCannotModifyAccountNoDPM() throws Exception {
+        unlockSystemUser();
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+        when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+        mAms.addAccountAsUser(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                null, // optionsIn
+                UserHandle.USER_SYSTEM);
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+        verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.SYSTEM));
+
+        // verify the intent for default CantAddAccountActivity is sent.
+        Intent intent = mIntentCaptor.getValue();
+        assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+        assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+                AccountManager.ERROR_CODE_USER_RESTRICTED);
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserUserCannotModifyAccountWithDPM() throws Exception {
+        unlockSystemUser();
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+        when(mMockUserManager.getUserRestrictions(any(UserHandle.class))).thenReturn(bundle);
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.addService(
+                DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+        when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+                anyInt(), anyString())).thenReturn(new Intent());
+        when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+                anyInt(), anyBoolean())).thenReturn(new Intent());
+
+        mAms.addAccountAsUser(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                null, // optionsIn
+                UserHandle.USER_SYSTEM);
+
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_USER_RESTRICTED), anyString());
+        verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.SYSTEM));
+        verify(mMockDevicePolicyManagerInternal).createUserRestrictionSupportIntent(
+                anyInt(), anyString());
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserUserCannotModifyAccountForTypeNoDPM() throws Exception {
+        unlockSystemUser();
+        when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+                .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+        mAms.addAccountAsUser(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                null, // optionsIn
+                UserHandle.USER_SYSTEM);
+
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+        verify(mMockContext).startActivityAsUser(mIntentCaptor.capture(), eq(UserHandle.SYSTEM));
+
+        // verify the intent for default CantAddAccountActivity is sent.
+        Intent intent = mIntentCaptor.getValue();
+        assertEquals(intent.getComponent().getClassName(), CantAddAccountActivity.class.getName());
+        assertEquals(intent.getIntExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, 0),
+                AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE);
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserUserCannotModifyAccountForTypeWithDPM() throws Exception {
+        unlockSystemUser();
+        when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+                mMockDevicePolicyManager);
+        when(mMockDevicePolicyManager.getAccountTypesWithManagementDisabledAsUser(anyInt()))
+                .thenReturn(new String[]{AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, "BBB"});
+
+        LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.addService(
+                DevicePolicyManagerInternal.class, mMockDevicePolicyManagerInternal);
+        when(mMockDevicePolicyManagerInternal.createUserRestrictionSupportIntent(
+                anyInt(), anyString())).thenReturn(new Intent());
+        when(mMockDevicePolicyManagerInternal.createShowAdminSupportIntent(
+                anyInt(), anyBoolean())).thenReturn(new Intent());
+
+        mAms.addAccountAsUser(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                null, // optionsIn
+                UserHandle.USER_SYSTEM);
+
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE), anyString());
+        verify(mMockContext).startActivityAsUser(any(Intent.class), eq(UserHandle.SYSTEM));
+        verify(mMockDevicePolicyManagerInternal).createShowAdminSupportIntent(
+                anyInt(), anyBoolean());
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserSuccess() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.addAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS),
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+        // Verify notification is cancelled
+        verify(mMockNotificationManager).cancelNotificationWithTag(
+                anyString(), anyString(), anyInt(), anyInt());
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        // Verify response data
+        assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+                result.getString(AccountManager.KEY_ACCOUNT_NAME));
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+        Bundle optionBundle = result.getParcelable(
+                AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE);
+        // Assert addAccountAsUser added calling uid and pid into the option bundle
+        assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+        assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_PID));
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserReturnWithInvalidIntent() throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.addAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+                UserHandle.USER_SYSTEM);
+
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserReturnWithValidIntent() throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.addAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE),
+                UserHandle.USER_SYSTEM);
+
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+        assertNotNull(intent);
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+    }
+
+    @SmallTest
+    public void testAddAccountAsUserError() throws Exception {
+        unlockSystemUser();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.addAccountAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                "authTokenType",
+                null, // requiredFeatures
+                true, // expectActivityLaunch
+                createAddAccountOptions(AccountManagerServiceTestFixtures.ACCOUNT_NAME_ERROR),
+                UserHandle.USER_SYSTEM);
+
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+    }
+
+    @SmallTest
+    public void testConfirmCredentialsAsUserWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.confirmCredentialsAsUser(
+                null, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                new Bundle(), // options
+                false, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testConfirmCredentialsAsUserWithNullAccount() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.confirmCredentialsAsUser(
+                mMockAccountManagerResponse, // response
+                null, // account
+                new Bundle(), // options
+                false, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testConfirmCredentialsAsUserSuccess() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.confirmCredentialsAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                new Bundle(), // options
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        // Verify response data
+        assertTrue(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+                result.getString(AccountManager.KEY_ACCOUNT_NAME));
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+    }
+
+    @SmallTest
+    public void testConfirmCredentialsAsUserReturnWithInvalidIntent() throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.confirmCredentialsAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+                new Bundle(), // options
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+    }
+
+    @SmallTest
+    public void testConfirmCredentialsAsUserReturnWithValidIntent() throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.confirmCredentialsAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+                new Bundle(), // options
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+        assertNotNull(intent);
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+    }
+
+    @SmallTest
+    public void testConfirmCredentialsAsUserError() throws Exception {
+        unlockSystemUser();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.confirmCredentialsAsUser(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+                new Bundle(), // options
+                true, // expectActivityLaunch
+                UserHandle.USER_SYSTEM);
+
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+    }
+
+    @SmallTest
+    public void testUpdateCredentialsWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.updateCredentials(
+                null, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                "authTokenType",
+                false, // expectActivityLaunch
+                new Bundle()); // options
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testUpdateCredentialsWithNullAccount() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.updateCredentials(
+                mMockAccountManagerResponse, // response
+                null, // account
+                "authTokenType",
+                false, // expectActivityLaunch
+                new Bundle()); // options
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testUpdateCredentialsSuccess() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.updateCredentials(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+                "authTokenType",
+                false, // expectActivityLaunch
+                new Bundle()); // options
+
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        // Verify response data
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+                result.getString(AccountManager.KEY_ACCOUNT_NAME));
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+    }
+
+    @SmallTest
+    public void testUpdateCredentialsReturnWithInvalidIntent() throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.updateCredentials(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+                "authTokenType",
+                true, // expectActivityLaunch
+                new Bundle()); // options
+
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_REMOTE_EXCEPTION), anyString());
+    }
+
+    @SmallTest
+    public void testUpdateCredentialsReturnWithValidIntent() throws Exception {
+        unlockSystemUser();
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.activityInfo = new ActivityInfo();
+        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
+        when(mMockPackageManager.resolveActivityAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(resolveInfo);
+        when(mMockPackageManager.checkSignatures(
+                anyInt(), anyInt())).thenReturn(PackageManager.SIGNATURE_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.updateCredentials(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE,
+                "authTokenType",
+                true, // expectActivityLaunch
+                new Bundle()); // options
+
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+        assertNotNull(intent);
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_RESULT));
+        assertNotNull(intent.getParcelableExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK));
+    }
+
+    @SmallTest
+    public void testUpdateCredentialsError() throws Exception {
+        unlockSystemUser();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.updateCredentials(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_ERROR,
+                "authTokenType",
+                false, // expectActivityLaunch
+                new Bundle()); // options
+
+        waitForLatch(latch);
+        verify(mMockAccountManagerResponse).onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+    }
+
+    @SmallTest
+    public void testEditPropertiesWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.editProperties(
+                null, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                false); // expectActivityLaunch
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testEditPropertiesWithNullAccountType() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.editProperties(
+                mMockAccountManagerResponse, // response
+                null, // accountType
+                false); // expectActivityLaunch
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testEditPropertiesAccountNotManagedByCaller() throws Exception {
+        unlockSystemUser();
+        when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+                    .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+        try {
+            mAms.editProperties(
+                mMockAccountManagerResponse, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                false); // expectActivityLaunch
+            fail("SecurityException expected. But no exception was thrown.");
+        } catch (SecurityException e) {
+            // SecurityException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testEditPropertiesSuccess() throws Exception {
+        unlockSystemUser();
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+
+        mAms.editProperties(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                false); // expectActivityLaunch
+
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        // Verify response data
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS,
+                result.getString(AccountManager.KEY_ACCOUNT_NAME));
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+    }
+
+    @SmallTest
+    public void testGetAccountsByFeaturesWithNullResponse() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.getAccountsByFeatures(
+                null, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+                AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+                "testpackage"); // opPackageName
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testGetAccountsByFeaturesWithNullAccountType() throws Exception {
+        unlockSystemUser();
+        try {
+            mAms.getAccountsByFeatures(
+                mMockAccountManagerResponse, // response
+                null, // accountType
+                AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+                "testpackage"); // opPackageName
+            fail("IllegalArgumentException expected. But no exception was thrown.");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected.
+        }
+    }
+
+    @SmallTest
+    public void testGetAccountsByFeaturesAccountNotVisible() throws Exception {
+        unlockSystemUser();
+
+        when(mMockContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        when(mMockPackageManager.checkSignatures(anyInt(), anyInt()))
+                    .thenReturn(PackageManager.SIGNATURE_NO_MATCH);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAccountsByFeatures(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+                "testpackage"); // opPackageName
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Account[] accounts = (Account[]) result.getParcelableArray(AccountManager.KEY_ACCOUNTS);
+        assertTrue(accounts.length == 0);
+    }
+
+    @SmallTest
+    public void testGetAccountsByFeaturesNullFeatureReturnsAllAccounts() throws Exception {
+        unlockSystemUser();
+
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p12", null);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAccountsByFeatures(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                null, // features
+                "testpackage"); // opPackageName
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Account[] accounts = (Account[]) result.getParcelableArray(AccountManager.KEY_ACCOUNTS);
+        Arrays.sort(accounts, new AccountSorter());
+        assertEquals(2, accounts.length);
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, accounts[0]);
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, accounts[1]);
+    }
+
+    @SmallTest
+    public void testGetAccountsByFeaturesReturnsAccountsWithFeaturesOnly() throws Exception {
+        unlockSystemUser();
+
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_INTERVENE, "p12", null);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAccountsByFeatures(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+                "testpackage"); // opPackageName
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
+        Bundle result = mBundleCaptor.getValue();
+        Account[] accounts = (Account[]) result.getParcelableArray(AccountManager.KEY_ACCOUNTS);
+        assertEquals(1, accounts.length);
+        assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, accounts[0]);
+    }
+
+    @SmallTest
+    public void testGetAccountsByFeaturesError() throws Exception {
+        unlockSystemUser();
+
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS, "p11", null);
+        mAms.addAccountExplicitly(AccountManagerServiceTestFixtures.ACCOUNT_ERROR, "p12", null);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        Response response = new Response(latch, mMockAccountManagerResponse);
+        mAms.getAccountsByFeatures(
+                response, // response
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, // accountType
+                AccountManagerServiceTestFixtures.ACCOUNT_FEATURES,
+                "testpackage"); // opPackageName
+        waitForLatch(latch);
+
+        verify(mMockAccountManagerResponse).onError(
+                eq(AccountManager.ERROR_CODE_INVALID_RESPONSE), anyString());
+        verify(mMockAccountManagerResponse, never()).onResult(any(Bundle.class));
+    }
+
     private void waitForLatch(CountDownLatch latch) {
         try {
             latch.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -1220,6 +2465,21 @@
         }
     }
 
+    private Bundle createAddAccountOptions(String accountName) {
+        Bundle options = new Bundle();
+        options.putString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME, accountName);
+        return options;
+    }
+
+    private Bundle createGetAuthTokenOptions() {
+        Bundle options = new Bundle();
+        options.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME,
+                AccountManagerServiceTestFixtures.CALLER_PACKAGE);
+        options.putLong(AccountManagerServiceTestFixtures.KEY_TOKEN_EXPIRY,
+                System.currentTimeMillis() + ONE_DAY_IN_MILLISECOND);
+        return options;
+    }
+
     private Bundle encryptBundleWithCryptoHelper(Bundle sessionBundle) {
         Bundle encryptedBundle = null;
         try {
@@ -1386,6 +2646,12 @@
         public String getOpPackageName() {
             return mMockContext.getOpPackageName();
         }
+
+        @Override
+        public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+                throws PackageManager.NameNotFoundException {
+            return mMockContext.createPackageContextAsUser(packageName, flags, user);
+        }
     }
 
     static class TestAccountAuthenticatorCache extends AccountAuthenticatorCache {
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
index 9a2c190..614680e 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTestFixtures.java
@@ -31,7 +31,8 @@
             "account_manager_service_test:account_status_token_key";
     public static final String KEY_ACCOUNT_PASSWORD =
             "account_manager_service_test:account_password_key";
-
+    public static final String KEY_OPTIONS_BUNDLE =
+            "account_manager_service_test:option_bundle_key";
     public static final String ACCOUNT_NAME_SUCCESS = "success_on_return@fixture.com";
     public static final String ACCOUNT_NAME_INTERVENE = "intervene@fixture.com";
     public static final String ACCOUNT_NAME_ERROR = "error@fixture.com";
@@ -47,7 +48,20 @@
 
     public static final String ACCOUNT_STATUS_TOKEN =
             "com.android.server.accounts.account_manager_service_test.account.status.token";
-
+    public static final String AUTH_TOKEN_LABEL =
+            "com.android.server.accounts.account_manager_service_test.auth.token.label";
+    public static final String AUTH_TOKEN =
+            "com.android.server.accounts.account_manager_service_test.auth.token";
+    public static final String KEY_TOKEN_EXPIRY =
+            "com.android.server.accounts.account_manager_service_test.auth.token.expiry";
+    public static final String ACCOUNT_FEATURE1 =
+            "com.android.server.accounts.account_manager_service_test.feature1";
+    public static final String ACCOUNT_FEATURE2 =
+            "com.android.server.accounts.account_manager_service_test.feature2";
+    public static final String[] ACCOUNT_FEATURES =
+            new String[]{ACCOUNT_FEATURE1, ACCOUNT_FEATURE2};
+    public static final String CALLER_PACKAGE =
+            "com.android.server.accounts.account_manager_service_test.caller.package";
     public static final String ACCOUNT_PASSWORD =
             "com.android.server.accounts.account_manager_service_test.account.password";
     public static final String KEY_RESULT = "account_manager_service_test:result";
diff --git a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
index 8ec6176..eb839a2 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/TestAccountType1Authenticator.java
@@ -45,8 +45,15 @@
 
     @Override
     public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
-        throw new UnsupportedOperationException(
-                "editProperties is not yet supported by the TestAccountAuthenticator");
+        Bundle result = new Bundle();
+        result.putString(AccountManager.KEY_ACCOUNT_NAME,
+                AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS);
+        result.putString(AccountManager.KEY_ACCOUNT_TYPE,
+                AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1);
+        result.putString(
+                AccountManager.KEY_AUTHTOKEN,
+                Integer.toString(mTokenCounter.incrementAndGet()));
+        return result;
     }
 
     @Override
@@ -59,10 +66,38 @@
         if (!mAccountType.equals(accountType)) {
             throw new IllegalArgumentException("Request to the wrong authenticator!");
         }
+        String accountName = null;
+
+        if (options != null) {
+            accountName = options.getString(AccountManagerServiceTestFixtures.KEY_ACCOUNT_NAME);
+        }
 
         Bundle result = new Bundle();
-        result.putString(AccountManager.KEY_ACCOUNT_NAME, "test_account@test.com");
-        result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
+        if (accountName.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+            // fill bundle with a success result.
+            result.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
+            result.putString(AccountManager.KEY_AUTHTOKEN,
+                    Integer.toString(mTokenCounter.incrementAndGet()));
+            result.putParcelable(AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE, options);
+        } else if (accountName.equals(
+                AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+            // Specify data to be returned by the eventual activity.
+            Intent eventualActivityResultData = new Intent();
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName);
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType);
+            // Fill result with Intent.
+            Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT, eventualActivityResultData);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+            result.putParcelable(AccountManager.KEY_INTENT, intent);
+        } else {
+            fillResultWithError(
+                    result,
+                    AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                    AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        }
         return result;
     }
 
@@ -71,8 +106,38 @@
             AccountAuthenticatorResponse response,
             Account account,
             Bundle options) throws NetworkErrorException {
-        throw new UnsupportedOperationException(
-                "confirmCredentials is not yet supported by the TestAccountAuthenticator");
+        if (!mAccountType.equals(account.type)) {
+            throw new IllegalArgumentException("Request to the wrong authenticator!");
+        }
+        Bundle result = new Bundle();
+
+        if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+            // fill bundle with a success result.
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+            result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+        } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+            // Specify data to be returned by the eventual activity.
+            Intent eventualActivityResultData = new Intent();
+            eventualActivityResultData.putExtra(AccountManager.KEY_BOOLEAN_RESULT, true);
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+            // Fill result with Intent.
+            Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
+                    eventualActivityResultData);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+            result.putParcelable(AccountManager.KEY_INTENT, intent);
+        } else {
+            // fill with error
+            fillResultWithError(
+                    result,
+                    AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                    AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        }
+        return result;
     }
 
     @Override
@@ -81,14 +146,53 @@
             Account account,
             String authTokenType,
             Bundle options) throws NetworkErrorException {
-        throw new UnsupportedOperationException(
-                "getAuthToken is not yet supported by the TestAccountAuthenticator");
+        if (!mAccountType.equals(account.type)) {
+            throw new IllegalArgumentException("Request to the wrong authenticator!");
+        }
+        Bundle result = new Bundle();
+
+        long expiryMillis = (options == null)
+                ? 0 : options.getLong(AccountManagerServiceTestFixtures.KEY_TOKEN_EXPIRY);
+        if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+            // fill bundle with a success result.
+            result.putString(
+                    AccountManager.KEY_AUTHTOKEN, AccountManagerServiceTestFixtures.AUTH_TOKEN);
+            result.putLong(
+                    AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY,
+                    expiryMillis);
+            result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+        } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+            // Specify data to be returned by the eventual activity.
+            Intent eventualActivityResultData = new Intent();
+            eventualActivityResultData.putExtra(
+                    AccountManager.KEY_AUTHTOKEN, AccountManagerServiceTestFixtures.AUTH_TOKEN);
+            eventualActivityResultData.putExtra(
+                    AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY,
+                    expiryMillis);
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+            // Fill result with Intent.
+            Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
+                    eventualActivityResultData);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+            result.putParcelable(AccountManager.KEY_INTENT, intent);
+
+        } else {
+            fillResultWithError(
+                    result,
+                    AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                    AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        }
+        return result;
     }
 
     @Override
     public String getAuthTokenLabel(String authTokenType) {
-        throw new UnsupportedOperationException(
-                "getAuthTokenLabel is not yet supported by the TestAccountAuthenticator");
+        return AccountManagerServiceTestFixtures.AUTH_TOKEN_LABEL;
     }
 
     @Override
@@ -101,8 +205,31 @@
             throw new IllegalArgumentException("Request to the wrong authenticator!");
         }
         Bundle result = new Bundle();
-        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
-        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+        if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+            // fill bundle with a success result.
+            result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+            result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+        } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+            // Specify data to be returned by the eventual activity.
+            Intent eventualActivityResultData = new Intent();
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
+            eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+
+            // Fill result with Intent.
+            Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_RESULT,
+                    eventualActivityResultData);
+            intent.putExtra(AccountManagerServiceTestFixtures.KEY_CALLBACK, response);
+
+            result.putParcelable(AccountManager.KEY_INTENT, intent);
+        } else {
+            // fill with error
+            fillResultWithError(
+                    result,
+                    AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                    AccountManagerServiceTestFixtures.ERROR_MESSAGE);
+        }
         return result;
     }
 
@@ -111,8 +238,20 @@
             AccountAuthenticatorResponse response,
             Account account,
             String[] features) throws NetworkErrorException {
-        throw new UnsupportedOperationException(
-                "hasFeatures is not yet supported by the TestAccountAuthenticator");
+        Bundle result = new Bundle();
+        if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+            // fill bundle with true.
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+        } else if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+            // fill bundle with false.
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+        } else {
+            // return null for error
+            result = null;
+        }
+
+        response.onResult(result);
+        return null;
     }
 
     @Override
@@ -300,6 +439,22 @@
         return null;
     }
 
+    @Override
+    public Bundle getAccountRemovalAllowed(
+            AccountAuthenticatorResponse response, Account account) throws NetworkErrorException {
+        Bundle result = new Bundle();
+        if (account.name.equals(AccountManagerServiceTestFixtures.ACCOUNT_NAME_SUCCESS)) {
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+        } else if (account.name.equals(
+                AccountManagerServiceTestFixtures.ACCOUNT_NAME_INTERVENE)) {
+            Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
+            result.putParcelable(AccountManager.KEY_INTENT, intent);
+        } else {
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+        }
+        return result;
+    }
+
     private void fillResultWithError(Bundle result, Bundle options) {
         int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE;
         String errorMsg = "Default Error Message";
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 4927f0c..3b92a34 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -37,6 +37,7 @@
 import com.android.internal.widget.LockPatternUtils;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Map;
 
 /**
@@ -264,6 +265,12 @@
         }
 
         @Override
+        void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
+                throws IOException {
+            context.recoverySystem.rebootWipeUserData(shutdown, reason, force);
+        }
+
+        @Override
         boolean systemPropertiesGetBoolean(String key, boolean def) {
             return context.systemProperties.getBoolean(key, def);
         }
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 8da47c8..c29668f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -68,6 +68,9 @@
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
+import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
@@ -82,6 +85,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -1128,6 +1132,7 @@
 
     public void testSetGetApplicationRestriction() {
         setAsProfileOwner(admin1);
+        mContext.packageName = admin1.getPackageName();
 
         {
             Bundle rest = new Bundle();
@@ -1159,29 +1164,131 @@
         assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg2").size());
     }
 
+    /**
+     * Setup a package in the package manager mock. Useful for faking installed applications.
+     *
+     * @param packageName the name of the package to be setup
+     * @param appId the application ID to be given to the package
+     * @return the UID of the package as known by the mock package manager
+     */
+    private int setupPackageInPackageManager(final String packageName, final int appId)
+            throws Exception {
+        // Make the PackageManager return the package instead of throwing a NameNotFoundException
+        final PackageInfo pi = new PackageInfo();
+        pi.applicationInfo = new ApplicationInfo();
+        pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
+        doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
+                eq(packageName),
+                anyInt(),
+                eq(DpmMockContext.CALLER_USER_HANDLE));
+        // Setup application UID with the PackageManager
+        final int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, appId);
+        doReturn(uid).when(mContext.packageManager).getPackageUidAsUser(
+                eq(packageName),
+                eq(DpmMockContext.CALLER_USER_HANDLE));
+        // Associate packageName to uid
+        doReturn(packageName).when(mContext.ipackageManager).getNameForUid(eq(uid));
+        doReturn(new String[]{packageName})
+            .when(mContext.ipackageManager).getPackagesForUid(eq(uid));
+        return uid;
+    }
+
+    /**
+     * Simple test for delegate set/get and general delegation. Tests verifying that delegated
+     * privileges can acually be exercised by a delegate are not covered here.
+     */
+    public void testDelegation() throws Exception {
+        setAsProfileOwner(admin1);
+
+        final int userHandle = DpmMockContext.CALLER_USER_HANDLE;
+
+        // Given two packages
+        final String CERT_DELEGATE = "com.delegate.certs";
+        final String RESTRICTIONS_DELEGATE = "com.delegate.apprestrictions";
+        final int CERT_DELEGATE_UID = setupPackageInPackageManager(CERT_DELEGATE, 20988);
+        final int RESTRICTIONS_DELEGATE_UID = setupPackageInPackageManager(RESTRICTIONS_DELEGATE,
+                20989);
+
+        // On delegation
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.packageName = admin1.getPackageName();
+        dpm.setCertInstallerPackage(admin1, CERT_DELEGATE);
+        dpm.setApplicationRestrictionsManagingPackage(admin1, RESTRICTIONS_DELEGATE);
+
+        // DPMS correctly stores and retrieves the delegates
+        DevicePolicyManagerService.DevicePolicyData policy = dpms.mUserData.get(userHandle);
+        assertEquals(2, policy.mDelegationMap.size());
+        MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(CERT_DELEGATE),
+            DELEGATION_CERT_INSTALL);
+        MoreAsserts.assertContentsInAnyOrder(dpm.getDelegatedScopes(admin1, CERT_DELEGATE),
+            DELEGATION_CERT_INSTALL);
+        assertEquals(CERT_DELEGATE, dpm.getCertInstallerPackage(admin1));
+        MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(RESTRICTIONS_DELEGATE),
+            DELEGATION_APP_RESTRICTIONS);
+        MoreAsserts.assertContentsInAnyOrder(dpm.getDelegatedScopes(admin1, RESTRICTIONS_DELEGATE),
+            DELEGATION_APP_RESTRICTIONS);
+        assertEquals(RESTRICTIONS_DELEGATE, dpm.getApplicationRestrictionsManagingPackage(admin1));
+
+        // On calling install certificate APIs from an unauthorized process
+        mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
+        mContext.packageName = RESTRICTIONS_DELEGATE;
+
+        // DPMS throws a SecurityException
+        try {
+            dpm.installCaCert(null, null);
+            fail("Didn't throw SecurityException on unauthorized access");
+        } catch (SecurityException expected) {
+        }
+
+        // On calling install certificate APIs from an authorized process
+        mContext.binder.callingUid = CERT_DELEGATE_UID;
+        mContext.packageName = CERT_DELEGATE;
+
+        // DPMS executes without a SecurityException
+        try {
+            dpm.installCaCert(null, null);
+        } catch (SecurityException unexpected) {
+            fail("Threw SecurityException on authorized access");
+        } catch (NullPointerException expected) {
+        }
+
+        // On removing a delegate
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.packageName = admin1.getPackageName();
+        dpm.setCertInstallerPackage(admin1, null);
+
+        // DPMS does not allow access to ex-delegate
+        mContext.binder.callingUid = CERT_DELEGATE_UID;
+        mContext.packageName = CERT_DELEGATE;
+        try {
+            dpm.installCaCert(null, null);
+            fail("Didn't throw SecurityException on unauthorized access");
+        } catch (SecurityException expected) {
+        }
+
+        // But still allows access to other existing delegates
+        mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
+        mContext.packageName = RESTRICTIONS_DELEGATE;
+        try {
+            dpm.getApplicationRestrictions(null, "pkg");
+        } catch (SecurityException expected) {
+            fail("Threw SecurityException on authorized access");
+        }
+    }
+
     public void testApplicationRestrictionsManagingApp() throws Exception {
         setAsProfileOwner(admin1);
 
         final String nonExistAppRestrictionsManagerPackage = "com.google.app.restrictions.manager2";
         final String appRestrictionsManagerPackage = "com.google.app.restrictions.manager";
         final int appRestrictionsManagerAppId = 20987;
-        final int appRestrictionsManagerUid = UserHandle.getUid(
-                DpmMockContext.CALLER_USER_HANDLE, appRestrictionsManagerAppId);
-        doReturn(appRestrictionsManagerUid).when(mContext.packageManager).getPackageUidAsUser(
-                eq(appRestrictionsManagerPackage),
-                eq(DpmMockContext.CALLER_USER_HANDLE));
-        mContext.binder.callingUid = appRestrictionsManagerUid;
-
-        final PackageInfo pi = new PackageInfo();
-        pi.applicationInfo = new ApplicationInfo();
-        pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
-        doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
-                eq(appRestrictionsManagerPackage),
-                anyInt(),
-                eq(DpmMockContext.CALLER_USER_HANDLE));
+        final int appRestrictionsManagerUid = setupPackageInPackageManager(
+                appRestrictionsManagerPackage, appRestrictionsManagerAppId);
 
         // appRestrictionsManager package shouldn't be able to manage restrictions as the PO hasn't
         // delegated that permission yet.
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.packageName = admin1.getPackageName();
         assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
         Bundle rest = new Bundle();
         rest.putString("KEY_STRING", "Foo1");
@@ -1190,18 +1297,21 @@
             fail("Didn't throw expected SecurityException");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "caller cannot manage application restrictions", expected.getMessage());
+                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    expected.getMessage());
         }
         try {
             dpm.getApplicationRestrictions(null, "pkg1");
             fail("Didn't throw expected SecurityException");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "caller cannot manage application restrictions", expected.getMessage());
+                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    expected.getMessage());
         }
 
         // Check via the profile owner that no restrictions were set.
         mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.packageName = admin1.getPackageName();
         assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size());
 
         // Check the API does not allow setting a non-existent package
@@ -1221,6 +1331,7 @@
 
         // Now that package should be able to set and retrieve app restrictions.
         mContext.binder.callingUid = appRestrictionsManagerUid;
+        mContext.packageName = appRestrictionsManagerPackage;
         assertTrue(dpm.isCallerApplicationRestrictionsManagingPackage());
         dpm.setApplicationRestrictions(null, "pkg1", rest);
         Bundle returned = dpm.getApplicationRestrictions(null, "pkg1");
@@ -1236,12 +1347,14 @@
             fail("Didn't throw expected SecurityException");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "caller cannot manage application restrictions", expected.getMessage());
+                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    expected.getMessage());
         }
 
         // The DPM is still able to manage app restrictions, even if it allowed another app to do it
         // too.
         mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        mContext.packageName = admin1.getPackageName();
         assertEquals(returned, dpm.getApplicationRestrictions(admin1, "pkg1"));
         dpm.setApplicationRestrictions(admin1, "pkg1", null);
         assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size());
@@ -1250,13 +1363,15 @@
         dpm.setApplicationRestrictionsManagingPackage(admin1, null);
         assertNull(dpm.getApplicationRestrictionsManagingPackage(admin1));
         mContext.binder.callingUid = appRestrictionsManagerUid;
+        mContext.packageName = appRestrictionsManagerPackage;
         assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
         try {
             dpm.setApplicationRestrictions(null, "pkg1", null);
             fail("Didn't throw expected SecurityException");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "caller cannot manage application restrictions", expected.getMessage());
+                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    expected.getMessage());
         }
     }
 
@@ -1835,6 +1950,81 @@
         }
     }
 
+    public void testCreateAdminSupportIntent() throws Exception {
+        // Setup device owner.
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+
+        // Nonexisting permission returns null
+        Intent intent = dpm.createAdminSupportIntent("disallow_nothing");
+        assertNull(intent);
+
+        // Existing permission that is not set returns null
+        intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+        assertNull(intent);
+
+        // Existing permission that is not set by device/profile owner returns null
+        when(mContext.userManager.hasUserRestriction(
+                eq(UserManager.DISALLOW_ADJUST_VOLUME),
+                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+                .thenReturn(true);
+        intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+        assertNull(intent);
+
+        // Permission that is set by device owner returns correct intent
+        when(mContext.userManager.getUserRestrictionSource(
+                eq(UserManager.DISALLOW_ADJUST_VOLUME),
+                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+        intent = dpm.createAdminSupportIntent(UserManager.DISALLOW_ADJUST_VOLUME);
+        assertNotNull(intent);
+        assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, intent.getAction());
+        assertEquals(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID),
+                intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+        assertEquals(admin1,
+                (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN));
+        assertEquals(UserManager.DISALLOW_ADJUST_VOLUME,
+                intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+        // Try with POLICY_DISABLE_CAMERA and POLICY_DISABLE_SCREEN_CAPTURE, which are not
+        // user restrictions
+
+        // Camera is not disabled
+        intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+        assertNull(intent);
+
+        // Camera is disabled
+        dpm.setCameraDisabled(admin1, true);
+        intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+        assertNotNull(intent);
+        assertEquals(DevicePolicyManager.POLICY_DISABLE_CAMERA,
+                intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+        // Screen capture is not disabled
+        intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+        assertNull(intent);
+
+        // Screen capture is disabled
+        dpm.setScreenCaptureDisabled(admin1, true);
+        intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+        assertNotNull(intent);
+        assertEquals(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE,
+                intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+
+        // Same checks for different user
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        // Camera should be disabled by device owner
+        intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_CAMERA);
+        assertNotNull(intent);
+        assertEquals(DevicePolicyManager.POLICY_DISABLE_CAMERA,
+                intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION));
+        assertEquals(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID),
+                intent.getIntExtra(Intent.EXTRA_USER_ID, -1));
+        // ScreenCapture should not be disabled by device owner
+        intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+        assertNull(intent);
+    }
+
     /**
      * Test for:
      * {@link DevicePolicyManager#setAffiliationIds}
@@ -2358,6 +2548,23 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
     }
 
+    private void setup_nonSplitUser_withDo_primaryUser() throws Exception {
+        setDeviceOwner();
+        setup_nonSplitUser_afterDeviceSetup_primaryUser();
+        setUpPackageManagerForFakeAdmin(adminAnotherPackage, DpmMockContext.ANOTHER_UID, admin2);
+    }
+
+    private void setup_nonSplitUser_withDo_primaryUser_ManagedProfile() throws Exception {
+        setup_nonSplitUser_withDo_primaryUser();
+        final int MANAGED_PROFILE_USER_ID = 18;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+                false /* we can't remove a managed profile */)).thenReturn(false);
+        when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+                true)).thenReturn(true);
+    }
+
     public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser()
             throws Exception {
         setup_nonSplitUser_afterDeviceSetup_primaryUser();
@@ -2387,144 +2594,124 @@
                 DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT);
     }
 
-    public void testIsProvisioningAllowed_nonSplitUser_withDo_primaryUser() throws Exception {
-        setDeviceOwner();
-        setup_nonSplitUser_afterDeviceSetup_primaryUser();
-        setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
+    public void testProvisioning_nonSplitUser_withDo_primaryUser() throws Exception {
+        setup_nonSplitUser_withDo_primaryUser();
         mContext.packageName = admin1.getPackageName();
-
-        final ComponentName adminDifferentPackage =
-                new ComponentName("another.package", "whatever.random.class");
-        final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948);
-        setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2);
-
-        // COMP mode is allowed.
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
-        when(mContext.userManager.hasUserRestriction(
-                eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
-                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
-                .thenReturn(true);
-
-        // The DO should be allowed to initiate provisioning if it set the restriction itself.
-        when(mContext.userManager.getUserRestrictionSource(
-                eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
-                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
-                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
-        // But another app should not
-        mContext.binder.callingUid = ANOTHER_UID;
-        mContext.packageName = adminDifferentPackage.getPackageName();
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
-        // The DO should not be allowed to initiate provisioning if the restriction is set by
-        // another entity.
-        when(mContext.userManager.getUserRestrictionSource(
-                eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
-                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
-                .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-        mContext.packageName = admin1.getPackageName();
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
-        mContext.binder.callingUid = ANOTHER_UID;
-        mContext.packageName = adminDifferentPackage.getPackageName();
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-    }
-
-    public void testIsProvisioningAllowed_nonSplitUser_comp() throws Exception {
-        setDeviceOwner();
-        setup_nonSplitUser_afterDeviceSetup_primaryUser();
-        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
-
-        final ComponentName adminDifferentPackage =
-                new ComponentName("another.package", "whatever.class");
-        final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948);
-        setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2);
-
-        final int MANAGED_PROFILE_USER_ID = 18;
-        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
-        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
-
-        when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
-                false /* we can't remove a managed profile */)).thenReturn(false);
-        when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
-                true)).thenReturn(true);
-
-        // We can delete the managed profile to create a new one, so provisioning is allowed.
-        mContext.packageName = admin1.getPackageName();
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
-        mContext.packageName = adminDifferentPackage.getPackageName();
-        mContext.binder.callingUid = ANOTHER_UID;
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
-        when(mContext.userManager.hasUserRestriction(
-                eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
-                eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))))
-                .thenReturn(true);
-
-        // Now, we can't remove the profile any more to create a new one.
-        mContext.packageName = admin1.getPackageName();
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
-        mContext.packageName = adminDifferentPackage.getPackageName();
-        mContext.binder.callingUid = ANOTHER_UID;
-        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-    }
-
-    public void
-    testCheckProvisioningPreCondition_nonSplitUser_withDo_primaryUser() throws Exception {
-        setDeviceOwner();
-        setup_nonSplitUser_afterDeviceSetup_primaryUser();
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
 
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
 
         // COMP mode is allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
 
-        // And other DPCs can also provisioning a managed profile (DO + BYOD case).
+        // And other DPCs can also provision a managed profile (DO + BYOD case).
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                "some.other.dpc.package.name",
+                DpmMockContext.ANOTHER_PACKAGE_NAME,
                 DevicePolicyManager.CODE_OK);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+                DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+    }
 
+    public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception {
+        setup_nonSplitUser_withDo_primaryUser();
+        mContext.packageName = admin1.getPackageName();
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        // The DO should be allowed to initiate provisioning if it set the restriction itself, but
+        // other packages should be forbidden.
         when(mContext.userManager.hasUserRestriction(
                 eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
                 eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
                 .thenReturn(true);
-
-        // The DO should be allowed to initiate provisioning if it set the restriction itself, but
-        // other packages should be forbidden.
         when(mContext.userManager.getUserRestrictionSource(
                 eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
                 eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
                 .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                "some.other.dpc.package.name",
+                DpmMockContext.ANOTHER_PACKAGE_NAME,
                 DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+                DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+    }
 
+    public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedBySystem()
+            throws Exception {
+        setup_nonSplitUser_withDo_primaryUser();
+        mContext.packageName = admin1.getPackageName();
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         // The DO should not be allowed to initiate provisioning if the restriction is set by
         // another entity.
+        when(mContext.userManager.hasUserRestriction(
+                eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+                eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+                .thenReturn(true);
         when(mContext.userManager.getUserRestrictionSource(
                 eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
                 eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
                 .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
-                assertCheckProvisioningPreCondition(
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
+
+        assertCheckProvisioningPreCondition(
                 DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
-                "some.other.dpc.package.name",
+                DpmMockContext.ANOTHER_PACKAGE_NAME,
                 DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+                DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+    }
+
+    public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception {
+        setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
+        mContext.packageName = admin1.getPackageName();
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+
+        // We can delete the managed profile to create a new one, so provisioning is allowed.
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                DevicePolicyManager.CODE_OK);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+        assertCheckProvisioningPreCondition(
+                DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                DpmMockContext.ANOTHER_PACKAGE_NAME,
+                DevicePolicyManager.CODE_OK);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+                DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+    }
+
+    public void testCheckProvisioningPreCondition_nonSplitUser_comp_cannot_remove_profile()
+            throws Exception {
+        setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
+        mContext.packageName = admin1.getPackageName();
+        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+        when(mContext.userManager.hasUserRestriction(
+                eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(true);
+        when(mContext.userManager.getUserRestrictionSource(
+                eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+                eq(UserHandle.SYSTEM)))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+        // We can't remove the profile to create a new one.
+        assertCheckProvisioningPreCondition(
+                DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                DpmMockContext.ANOTHER_PACKAGE_NAME,
+                DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+                DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+
+        // But the device owner can still do it because it has set the restriction itself.
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                DevicePolicyManager.CODE_OK);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
     }
 
     private void setup_splitUser_firstBoot_systemUser() throws Exception {
@@ -3233,6 +3420,140 @@
         }
     }
 
+    public void testWipeDataDeviceOwner() throws Exception {
+        setDeviceOwner();
+        when(mContext.userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_FACTORY_RESET,
+                UserHandle.SYSTEM))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+        dpm.wipeData(0);
+        verify(mContext.recoverySystem).rebootWipeUserData(
+                /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+    }
+
+    public void testWipeDataDeviceOwnerDisallowed() throws Exception {
+        setDeviceOwner();
+        when(mContext.userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_FACTORY_RESET,
+                UserHandle.SYSTEM))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+        try {
+            // The DO is not allowed to wipe the device if the user restriction was set
+            // by the system
+            dpm.wipeData(0);
+            fail("SecurityException not thrown");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testMaximumFailedPasswordAttemptsReachedManagedProfile() throws Exception {
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+        // Even if the caller is the managed profile, the current user is the user 0
+        when(mContext.iactivityManager.getCurrentUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+        when(mContext.userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                UserHandle.of(MANAGED_PROFILE_USER_ID)))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
+
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+        // Failed password attempts on the parent user are taken into account, as there isn't a
+        // separate work challenge.
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+        // The profile should be wiped even if DISALLOW_REMOVE_MANAGED_PROFILE is enabled, because
+        // both the user restriction and the policy were set by the PO.
+        verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed(
+                MANAGED_PROFILE_USER_ID);
+        verifyZeroInteractions(mContext.recoverySystem);
+    }
+
+    public void testMaximumFailedPasswordAttemptsReachedManagedProfileDisallowed()
+            throws Exception {
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+        // Even if the caller is the managed profile, the current user is the user 0
+        when(mContext.iactivityManager.getCurrentUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+        when(mContext.userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                UserHandle.of(MANAGED_PROFILE_USER_ID)))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+        // Failed password attempts on the parent user are taken into account, as there isn't a
+        // separate work challenge.
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+        // DISALLOW_REMOVE_MANAGED_PROFILE was set by the system, not the PO, so the profile is
+        // not wiped.
+        verify(mContext.userManagerInternal, never())
+                .removeUserEvenWhenDisallowed(anyInt());
+        verifyZeroInteractions(mContext.recoverySystem);
+    }
+
+    public void testMaximumFailedPasswordAttemptsReachedDeviceOwner() throws Exception {
+        setDeviceOwner();
+        when(mContext.userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_FACTORY_RESET,
+                UserHandle.SYSTEM))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+        dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+        // The device should be wiped even if DISALLOW_FACTORY_RESET is enabled, because both the
+        // user restriction and the policy were set by the DO.
+        verify(mContext.recoverySystem).rebootWipeUserData(
+                /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+    }
+
+    public void testMaximumFailedPasswordAttemptsReachedDeviceOwnerDisallowed() throws Exception {
+        setDeviceOwner();
+        when(mContext.userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_FACTORY_RESET,
+                UserHandle.SYSTEM))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+
+        dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+        dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+        // DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped.
+        verifyZeroInteractions(mContext.recoverySystem);
+        verify(mContext.userManagerInternal, never())
+                .removeUserEvenWhenDisallowed(anyInt());
+    }
+
     public void testGetPermissionGrantState() throws Exception {
         final String permission = "some.permission";
         final String app1 = "com.example.app1";
@@ -3287,6 +3608,21 @@
                 dpm.isProvisioningAllowed(action));
     }
 
+    private void assertProvisioningAllowed(String action, boolean expected, String packageName,
+            int uid) {
+        String previousPackageName = mContext.packageName;
+        int previousUid = mMockContext.binder.callingUid;
+
+        // Call assertProvisioningAllowed with the packageName / uid passed as arguments.
+        mContext.packageName = packageName;
+        mMockContext.binder.callingUid = uid;
+        assertProvisioningAllowed(action, expected);
+
+        // Set the previous package name / calling uid to go back to the initial state.
+        mContext.packageName = previousPackageName;
+        mMockContext.binder.callingUid = previousUid;
+    }
+
     private void assertCheckProvisioningPreCondition(String action, int provisioningCondition) {
         assertCheckProvisioningPreCondition(action, admin1.getPackageName(), provisioningCondition);
     }
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 44bf547..22cd135 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -55,6 +55,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -99,6 +100,10 @@
      */
     public static final int SYSTEM_PID = 11111;
 
+    public static final String ANOTHER_PACKAGE_NAME = "com.another.package.name";
+
+    public static final int ANOTHER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, 18434);
+
     public static class MockBinder {
         public int callingUid = CALLER_UID;
         public int callingPid = CALLER_PID;
@@ -154,6 +159,12 @@
         }
     }
 
+    public static class RecoverySystemForMock {
+        public void rebootWipeUserData(
+                boolean shutdown, String reason, boolean force) throws IOException {
+        }
+    }
+
     public static class SystemPropertiesForMock {
         public boolean getBoolean(String key, boolean def) {
             return false;
@@ -263,6 +274,7 @@
     public final UserManagerForMock userManagerForMock;
     public final PowerManagerForMock powerManager;
     public final PowerManagerInternal powerManagerInternal;
+    public final RecoverySystemForMock recoverySystem;
     public final NotificationManager notificationManager;
     public final IIpConnectivityMetrics iipConnectivityMetrics;
     public final IWindowManager iwindowManager;
@@ -308,6 +320,7 @@
         packageManagerInternal = mock(PackageManagerInternal.class);
         powerManager = mock(PowerManagerForMock.class);
         powerManagerInternal = mock(PowerManagerInternal.class);
+        recoverySystem = mock(RecoverySystemForMock.class);
         notificationManager = mock(NotificationManager.class);
         iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
         iwindowManager = mock(IWindowManager.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 8a11976..ed6779c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -45,6 +45,7 @@
     public ComponentName admin1;
     public ComponentName admin2;
     public ComponentName admin3;
+    public ComponentName adminAnotherPackage;
     public ComponentName adminNoPerm;
 
     @Override
@@ -59,6 +60,8 @@
         admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
         admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
         admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
+        adminAnotherPackage = new ComponentName(DpmMockContext.ANOTHER_PACKAGE_NAME,
+                "whatever.random.class");
         adminNoPerm = new ComponentName(mRealTestContext, DummyDeviceAdmins.AdminNoPerm.class);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
index 2aca702..13a3c2f 100644
--- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
@@ -155,7 +155,7 @@
     }
 
     @Test
-    public void testDuplicatePackageNameIsMergedAcrossMultipleUsers() throws Exception {
+    public void testDuplicatePackageNameIsNotMergedAcrossMultipleUsers() throws Exception {
         PackageStats app = new PackageStats("com.test.app");
         app.dataSize = 1000;
         app.externalDataSize = 1000;
@@ -175,19 +175,19 @@
         logger.dumpToFile(mOutputFile);
 
         JSONObject output = getOutputFileAsJson();
-        assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2200);
-        assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22);
+        assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2000);
+        assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(20);
         JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
         assertThat(packageNames.length()).isEqualTo(1);
         assertThat(packageNames.getString(0)).isEqualTo("com.test.app");
 
         JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
         assertThat(appSizes.length()).isEqualTo(1);
-        assertThat(appSizes.getLong(0)).isEqualTo(2200);
+        assertThat(appSizes.getLong(0)).isEqualTo(2000);
 
         JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
         assertThat(cacheSizes.length()).isEqualTo(1);
-        assertThat(cacheSizes.getLong(0)).isEqualTo(22);
+        assertThat(cacheSizes.getLong(0)).isEqualTo(20);
     }
 
     private JSONObject getOutputFileAsJson() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
index 67e78d4..cd3ae1a 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java
@@ -38,16 +38,18 @@
     private final int mNumRelros;
     private final boolean mIsDebuggable;
     private int mMultiProcessSetting;
+    private final boolean mMultiProcessDefault;
 
     public static final int PRIMARY_USER_ID = 0;
 
     public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled,
-            int numRelros, boolean isDebuggable) {
+            int numRelros, boolean isDebuggable, boolean multiProcessDefault) {
         mPackageConfigs = packageConfigs;
         mFallbackLogicEnabled = fallbackLogicEnabled;
         mNumRelros = numRelros;
         mIsDebuggable = isDebuggable;
         mUsers.add(PRIMARY_USER_ID);
+        mMultiProcessDefault = multiProcessDefault;
     }
 
     public void addUser(int userId) {
@@ -187,4 +189,9 @@
 
     @Override
     public void notifyZygote(boolean enableMultiProcess) {}
+
+    @Override
+    public boolean isMultiProcessDefaultEnabled() {
+        return mMultiProcessDefault;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 33cedfa..4c0f042 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -84,8 +85,15 @@
 
     private void setupWithPackages(WebViewProviderInfo[] packages,
             boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable) {
+        setupWithPackages(packages, fallbackLogicEnabled, numRelros, isDebuggable,
+                false /* multiProcessDefault */);
+    }
+
+    private void setupWithPackages(WebViewProviderInfo[] packages,
+            boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable,
+            boolean multiProcessDefault) {
         TestSystemImpl testing = new TestSystemImpl(packages, fallbackLogicEnabled, numRelros,
-                isDebuggable);
+                isDebuggable, multiProcessDefault);
         mTestSystemImpl = Mockito.spy(testing);
         mWebViewUpdateServiceImpl =
             new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl);
@@ -1521,4 +1529,89 @@
         assertEquals(firstPackage.versionName,
                 mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName);
     }
+
+    @Test
+    public void testMultiProcessEnabledByDefault() {
+        String primaryPackage = "primary";
+        WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
+            new WebViewProviderInfo(
+                    primaryPackage, "", true /* default available */, false /* fallback */, null)};
+        setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */,
+                          true /* debuggable */, true /* multiprocess by default */);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+                    true /* valid */, true /* installed */, null /* signatures */,
+                    10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
+                    false /* isSystemApp */));
+
+        runWebViewBootPreparationOnMainSync();
+        checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
+
+        // Check it's on by default
+        assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+        // Test toggling it
+        mWebViewUpdateServiceImpl.enableMultiProcess(false);
+        assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+        mWebViewUpdateServiceImpl.enableMultiProcess(true);
+        assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+        // Disable, then upgrade provider, which should re-enable it
+        mWebViewUpdateServiceImpl.enableMultiProcess(false);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+                    true /* valid */, true /* installed */, null /* signatures */,
+                    20 /* lastUpdateTime*/, false /* not hidden */, 2000 /* versionCode */,
+                    false /* isSystemApp */));
+        mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+                WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+        checkPreparationPhasesForPackage(primaryPackage, 2);
+        assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+    }
+
+    @Test
+    public void testMultiProcessDisabledByDefault() {
+        String primaryPackage = "primary";
+        WebViewProviderInfo[] packages = new WebViewProviderInfo[] {
+            new WebViewProviderInfo(
+                    primaryPackage, "", true /* default available */, false /* fallback */, null)};
+        setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */,
+                          true /* debuggable */, false /* not multiprocess by default */);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+                    true /* valid */, true /* installed */, null /* signatures */,
+                    10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */,
+                    false /* isSystemApp */));
+
+        runWebViewBootPreparationOnMainSync();
+        checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */);
+
+        // Check it's off by default
+        assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+        // Test toggling it
+        mWebViewUpdateServiceImpl.enableMultiProcess(true);
+        assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+        mWebViewUpdateServiceImpl.enableMultiProcess(false);
+        assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+        // Disable, then upgrade provider, which should not re-enable it
+        mWebViewUpdateServiceImpl.enableMultiProcess(false);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+                    true /* valid */, true /* installed */, null /* signatures */,
+                    20 /* lastUpdateTime*/, false /* not hidden */, 2000 /* versionCode */,
+                    false /* isSystemApp */));
+        mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+                WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+        checkPreparationPhasesForPackage(primaryPackage, 2);
+        assertFalse(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+
+        // Enable, then upgrade provider, which should leave it on
+        mWebViewUpdateServiceImpl.enableMultiProcess(true);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */,
+                    true /* valid */, true /* installed */, null /* signatures */,
+                    30 /* lastUpdateTime*/, false /* not hidden */, 3000 /* versionCode */,
+                    false /* isSystemApp */));
+        mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
+                WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
+        checkPreparationPhasesForPackage(primaryPackage, 3);
+        assertTrue(mWebViewUpdateServiceImpl.isMultiProcessEnabled());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 1d9875f..154fa91 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -119,6 +119,7 @@
         sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
         assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, sWm.mLastOrientation);
         assertTrue(appWindow.resizeReported);
+        appWindow.removeImmediately();
     }
 
     @Test
@@ -148,5 +149,6 @@
         sWm.updateRotation(false, false);
         sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */);
         assertTrue(appWindow.resizeReported);
+        appWindow.removeImmediately();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 85931e8..e54e319 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -236,7 +236,7 @@
         assertEquals(dc, token.getDisplayContent());
 
         // Move stack to first display.
-        sWm.moveStackToDisplay(stack.mStackId, sDisplayContent.getDisplayId());
+        sDisplayContent.moveStackToDisplay(stack);
         assertEquals(sDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
         assertEquals(sDisplayContent, stack.getParent().getParent());
         assertEquals(sDisplayContent, stack.getDisplayContent());
diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
new file mode 100644
index 0000000..7a789d4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java
@@ -0,0 +1,123 @@
+/*
+ * 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.wm;
+
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import android.graphics.Rect;
+import android.hardware.display.DisplayManagerGlobal;
+import android.view.Display;
+import android.view.DisplayInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test class for {@link StackWindowController}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.wm.StackWindowControllerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class StackWindowControllerTests extends WindowTestsBase {
+    @Test
+    public void testRemoveContainer() throws Exception {
+        final StackWindowController stackController =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TestTaskWindowContainerController taskController =
+                new TestTaskWindowContainerController(stackController);
+
+        final TaskStack stack = stackController.mContainer;
+        final Task task = taskController.mContainer;
+        assertNotNull(stack);
+        assertNotNull(task);
+        stackController.removeContainer();
+        // Assert that the container was removed.
+        assertNull(stackController.mContainer);
+        assertNull(taskController.mContainer);
+        assertNull(stack.getDisplayContent());
+        assertNull(task.getDisplayContent());
+        assertNull(task.mStack);
+    }
+
+    @Test
+    public void testRemoveContainer_deferRemoval() throws Exception {
+        final StackWindowController stackController =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TestTaskWindowContainerController taskController =
+                new TestTaskWindowContainerController(stackController);
+
+        final TaskStack stack = stackController.mContainer;
+        final TestTask task = (TestTask) taskController.mContainer;
+        // Stack removal is deferred if one of its child is animating.
+        task.setLocalIsAnimating(true);
+
+        stackController.removeContainer();
+        // For the case of deferred removal the stack controller will no longer be connected to the
+        // container, but the task controller will still be connected to the its container until
+        // the stack window container is removed.
+        assertNull(stackController.mContainer);
+        assertNull(stack.getController());
+        assertNotNull(taskController.mContainer);
+        assertNotNull(task.getController());
+
+        stack.removeImmediately();
+        assertNull(taskController.mContainer);
+        assertNull(task.getController());
+    }
+
+    @Test
+    public void testReparent() throws Exception {
+        // Create first stack on primary display.
+        final StackWindowController stack1Controller =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TaskStack stack1 = stack1Controller.mContainer;
+        final TestTaskWindowContainerController taskController =
+                new TestTaskWindowContainerController(stack1Controller);
+        final TestTask task1 = (TestTask) taskController.mContainer;
+        task1.mOnDisplayChangedCalled = false;
+
+        // Create second display and put second stack on it.
+        final Display display = new Display(DisplayManagerGlobal.getInstance(),
+                sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+        final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
+                new WallpaperController(sWm));
+        sWm.mRoot.addChild(dc, 1);
+
+        final StackWindowController stack2Controller =
+                createStackControllerOnDisplay(dc);
+        final TaskStack stack2 = stack2Controller.mContainer;
+
+        // Reparent
+        stack1Controller.reparent(dc.getDisplayId(), new Rect());
+        assertEquals(dc, stack1.getDisplayContent());
+        final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
+        final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
+        assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
+        assertTrue(task1.mOnDisplayChangedCalled);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
index bb9bc9e..462bd68 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -17,17 +17,16 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.After;
 
-import android.hardware.display.DisplayManagerGlobal;
+import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.view.Display;
-import android.view.DisplayInfo;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -46,95 +45,69 @@
 @RunWith(AndroidJUnit4.class)
 public class TaskStackContainersTests extends WindowTestsBase {
 
+    private TaskStack mPinnedStack;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mPinnedStack = new StackWindowController(PINNED_STACK_ID, null,
+                sDisplayContent.getDisplayId(), true /* onTop */, new Rect(), sWm).mContainer;
+
+        // Stack should contain visible app window to be considered visible.
+        final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
+        assertFalse(mPinnedStack.isVisible());
+        final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent);
+        pinnedTask.addChild(pinnedApp, 0 /* addPos */);
+        assertTrue(mPinnedStack.isVisible());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mPinnedStack.removeImmediately();
+    }
+
     @Test
     public void testStackPositionChildAt() throws Exception {
         // Test that always-on-top stack can't be moved to position other than top.
         final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
         final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
-        final TaskStack pinnedStack = addPinnedStack();
 
         final WindowContainer taskStackContainer = stack1.getParent();
 
         final int stack1Pos = taskStackContainer.mChildren.indexOf(stack1);
         final int stack2Pos = taskStackContainer.mChildren.indexOf(stack2);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
         assertGreaterThan(pinnedStackPos, stack2Pos);
         assertGreaterThan(stack2Pos, stack1Pos);
 
-        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, pinnedStack, false);
+        taskStackContainer.positionChildAt(WindowContainer.POSITION_BOTTOM, mPinnedStack, false);
         assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
         assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
 
-        taskStackContainer.positionChildAt(1, pinnedStack, false);
+        taskStackContainer.positionChildAt(1, mPinnedStack, false);
         assertEquals(taskStackContainer.mChildren.get(stack1Pos), stack1);
         assertEquals(taskStackContainer.mChildren.get(stack2Pos), stack2);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
     }
+
     @Test
     public void testStackPositionBelowPinnedStack() throws Exception {
         // Test that no stack can be above pinned stack.
-        final TaskStack pinnedStack = addPinnedStack();
         final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
 
         final WindowContainer taskStackContainer = stack1.getParent();
 
         final int stackPos = taskStackContainer.mChildren.indexOf(stack1);
-        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(pinnedStack);
+        final int pinnedStackPos = taskStackContainer.mChildren.indexOf(mPinnedStack);
         assertGreaterThan(pinnedStackPos, stackPos);
 
         taskStackContainer.positionChildAt(WindowContainer.POSITION_TOP, stack1, false);
         assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
 
         taskStackContainer.positionChildAt(taskStackContainer.mChildren.size() - 1, stack1, false);
         assertEquals(taskStackContainer.mChildren.get(stackPos), stack1);
-        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), pinnedStack);
-    }
-
-    @Test
-    public void testReparentBetweenDisplays() throws Exception {
-        // Create first stack on primary display.
-        final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
-        final TestTaskWindowContainerController taskController =
-                new TestTaskWindowContainerController(stack1.mStackId);
-        final TestTask task1 = (TestTask) taskController.mContainer;
-        task1.mOnDisplayChangedCalled = false;
-
-        // Create second display and put second stack on it.
-        final Display display = new Display(DisplayManagerGlobal.getInstance(),
-                sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
-                DEFAULT_DISPLAY_ADJUSTMENTS);
-        final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
-                new WallpaperController(sWm));
-        sWm.mRoot.addChild(dc, 1);
-        final TaskStack stack2 = createTaskStackOnDisplay(dc);
-
-        // Reparent and check state.DisplayContent.java:2572
-        sWm.moveStackToDisplay(stack1.mStackId, dc.getDisplayId());
-        assertEquals(dc, stack1.getDisplayContent());
-        final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
-        final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
-        assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
-        assertTrue(task1.mOnDisplayChangedCalled);
-    }
-
-    private TaskStack addPinnedStack() {
-        TaskStack pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
-        if (pinnedStack == null) {
-            sDisplayContent.addStackToDisplay(PINNED_STACK_ID, true);
-            pinnedStack = sWm.mStackIdToStack.get(PINNED_STACK_ID);
-        }
-
-        if (!pinnedStack.isVisible()) {
-            // Stack should contain visible app window to be considered visible.
-            final Task pinnedTask = createTaskInStack(pinnedStack, 0 /* userId */);
-            assertFalse(pinnedStack.isVisible());
-            final TestAppWindowToken pinnedApp = new TestAppWindowToken(sDisplayContent);
-            pinnedTask.addChild(pinnedApp, 0 /* addPos */);
-            assertTrue(pinnedStack.isVisible());
-        }
-
-        return pinnedStack;
+        assertEquals(taskStackContainer.mChildren.get(pinnedStackPos), mPinnedStack);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
index 7cd3f64..3ee1da43 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
@@ -18,14 +18,14 @@
 
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
-import org.junit.Test;
-
 import android.hardware.display.DisplayManagerGlobal;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.Display;
 import android.view.DisplayInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -40,7 +40,7 @@
  */
 @SmallTest
 @Presubmit
-@org.junit.runner.RunWith(AndroidJUnit4.class)
+@RunWith(AndroidJUnit4.class)
 public class TaskWindowContainerControllerTests extends WindowTestsBase {
 
     @Test
@@ -57,7 +57,7 @@
     }
 
     @Test
-    public void testRemoveContainer_DeferRemoval() throws Exception {
+    public void testRemoveContainer_deferRemoval() throws Exception {
         final TestTaskWindowContainerController taskController =
                 new TestTaskWindowContainerController();
         final TestAppWindowContainerController appController =
@@ -83,58 +83,66 @@
 
     @Test
     public void testReparent() throws Exception {
-        final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+        final StackWindowController stackController1 =
+                createStackControllerOnDisplay(sDisplayContent);
         final TestTaskWindowContainerController taskController =
-                new TestTaskWindowContainerController(stack1.mStackId);
-        final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
+                new TestTaskWindowContainerController(stackController1);
+        final StackWindowController stackController2 =
+                createStackControllerOnDisplay(sDisplayContent);
         final TestTaskWindowContainerController taskController2 =
-                new TestTaskWindowContainerController(stack2.mStackId);
+                new TestTaskWindowContainerController(stackController2);
 
         boolean gotException = false;
         try {
-            taskController.reparent(stack1.mStackId, 0);
+            taskController.reparent(stackController1, 0);
         } catch (IllegalArgumentException e) {
             gotException = true;
         }
         assertTrue("Should not be able to reparent to the same parent", gotException);
 
+        final StackWindowController stackController3 =
+                createStackControllerOnDisplay(sDisplayContent);
+        stackController3.setContainer(null);
         gotException = false;
         try {
-            taskController.reparent(sNextStackId + 1, 0);
+            taskController.reparent(stackController3, 0);
         } catch (IllegalArgumentException e) {
             gotException = true;
         }
-        assertTrue("Should not be able to reparent to a stackId that doesn't exist", gotException);
+        assertTrue("Should not be able to reparent to a stack that doesn't have a container",
+                gotException);
 
-        taskController.reparent(stack2.mStackId, 0);
-        assertEquals(stack2, taskController.mContainer.getParent());
+        taskController.reparent(stackController2, 0);
+        assertEquals(stackController2.mContainer, taskController.mContainer.getParent());
         assertEquals(0, ((TestTask) taskController.mContainer).positionInParent());
         assertEquals(1, ((TestTask) taskController2.mContainer).positionInParent());
     }
 
     @Test
-    public void testReparentBetweenDisplays() throws Exception {
+    public void testReparent_BetweenDisplays() throws Exception {
         // Create first stack on primary display.
-        final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+        final StackWindowController stack1Controller =
+                createStackControllerOnDisplay(sDisplayContent);
+        final TaskStack stack1 = stack1Controller.mContainer;
         final TestTaskWindowContainerController taskController =
-                new TestTaskWindowContainerController(stack1.mStackId);
+                new TestTaskWindowContainerController(stack1Controller);
         final TestTask task1 = (TestTask) taskController.mContainer;
         task1.mOnDisplayChangedCalled = false;
 
         // Create second display and put second stack on it.
         final Display display = new Display(DisplayManagerGlobal.getInstance(),
-                sDisplayContent.getDisplayId() + 1, new DisplayInfo(),
-                DEFAULT_DISPLAY_ADJUSTMENTS);
+                sDisplayContent.getDisplayId() + 1, new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
         final DisplayContent dc = new DisplayContent(display, sWm, sLayersController,
                 new WallpaperController(sWm));
         sWm.mRoot.addChild(dc, 1);
-        final TaskStack stack2 = createTaskStackOnDisplay(dc);
+        final StackWindowController stack2Controller = createStackControllerOnDisplay(dc);
+        final TaskStack stack2 = stack2Controller.mContainer;
         final TestTaskWindowContainerController taskController2 =
-                new TestTaskWindowContainerController(stack2.mStackId);
+                new TestTaskWindowContainerController(stack2Controller);
         final TestTask task2 = (TestTask) taskController2.mContainer;
 
         // Reparent and check state
-        taskController.reparent(stack2.mStackId, 0);
+        taskController.reparent(stack2Controller, 0);
         assertEquals(stack2, task1.getParent());
         assertEquals(0, task1.positionInParent());
         assertEquals(1, task2.positionInParent());
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 085cfd8..186884b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -77,7 +77,8 @@
         final Rect mInsetBounds = new Rect();
         boolean mFullscreenForTest = true;
         TaskWithBounds(Rect bounds) {
-            super(0, mStubStack, 0, sWm, null, null, false, 0, false, new TaskDescription(), null);
+            super(0, mStubStack, 0, sWm, null, null, false, 0, false, false, new TaskDescription(),
+                    null);
             mBounds = bounds;
         }
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index ae344dd..72157b6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -28,6 +28,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import android.app.ActivityManager.TaskSnapshot;
 import android.content.Context;
 import android.os.IBinder;
 import android.support.test.InstrumentationRegistry;
@@ -95,6 +96,10 @@
         sWm = TestWindowManagerPolicy.getWindowManagerService(context);
         sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
         sLayersController = new WindowLayersController(sWm);
+        sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
+        if (sDisplayContent != null) {
+            sDisplayContent.removeImmediately();
+        }
         sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
                 new WallpaperController(sWm));
         sWm.mRoot.addChild(sDisplayContent, 0);
@@ -173,15 +178,19 @@
 
     /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */
     static TaskStack createTaskStackOnDisplay(DisplayContent dc) {
-        final int stackId = sNextStackId++;
-        dc.addStackToDisplay(stackId, true);
-        return sWm.mStackIdToStack.get(stackId);
+        return createStackControllerOnDisplay(dc).mContainer;
+    }
+
+    static StackWindowController createStackControllerOnDisplay(DisplayContent dc) {
+        final int stackId = ++sNextStackId;
+        return new StackWindowController(stackId, null, dc.getDisplayId(),
+                true /* onTop */, new Rect(), sWm);
     }
 
     /**Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
     static Task createTaskInStack(TaskStack stack, int userId) {
         final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, false, 0,
-                false, new TaskDescription(), null);
+                false, false, new TaskDescription(), null);
         stack.addTask(newTask, POSITION_TOP);
         return newTask;
     }
@@ -235,12 +244,16 @@
 
         boolean mShouldDeferRemoval = false;
         boolean mOnDisplayChangedCalled = false;
+        private boolean mUseLocalIsAnimating = false;
+        private boolean mIsAnimating = false;
 
         TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
                 Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
-                boolean homeTask, TaskWindowContainerController controller) {
+                boolean supportsPictureInPicture, boolean homeTask,
+                TaskWindowContainerController controller) {
             super(taskId, stack, userId, service, bounds, overrideConfig, isOnTopLauncher,
-                    resizeMode, homeTask, new TaskDescription(), controller);
+                    resizeMode, supportsPictureInPicture, homeTask, new TaskDescription(),
+                            controller);
         }
 
         boolean shouldDeferRemoval() {
@@ -256,6 +269,16 @@
             super.onDisplayChanged(dc);
             mOnDisplayChangedCalled = true;
         }
+
+        @Override
+        boolean isAnimating() {
+            return mUseLocalIsAnimating ? mIsAnimating : super.isAnimating();
+        }
+
+        void setLocalIsAnimating(boolean isAnimating) {
+            mUseLocalIsAnimating = true;
+            mIsAnimating = isAnimating;
+        }
     }
 
     /**
@@ -265,22 +288,33 @@
     class TestTaskWindowContainerController extends TaskWindowContainerController {
 
         TestTaskWindowContainerController() {
-            this(createTaskStackOnDisplay(sDisplayContent).mStackId);
+            this(createStackControllerOnDisplay(sDisplayContent));
         }
 
-        TestTaskWindowContainerController(int stackId) {
-            super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
-                    EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/,
+        TestTaskWindowContainerController(StackWindowController stackController) {
+            super(sNextTaskId++, new TaskWindowContainerListener() {
+                        @Override
+                        public void onSnapshotChanged(TaskSnapshot snapshot) {
+
+                        }
+
+                        @Override
+                        public void requestResize(Rect bounds, int resizeMode) {
+
+                        }
+                    }, stackController, 0 /* userId */, null /* bounds */,
+                    EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE,
+                    false /* supportsPictureInPicture */, false /* homeTask*/,
                     false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */,
-                    new TaskDescription());
+                    new TaskDescription(), sWm);
         }
 
         @Override
         TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds,
-                Configuration overrideConfig, int resizeMode, boolean homeTask,
-                boolean isOnTopLauncher, TaskDescription taskDescription) {
+                Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+                boolean homeTask, boolean isOnTopLauncher, TaskDescription taskDescription) {
             return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig,
-                    isOnTopLauncher, resizeMode, homeTask, this);
+                    isOnTopLauncher, resizeMode, supportsPictureInPicture, homeTask, this);
         }
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 6dfb48b..7a69803 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -106,7 +106,7 @@
     private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
     private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
 
-    private static final boolean ENABLE_KERNEL_UPDATES = false;
+    private static final boolean ENABLE_KERNEL_UPDATES = true;
     private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
 
     long mAppIdleScreenThresholdMillis;
diff --git a/services/usb/Android.mk b/services/usb/Android.mk
index feabf0a..f560e71 100644
--- a/services/usb/Android.mk
+++ b/services/usb/Android.mk
@@ -8,5 +8,7 @@
       $(call all-java-files-under,java)
 
 LOCAL_JAVA_LIBRARIES := services.core
+LOCAL_STATIC_JAVA_LIBRARIES := android.hardware.usb@1.0-java-static \
+android.hidl.manager@1.0-java-static
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 4aff3d54..4b8e4c8 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -16,36 +16,40 @@
 
 package com.android.server.usb;
 
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.FgThread;
-
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.V1_0.IUsb;
+import android.hardware.usb.V1_0.IUsbCallback;
+import android.hardware.usb.V1_0.PortRole;
+import android.hardware.usb.V1_0.PortRoleType;
+import android.hardware.usb.V1_0.PortStatus;
+import android.hardware.usb.V1_0.Status;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.HwBinder;
 import android.os.Message;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UEventObserver;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
 import android.os.UserHandle;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
 
-import libcore.io.IoUtils;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
 
 /**
  * Allows trusted components to control the properties of physical USB ports
- * via the "/sys/class/dual_role_usb" kernel interface.
+ * via the IUsb.hal.
  * <p>
  * Note: This interface may not be supported on all chipsets since the USB drivers
  * must be changed to publish this information through the module.  At the moment
@@ -60,43 +64,6 @@
 
     private static final int MSG_UPDATE_PORTS = 1;
 
-    // UEvent path to watch.
-    private static final String UEVENT_FILTER = "SUBSYSTEM=dual_role_usb";
-
-    // SysFS directory that contains USB ports as subdirectories.
-    private static final String SYSFS_CLASS = "/sys/class/dual_role_usb";
-
-    // SysFS file that contains a USB port's supported modes.  (read-only)
-    // Contents: "", "ufp", "dfp", or "ufp dfp".
-    private static final String SYSFS_PORT_SUPPORTED_MODES = "supported_modes";
-
-    // SysFS file that contains a USB port's current mode.  (read-write if configurable)
-    // Contents: "", "ufp", or "dfp".
-    private static final String SYSFS_PORT_MODE = "mode";
-
-    // SysFS file that contains a USB port's current power role.  (read-write if configurable)
-    // Contents: "", "source", or "sink".
-    private static final String SYSFS_PORT_POWER_ROLE = "power_role";
-
-    // SysFS file that contains a USB port's current data role.  (read-write if configurable)
-    // Contents: "", "host", or "device".
-    private static final String SYSFS_PORT_DATA_ROLE = "data_role";
-
-    // Port modes: upstream facing port or downstream facing port.
-    private static final String PORT_MODE_DFP = "dfp";
-    private static final String PORT_MODE_UFP = "ufp";
-
-    // Port power roles: source or sink.
-    private static final String PORT_POWER_ROLE_SOURCE = "source";
-    private static final String PORT_POWER_ROLE_SINK = "sink";
-
-    // Port data roles: host or device.
-    private static final String PORT_DATA_ROLE_HOST = "host";
-    private static final String PORT_DATA_ROLE_DEVICE = "device";
-
-    private static final String USB_TYPEC_PROP_PREFIX = "sys.usb.typec.";
-    private static final String USB_TYPEC_STATE = "sys.usb.typec.state";
-
     // All non-trivial role combinations.
     private static final int COMBO_SOURCE_HOST =
             UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST);
@@ -110,8 +77,29 @@
     // The system context.
     private final Context mContext;
 
-    // True if we have kernel support.
-    private final boolean mHaveKernelSupport;
+    // Proxy object for the usb hal daemon.
+    // @GuardedBy("mLock")
+    private IUsb mProxy = null;
+
+    // Callback when the UsbPort status is changed by the kernel.
+    // Mostly due a command sent by the remote Usb device.
+    private HALCallback mHALCallback = new HALCallback(null, this);
+
+    // Notification object used to listen to the start of the usb daemon.
+    private final ServiceNotification mServiceNotification = new ServiceNotification();
+
+    // Cookie sent for usb hal death notification.
+    private static final int USB_HAL_DEATH_COOKIE = 1000;
+
+    // Usb hal service name.
+    private static String sSERVICENAME = "usb_hal";
+
+    // Used as the key while sending the bundle to Main thread.
+    private static final String PORT_INFO = "port_info";
+
+    // This is monitored to prevent updating the protInfo before the system
+    // is ready.
+    private boolean mSystemReady;
 
     // Mutex for all mutable shared state.
     private final Object mLock = new Object();
@@ -123,17 +111,37 @@
     private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>();
 
     // List of all simulated ports, indexed by id.
-    private final ArrayMap<String, SimulatedPortInfo> mSimulatedPorts =
-            new ArrayMap<String, SimulatedPortInfo>();
+    private final ArrayMap<String, RawPortInfo> mSimulatedPorts =
+            new ArrayMap<String, RawPortInfo>();
 
     public UsbPortManager(Context context) {
         mContext = context;
-        mHaveKernelSupport = new File(SYSFS_CLASS).exists();
+        try {
+            boolean ret = IServiceManager.getService("manager")
+                    .registerForNotifications("android.hardware.usb@1.0::IUsb",
+                    "", mServiceNotification);
+            if (!ret) {
+                logAndPrint(Log.ERROR, null, "Failed to register service start notification");
+            }
+        } catch (RemoteException e) {
+            logAndPrint(Log.ERROR, null, "Failed to register service start notification");
+            Thread.dumpStack();
+            return;
+        }
+        connectToProxy(null);
     }
 
     public void systemReady() {
-        mUEventObserver.startObserving(UEVENT_FILTER);
-        scheduleUpdatePorts();
+        if (mProxy != null) {
+            try {
+                mProxy.queryPortStatus();
+            } catch (RemoteException e) {
+                logAndPrint(Log.ERROR, null,
+                        "ServiceStart: Failed to query port status");
+                Thread.dumpStack();
+            }
+        }
+        mSystemReady = true;
     }
 
     public UsbPort[] getPorts() {
@@ -223,20 +231,14 @@
                     + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
                     + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
 
-            SimulatedPortInfo sim = mSimulatedPorts.get(portId);
+            RawPortInfo sim = mSimulatedPorts.get(portId);
             if (sim != null) {
                 // Change simulated state.
                 sim.mCurrentMode = newMode;
                 sim.mCurrentPowerRole = newPowerRole;
                 sim.mCurrentDataRole = newDataRole;
-            } else if (mHaveKernelSupport) {
-                // Change actual state.
-                final File portDir = new File(SYSFS_CLASS, portId);
-                if (!portDir.exists()) {
-                    logAndPrint(Log.ERROR, pw, "USB port not found: portId=" + portId);
-                    return;
-                }
-
+                updatePortsLocked(pw, null);
+            } else if (mProxy != null) {
                 if (currentMode != newMode) {
                     // Changing the mode will have the side-effect of also changing
                     // the power and data roles but it might take some time to apply
@@ -244,38 +246,54 @@
                     // hardware, we have no way of knowing whether it will work apriori
                     // which is why we would prefer to set the power and data roles
                     // directly instead.
-                    if (!writeFile(portDir, SYSFS_PORT_MODE,
-                            newMode == UsbPort.MODE_DFP ? PORT_MODE_DFP : PORT_MODE_UFP)) {
-                        logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: "
+
+                    logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: "
                                 + "portId=" + portId
                                 + ", newMode=" + UsbPort.modeToString(newMode));
+                    PortRole newRole = new PortRole();
+                    newRole.type = PortRoleType.MODE;
+                    newRole.role = newMode;
+                    try {
+                        mProxy.switchRole(portId, newRole);
+                    } catch (RemoteException e) {
+                        logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: "
+                                + "portId=" + portId
+                                + ", newMode=" + UsbPort.modeToString(newRole.role));
+                        Thread.dumpStack();
                         return;
                     }
                 } else {
                     // Change power and data role independently as needed.
                     if (currentPowerRole != newPowerRole) {
-                        if (!writeFile(portDir, SYSFS_PORT_POWER_ROLE,
-                                newPowerRole == UsbPort.POWER_ROLE_SOURCE
-                                ? PORT_POWER_ROLE_SOURCE : PORT_POWER_ROLE_SINK)) {
+                        PortRole newRole = new PortRole();
+                        newRole.type = PortRoleType.POWER_ROLE;
+                        newRole.role = newPowerRole;
+                        try {
+                            mProxy.switchRole(portId, newRole);
+                        } catch (RemoteException e) {
                             logAndPrint(Log.ERROR, pw, "Failed to set the USB port power role: "
                                     + "portId=" + portId
-                                    + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole));
+                                    + ", newPowerRole=" + UsbPort.powerRoleToString(newRole.role));
+                            Thread.dumpStack();
                             return;
                         }
                     }
                     if (currentDataRole != newDataRole) {
-                        if (!writeFile(portDir, SYSFS_PORT_DATA_ROLE,
-                                newDataRole == UsbPort.DATA_ROLE_HOST
-                                ? PORT_DATA_ROLE_HOST : PORT_DATA_ROLE_DEVICE)) {
+                        PortRole newRole = new PortRole();
+                        newRole.type = PortRoleType.DATA_ROLE;
+                        newRole.role = newDataRole;
+                        try {
+                            mProxy.switchRole(portId, newRole);
+                        } catch (RemoteException e) {
                             logAndPrint(Log.ERROR, pw, "Failed to set the USB port data role: "
                                     + "portId=" + portId
-                                    + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
+                                    + ", newDataRole=" + UsbPort.dataRoleToString(newRole.role));
+                            Thread.dumpStack();
                             return;
                         }
                     }
                 }
             }
-            updatePortsLocked(pw);
         }
     }
 
@@ -289,8 +307,8 @@
             pw.println("Adding simulated port: portId=" + portId
                     + ", supportedModes=" + UsbPort.modeToString(supportedModes));
             mSimulatedPorts.put(portId,
-                    new SimulatedPortInfo(portId, supportedModes));
-            updatePortsLocked(pw);
+                    new RawPortInfo(portId, supportedModes));
+            updatePortsLocked(pw, null);
         }
     }
 
@@ -298,7 +316,7 @@
             int powerRole, boolean canChangePowerRole,
             int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) {
         synchronized (mLock) {
-            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
+            final RawPortInfo portInfo = mSimulatedPorts.get(portId);
             if (portInfo == null) {
                 pw.println("Cannot connect simulated port which does not exist.");
                 return;
@@ -328,13 +346,13 @@
             portInfo.mCanChangePowerRole = canChangePowerRole;
             portInfo.mCurrentDataRole = dataRole;
             portInfo.mCanChangeDataRole = canChangeDataRole;
-            updatePortsLocked(pw);
+            updatePortsLocked(pw, null);
         }
     }
 
     public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
         synchronized (mLock) {
-            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
+            final RawPortInfo portInfo = mSimulatedPorts.get(portId);
             if (portInfo == null) {
                 pw.println("Cannot disconnect simulated port which does not exist.");
                 return;
@@ -347,7 +365,7 @@
             portInfo.mCanChangePowerRole = false;
             portInfo.mCurrentDataRole = 0;
             portInfo.mCanChangeDataRole = false;
-            updatePortsLocked(pw);
+            updatePortsLocked(pw, null);
         }
     }
 
@@ -361,7 +379,7 @@
 
             pw.println("Disconnecting simulated port: portId=" + portId);
             mSimulatedPorts.removeAt(index);
-            updatePortsLocked(pw);
+            updatePortsLocked(pw, null);
         }
     }
 
@@ -370,7 +388,7 @@
             pw.println("Removing all simulated ports and ending simulation.");
             if (!mSimulatedPorts.isEmpty()) {
                 mSimulatedPorts.clear();
-                updatePortsLocked(pw);
+                updatePortsLocked(pw, null);
             }
         }
     }
@@ -393,9 +411,108 @@
         }
     }
 
-    private void updatePortsLocked(IndentingPrintWriter pw) {
-        // Assume all ports are gone unless informed otherwise.
-        // Kind of pessimistic but simple.
+    private static class HALCallback extends IUsbCallback.Stub {
+        public IndentingPrintWriter pw;
+        public UsbPortManager portManager;
+
+        HALCallback() {
+            super();
+        }
+
+        HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) {
+            this.pw = pw;
+            this.portManager = portManager;
+        }
+
+        public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) {
+            if (!portManager.mSystemReady) return;
+
+            if (retval != Status.SUCCESS) {
+                logAndPrint(Log.ERROR, pw, "port status enquiry failed");
+                return;
+            }
+
+            ArrayList<RawPortInfo> newPortInfo = new ArrayList<RawPortInfo>();
+
+            for (PortStatus current : currentPortStatus) {
+                RawPortInfo temp = new RawPortInfo(current.portName,
+                        current.supportedModes, current.currentMode,
+                        current.canChangeMode, current.currentPowerRole,
+                        current.canChangePowerRole,
+                        current.currentDataRole, current.canChangeDataRole);
+                newPortInfo.add(temp);
+                logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName);
+            }
+
+            Message message = portManager.mHandler.obtainMessage();
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
+            message.what = MSG_UPDATE_PORTS;
+            message.setData(bundle);
+            portManager.mHandler.sendMessage(message);
+            return;
+        }
+
+        public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) {
+            if (retval == Status.SUCCESS) {
+                logAndPrint(Log.INFO, pw, portName + "role switch successful");
+            } else {
+                logAndPrint(Log.ERROR, pw, portName + "role switch failed");
+            }
+        }
+    };
+
+    final class DeathRecipient implements HwBinder.DeathRecipient {
+        public IndentingPrintWriter pw;
+
+        DeathRecipient(IndentingPrintWriter pw) {
+            this.pw = pw;
+        }
+
+        @Override
+        public void serviceDied(long cookie) {
+            if (cookie == USB_HAL_DEATH_COOKIE) {
+                logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie);
+                synchronized (mLock) {
+                    mProxy = null;
+                }
+            }
+        }
+    }
+
+    final class ServiceNotification extends IServiceNotification.Stub {
+        @Override
+        public void onRegistration(String fqName, String name, boolean preexisting) {
+            logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name);
+            connectToProxy(null);
+        }
+    }
+
+    private void connectToProxy(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            if (mProxy != null) return;
+
+            try {
+                mProxy = IUsb.getService(sSERVICENAME);
+                mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
+                mProxy.setCallback(mHALCallback);
+                mProxy.queryPortStatus();
+            } catch (NoSuchElementException e) {
+                logAndPrint(Log.ERROR, pw, sSERVICENAME + "not found."
+                        + " Did the service failed to start ?");
+                Thread.dumpStack();
+            } catch (RemoteException e) {
+                logAndPrint(Log.ERROR, pw, sSERVICENAME + "connectToProxy: Service not responding");
+                Thread.dumpStack();
+            }
+        }
+    }
+
+    /**
+     * Simulated ports directly add the new roles to mSimulatedPorts before calling.
+     * USB hal callback populates and sends the newPortInfo.
+     */
+    private void updatePortsLocked(IndentingPrintWriter pw, ArrayList<RawPortInfo> newPortInfo) {
         for (int i = mPorts.size(); i-- > 0; ) {
             mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED;
         }
@@ -404,34 +521,18 @@
         if (!mSimulatedPorts.isEmpty()) {
             final int count = mSimulatedPorts.size();
             for (int i = 0; i < count; i++) {
-                final SimulatedPortInfo portInfo = mSimulatedPorts.valueAt(i);
+                final RawPortInfo portInfo = mSimulatedPorts.valueAt(i);
                 addOrUpdatePortLocked(portInfo.mPortId, portInfo.mSupportedModes,
                         portInfo.mCurrentMode, portInfo.mCanChangeMode,
                         portInfo.mCurrentPowerRole, portInfo.mCanChangePowerRole,
                         portInfo.mCurrentDataRole, portInfo.mCanChangeDataRole, pw);
             }
-        } else if (mHaveKernelSupport) {
-            final File[] portDirs = new File(SYSFS_CLASS).listFiles();
-            if (portDirs != null) {
-                for (File portDir : portDirs) {
-                    if (!portDir.isDirectory()) {
-                        continue;
-                    }
-
-                    // Parse the sysfs file contents.
-                    final String portId = portDir.getName();
-                    final int supportedModes = readSupportedModes(portDir);
-                    final int currentMode = readCurrentMode(portDir);
-                    final boolean canChangeMode = canChangeMode(portDir);
-                    final int currentPowerRole = readCurrentPowerRole(portDir);
-                    final boolean canChangePowerRole = canChangePowerRole(portDir);
-                    final int currentDataRole = readCurrentDataRole(portDir);
-                    final boolean canChangeDataRole = canChangeDataRole(portDir);
-                    addOrUpdatePortLocked(portId, supportedModes,
-                            currentMode, canChangeMode,
-                            currentPowerRole, canChangePowerRole,
-                            currentDataRole, canChangeDataRole, pw);
-                 }
+        } else {
+            for (RawPortInfo currentPortInfo : newPortInfo) {
+                addOrUpdatePortLocked(currentPortInfo.mPortId, currentPortInfo.mSupportedModes,
+                        currentPortInfo.mCurrentMode, currentPortInfo.mCanChangeMode,
+                        currentPortInfo.mCurrentPowerRole, currentPortInfo.mCanChangePowerRole,
+                        currentPortInfo.mCurrentDataRole, currentPortInfo.mCanChangeDataRole, pw);
             }
         }
 
@@ -457,20 +558,21 @@
         }
     }
 
+
     // Must only be called by updatePortsLocked.
     private void addOrUpdatePortLocked(String portId, int supportedModes,
-            int currentMode, boolean canChangeMode,
-            int currentPowerRole, boolean canChangePowerRole,
-            int currentDataRole, boolean canChangeDataRole,
-            IndentingPrintWriter pw) {
+                                       int currentMode, boolean canChangeMode,
+                                       int currentPowerRole, boolean canChangePowerRole,
+                                       int currentDataRole, boolean canChangeDataRole,
+                                       IndentingPrintWriter pw) {
         // Only allow mode switch capability for dual role ports.
         // Validate that the current mode matches the supported modes we expect.
         if (supportedModes != UsbPort.MODE_DUAL) {
             canChangeMode = false;
             if (currentMode != 0 && currentMode != supportedModes) {
                 logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB "
-                        + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
-                        + ", currentMode=" + UsbPort.modeToString(currentMode));
+                            + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
+                            + ", currentMode=" + UsbPort.modeToString(currentMode));
                 currentMode = 0;
             }
         }
@@ -485,8 +587,8 @@
                 // Can change both power and data role independently.
                 // Assume all combinations are possible.
                 supportedRoleCombinations |=
-                        COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
-                                | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
+                    COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
+                    | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
             } else if (canChangePowerRole) {
                 // Can only change power role.
                 // Assume data role must remain at its current value.
@@ -514,24 +616,24 @@
         if (portInfo == null) {
             portInfo = new PortInfo(portId, supportedModes);
             portInfo.setStatus(currentMode, canChangeMode,
-                    currentPowerRole, canChangePowerRole,
-                    currentDataRole, canChangeDataRole,
-                    supportedRoleCombinations);
+                               currentPowerRole, canChangePowerRole,
+                               currentDataRole, canChangeDataRole,
+                               supportedRoleCombinations);
             mPorts.put(portId, portInfo);
         } else {
             // Sanity check that ports aren't changing definition out from under us.
             if (supportedModes != portInfo.mUsbPort.getSupportedModes()) {
                 logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from "
-                        + "USB port driver (should be immutable): "
-                        + "previous=" + UsbPort.modeToString(
-                                portInfo.mUsbPort.getSupportedModes())
-                        + ", current=" + UsbPort.modeToString(supportedModes));
+                            + "USB port driver (should be immutable): "
+                            + "previous=" + UsbPort.modeToString(
+                            portInfo.mUsbPort.getSupportedModes())
+                            + ", current=" + UsbPort.modeToString(supportedModes));
             }
 
             if (portInfo.setStatus(currentMode, canChangeMode,
-                    currentPowerRole, canChangePowerRole,
-                    currentDataRole, canChangeDataRole,
-                    supportedRoleCombinations)) {
+                                   currentPowerRole, canChangePowerRole,
+                                   currentDataRole, canChangeDataRole,
+                                   supportedRoleCombinations)) {
                 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
             } else {
                 portInfo.mDisposition = PortInfo.DISPOSITION_READY;
@@ -572,120 +674,6 @@
         });
     }
 
-    private void scheduleUpdatePorts() {
-        if (!mHandler.hasMessages(MSG_UPDATE_PORTS)) {
-            mHandler.sendEmptyMessage(MSG_UPDATE_PORTS);
-        }
-    }
-
-    private static int readSupportedModes(File portDir) {
-        int modes = 0;
-        final String contents = readFile(portDir, SYSFS_PORT_SUPPORTED_MODES);
-        if (contents != null) {
-            if (contents.contains(PORT_MODE_DFP)) {
-                modes |= UsbPort.MODE_DFP;
-            }
-            if (contents.contains(PORT_MODE_UFP)) {
-                modes |= UsbPort.MODE_UFP;
-            }
-        }
-        return modes;
-    }
-
-    private static int readCurrentMode(File portDir) {
-        final String contents = readFile(portDir, SYSFS_PORT_MODE);
-        if (contents != null) {
-            if (contents.equals(PORT_MODE_DFP)) {
-                return UsbPort.MODE_DFP;
-            }
-            if (contents.equals(PORT_MODE_UFP)) {
-                return UsbPort.MODE_UFP;
-            }
-        }
-        return 0;
-    }
-
-    private static int readCurrentPowerRole(File portDir) {
-        final String contents = readFile(portDir, SYSFS_PORT_POWER_ROLE);
-        if (contents != null) {
-            if (contents.equals(PORT_POWER_ROLE_SOURCE)) {
-                return UsbPort.POWER_ROLE_SOURCE;
-            }
-            if (contents.equals(PORT_POWER_ROLE_SINK)) {
-                return UsbPort.POWER_ROLE_SINK;
-            }
-        }
-        return 0;
-    }
-
-    private static int readCurrentDataRole(File portDir) {
-        final String contents = readFile(portDir, SYSFS_PORT_DATA_ROLE);
-        if (contents != null) {
-            if (contents.equals(PORT_DATA_ROLE_HOST)) {
-                return UsbPort.DATA_ROLE_HOST;
-            }
-            if (contents.equals(PORT_DATA_ROLE_DEVICE)) {
-                return UsbPort.DATA_ROLE_DEVICE;
-            }
-        }
-        return 0;
-    }
-
-    private static boolean fileIsRootWritable(String path) {
-        try {
-            // If the file is user writable, then it is root writable.
-            return (Os.stat(path).st_mode & OsConstants.S_IWUSR) != 0;
-        } catch (ErrnoException e) {
-            return false;
-        }
-    }
-
-    private static boolean canChangeMode(File portDir) {
-        return fileIsRootWritable(new File(portDir, SYSFS_PORT_MODE).getPath());
-    }
-
-    private static boolean canChangePowerRole(File portDir) {
-        return fileIsRootWritable(new File(portDir, SYSFS_PORT_POWER_ROLE).getPath());
-    }
-
-    private static boolean canChangeDataRole(File portDir) {
-        return fileIsRootWritable(new File(portDir, SYSFS_PORT_DATA_ROLE).getPath());
-    }
-
-    private static String readFile(File dir, String filename) {
-        final File file = new File(dir, filename);
-        try {
-            return IoUtils.readFileAsString(file.getAbsolutePath()).trim();
-        } catch (IOException ex) {
-            return null;
-        }
-    }
-
-    private static boolean waitForState(String property, String state) {
-        // wait for the transition to complete.
-        // give up after 5 seconds.
-        // 5 seconds is probably too long, but we have seen hardware that takes
-        // over 3 seconds to change states.
-        String value = null;
-        for (int i = 0; i < 100; i++) {
-            // State transition is done when property is set to the new configuration
-            value = SystemProperties.get(property);
-            if (state.equals(value)) return true;
-            SystemClock.sleep(50);
-        }
-        Slog.e(TAG, "waitForState(" + state + ") for " + property + " FAILED: got " + value);
-        return false;
-    }
-
-    private static String propertyFromFilename(String filename) {
-        return USB_TYPEC_PROP_PREFIX + filename;
-    }
-
-    private static boolean writeFile(File dir, String filename, String contents) {
-        SystemProperties.set(propertyFromFilename(filename), contents);
-        return waitForState(USB_TYPEC_STATE, contents);
-    }
-
     private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
         Slog.println(priority, TAG, msg);
         if (pw != null) {
@@ -698,8 +686,10 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_UPDATE_PORTS: {
+                    Bundle b = msg.getData();
+                    ArrayList<RawPortInfo> PortInfo = b.getParcelableArrayList(PORT_INFO);
                     synchronized (mLock) {
-                        updatePortsLocked(null);
+                        updatePortsLocked(null, PortInfo);
                     }
                     break;
                 }
@@ -707,13 +697,6 @@
         }
     };
 
-    private final UEventObserver mUEventObserver = new UEventObserver() {
-        @Override
-        public void onUEvent(UEvent event) {
-            scheduleUpdatePorts();
-        }
-    };
-
     /**
      * Describes a USB port.
      */
@@ -764,10 +747,10 @@
     }
 
     /**
-     * Describes a simulated USB port.
-     * Roughly mirrors the information we would ordinarily get from the kernel.
+     * Used for storing the raw data from the kernel
+     * Values of the member variables mocked directly incase of emulation.
      */
-    private static final class SimulatedPortInfo {
+    private static final class RawPortInfo implements Parcelable {
         public final String mPortId;
         public final int mSupportedModes;
         public int mCurrentMode;
@@ -777,9 +760,63 @@
         public int mCurrentDataRole;
         public boolean mCanChangeDataRole;
 
-        public SimulatedPortInfo(String portId, int supportedModes) {
+        RawPortInfo(String portId, int supportedModes) {
             mPortId = portId;
             mSupportedModes = supportedModes;
         }
+
+        RawPortInfo(String portId, int supportedModes,
+                                 int currentMode, boolean canChangeMode,
+                                 int currentPowerRole, boolean canChangePowerRole,
+                                 int currentDataRole, boolean canChangeDataRole) {
+            mPortId = portId;
+            mSupportedModes = supportedModes;
+            mCurrentMode = currentMode;
+            mCanChangeMode = canChangeMode;
+            mCurrentPowerRole = currentPowerRole;
+            mCanChangePowerRole = canChangePowerRole;
+            mCurrentDataRole = currentDataRole;
+            mCanChangeDataRole = canChangeDataRole;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mPortId);
+            dest.writeInt(mSupportedModes);
+            dest.writeInt(mCurrentMode);
+            dest.writeByte((byte) (mCanChangeMode ? 1 : 0));
+            dest.writeInt(mCurrentPowerRole);
+            dest.writeByte((byte) (mCanChangePowerRole ? 1 : 0));
+            dest.writeInt(mCurrentDataRole);
+            dest.writeByte((byte) (mCanChangeDataRole ? 1 : 0));
+        }
+
+        public static final Parcelable.Creator<RawPortInfo> CREATOR =
+                new Parcelable.Creator<RawPortInfo>() {
+            @Override
+            public RawPortInfo createFromParcel(Parcel in) {
+                String id = in.readString();
+                int supportedModes = in.readInt();
+                int currentMode = in.readInt();
+                boolean canChangeMode = in.readByte() != 0;
+                int currentPowerRole = in.readInt();
+                boolean canChangePowerRole = in.readByte() != 0;
+                int currentDataRole = in.readInt();
+                boolean canChangeDataRole = in.readByte() != 0;
+                return new RawPortInfo(id, supportedModes, currentMode, canChangeMode,
+                                   currentPowerRole, canChangePowerRole,
+                                   currentDataRole, canChangeDataRole);
+            }
+
+            @Override
+            public RawPortInfo[] newArray(int size) {
+                return new RawPortInfo[size];
+            }
+        };
     }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a853d5c..cd62cd5 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -259,6 +259,12 @@
             KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
 
     /**
+     * Override the device's configuration for the ImsService to use for this SIM card.
+     */
+    public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
+            "config_ims_package_override_string";
+
+    /**
      * Override the platform's notion of a network operator being considered roaming.
      * Value is string array of SIDs to be considered roaming for 3GPP2 RATs.
      */
@@ -1396,6 +1402,7 @@
                 });
         sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
+        sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
         sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a3f7c18..33e9b16 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -373,13 +373,46 @@
             "android.telephony.action.EMERGENCY_ASSISTANCE";
 
     /**
+     * A boolean meta-data value indicating whether the voicemail settings should be hidden in the
+     * call settings page launched by
+     * {@link android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS}.
+     * Dialer implementations (see {@link android.telecom.TelecomManager#getDefaultDialerPackage()})
+     * which would also like to manage voicemail settings should set this meta-data to {@code true}
+     * in the manifest registration of their application.
+     *
+     * @see android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS
+     * @see #ACTION_CONFIGURE_VOICEMAIL
+     * @see #EXTRA_HIDE_PUBLIC_SETTINGS
+     */
+    public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU =
+            "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
+
+    /**
      * Open the voicemail settings activity to make changes to voicemail configuration.
+     *
+     * <p>
+     * The {@link #EXTRA_HIDE_PUBLIC_SETTINGS} hides settings the dialer will modify through public
+     * API if set.
+     *
+     * @see #EXTRA_HIDE_PUBLIC_SETTINGS
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CONFIGURE_VOICEMAIL =
             "android.telephony.action.CONFIGURE_VOICEMAIL";
 
     /**
+     * The boolean value indicating whether the voicemail settings activity launched by {@link
+     * #ACTION_CONFIGURE_VOICEMAIL} should hide settings accessible through public API. This is
+     * used by dialer implementations which provides their own voicemail settings UI, but still
+     * needs to expose device specific voicemail settings to the user.
+     *
+     * @see #ACTION_CONFIGURE_VOICEMAIL
+     * @see #METADATA_HIDE_VOICEMAIL_SETTINGS_MENU
+     */
+    public static final String EXTRA_HIDE_PUBLIC_SETTINGS =
+            "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
+
+    /**
      * @hide
      */
     public static final boolean EMERGENCY_ASSISTANCE_ENABLED = true;
@@ -4791,6 +4824,20 @@
         }
     }
 
+   /*
+    * @return true, if the device is currently on a technology (e.g. UMTS or LTE) which can support
+    * voice and data simultaneously. This can change based on location or network condition.
+    */
+    public boolean isConcurrentVoiceAndDataAllowed() {
+        try {
+            ITelephony telephony = getITelephony();
+            return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(mSubId));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e);
+        }
+        return false;
+    }
+
     /** @hide */
     @SystemApi
     public boolean handlePinMmi(String dialString) {
diff --git a/telephony/java/android/telephony/ims/ImsServiceBase.java b/telephony/java/android/telephony/ims/ImsServiceBase.java
new file mode 100644
index 0000000..0b50eca
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsServiceBase.java
@@ -0,0 +1,35 @@
+/*
+ * 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 android.telephony.ims;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Base ImsService Implementation, which is used by the ImsResolver to bind.
+ * @hide
+ */
+@SystemApi
+public class ImsServiceBase extends Service {
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
new file mode 100644
index 0000000..0509d60
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.telephony.ims.feature;
+
+/**
+ * Base class for all IMS features that are supported by the framework.
+ * @hide
+ */
+public class ImsFeature {
+
+    // Invalid feature value
+    public static final int INVALID = -1;
+    // ImsFeatures that are defined in the Manifests
+    public static final int EMERGENCY_MMTEL = 0;
+    public static final int MMTEL = 1;
+    public static final int RCS = 2;
+    // Total number of features defined
+    public static final int MAX = 3;
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
new file mode 100644
index 0000000..fa86a43
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.ims.internal;
+
+/**
+ * {@hide}
+ */
+interface IImsServiceController {
+    void createImsFeature(int slotId, int feature);
+    void removeImsFeature(int slotId, int feature);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
new file mode 100644
index 0000000..0a36b6b
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.ims.internal;
+
+/**
+ * {@hide}
+ */
+oneway interface IImsServiceFeatureListener {
+    void imsFeatureCreated(int slotId, int feature);
+    void imsFeatureRemoved(int slotId, int feature);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c0d6768ae..9a9a092 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -466,6 +466,12 @@
      */
     int getVoiceMessageCountForSubscriber(int subId);
 
+    /**
+      * Returns true if current state supports both voice and data
+      * simultaneously. This can change based on location or network condition.
+      */
+    boolean isConcurrentVoiceAndDataAllowed(int subId);
+
     oneway void setVisualVoicemailEnabled(String callingPackage,
             in PhoneAccountHandle accountHandle, boolean enabled);
 
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index b6e701e..9ffd92d 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -692,6 +692,13 @@
         return null;
     }
 
+    /** @hide */
+    @Override
+    public Context createContextForSplit(String splitName)
+            throws PackageManager.NameNotFoundException {
+        throw new UnsupportedOperationException();
+    }
+
     /** {@hide} */
     @Override
     public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index da27ea9..56aad23 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -424,6 +424,17 @@
                     mNM.notify("secret", 7012, n);
                 }
             },
+            new Test("1 minute timeout") {
+                public void run()
+                {
+                    Notification n = new Notification.Builder(NotificationTestList.this)
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle("timeout in a minute")
+                            .setTimeout(System.currentTimeMillis() + (1000 * 60))
+                            .build();
+                    mNM.notify("timeout_min", 7013, n);
+                }
+            },
         new Test("Off") {
             public void run() {
                 PowerManager pm = (PowerManager)NotificationTestList.this.getSystemService(Context.POWER_SERVICE);
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index d0026a2..0aca45e 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -267,8 +267,8 @@
     uint16_t minSdk = 0;
     if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
                 == ResTable_config::UI_MODE_TYPE_VR_HEADSET
-            || config->colorimetry & ResTable_config::MASK_WIDE_COLOR_GAMUT
-            || config->colorimetry & ResTable_config::MASK_HDR) {
+            || config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT
+            || config->colorMode & ResTable_config::MASK_HDR) {
         minSdk = SDK_O;
     } else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
         minSdk = SDK_MNC;
@@ -451,18 +451,18 @@
 
 bool parseWideColorGamut(const char* name, ResTable_config* out) {
     if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->colorimetry =
-                (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
+        if (out) out->colorMode =
+                (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
                 | ResTable_config::WIDE_COLOR_GAMUT_ANY;
         return true;
     } else if (strcmp(name, "widecg") == 0) {
-        if (out) out->colorimetry =
-                (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
+        if (out) out->colorMode =
+                (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
                 | ResTable_config::WIDE_COLOR_GAMUT_YES;
         return true;
     } else if (strcmp(name, "nowidecg") == 0) {
-        if (out) out->colorimetry =
-                (out->colorimetry&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
+        if (out) out->colorMode =
+                (out->colorMode&~ResTable_config::MASK_WIDE_COLOR_GAMUT)
                 | ResTable_config::WIDE_COLOR_GAMUT_NO;
         return true;
     }
@@ -471,18 +471,18 @@
 
 bool parseHdr(const char* name, ResTable_config* out) {
     if (strcmp(name, kWildcardName) == 0) {
-        if (out) out->colorimetry =
-                (out->colorimetry&~ResTable_config::MASK_HDR)
+        if (out) out->colorMode =
+                (out->colorMode&~ResTable_config::MASK_HDR)
                 | ResTable_config::HDR_ANY;
         return true;
     } else if (strcmp(name, "highdr") == 0) {
-        if (out) out->colorimetry =
-                (out->colorimetry&~ResTable_config::MASK_HDR)
+        if (out) out->colorMode =
+                (out->colorMode&~ResTable_config::MASK_HDR)
                 | ResTable_config::HDR_YES;
         return true;
     } else if (strcmp(name, "lowdr") == 0) {
-        if (out) out->colorimetry =
-                (out->colorimetry&~ResTable_config::MASK_HDR)
+        if (out) out->colorMode =
+                (out->colorMode&~ResTable_config::MASK_HDR)
                 | ResTable_config::HDR_NO;
         return true;
     }
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 653c1b4..a93ee2e 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -55,6 +55,7 @@
           mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL),
           mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL),
           mAutoAddOverlay(false), mGenDependencies(false), mNoVersionVectors(false),
+          mNoVersionTransitions(false),
           mCrunchedOutputDir(NULL), mProguardFile(NULL), mMainDexProguardFile(NULL),
           mAndroidManifestFile(NULL), mPublicOutputFile(NULL),
           mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL),
@@ -219,6 +220,8 @@
     void setBuildAppAsSharedLibrary(bool val) { mBuildAppAsSharedLibrary = val; }
     void setNoVersionVectors(bool val) { mNoVersionVectors = val; }
     bool getNoVersionVectors() const { return mNoVersionVectors; }
+    void setNoVersionTransitions(bool val) { mNoVersionTransitions = val; }
+    bool getNoVersionTransitions() const { return mNoVersionTransitions; }
 
     /*
      * Set and get the file specification.
@@ -299,6 +302,7 @@
     bool        mAutoAddOverlay;
     bool        mGenDependencies;
     bool        mNoVersionVectors;
+    bool        mNoVersionTransitions;
     const char* mCrunchedOutputDir;
     const char* mProguardFile;
     const char* mMainDexProguardFile;
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 984d98e..417b7ae 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -223,6 +223,8 @@
         "       localization\n"
         "   --no-version-vectors\n"
         "       Do not automatically generate versioned copies of vector XML resources.\n"
+        "   --no-version-transitions\n"
+        "       Do not automatically generate versioned copies of transition XML resources.\n"
         "   --private-symbols\n"
         "       Java package name to use when generating R.java for private resources.\n",
         gDefaultIgnoreAssets);
@@ -704,6 +706,8 @@
                     bundle.setPseudolocalize(PSEUDO_ACCENTED | PSEUDO_BIDI);
                 } else if (strcmp(cp, "-no-version-vectors") == 0) {
                     bundle.setNoVersionVectors(true);
+                } else if (strcmp(cp, "-no-version-transitions") == 0) {
+                    bundle.setNoVersionTransitions(true);
                 } else if (strcmp(cp, "-private-symbols") == 0) {
                     argc--;
                     argv++;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 045d68c..cf5badc 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1223,6 +1223,7 @@
     sp<ResourceTypeSet> colors;
     sp<ResourceTypeSet> menus;
     sp<ResourceTypeSet> mipmaps;
+    sp<ResourceTypeSet> fonts;
 
     ASSIGN_IT(drawable);
     ASSIGN_IT(layout);
@@ -1235,6 +1236,7 @@
     ASSIGN_IT(color);
     ASSIGN_IT(menu);
     ASSIGN_IT(mipmap);
+    ASSIGN_IT(font);
 
     assets->setResources(resources);
     // now go through any resource overlays and collect their files
@@ -1257,6 +1259,7 @@
             !applyFileOverlay(bundle, assets, &raws, "raw") ||
             !applyFileOverlay(bundle, assets, &colors, "color") ||
             !applyFileOverlay(bundle, assets, &menus, "menu") ||
+            !applyFileOverlay(bundle, assets, &fonts, "font") ||
             !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
         return UNKNOWN_ERROR;
     }
@@ -1291,6 +1294,13 @@
         }
     }
 
+    if (fonts != NULL) {
+        err = makeFileResources(bundle, assets, &table, fonts, "font");
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
+    }
+
     if (layouts != NULL) {
         err = makeFileResources(bundle, assets, &table, layouts, "layout");
         if (err != NO_ERROR) {
@@ -1549,6 +1559,26 @@
         err = NO_ERROR;
     }
 
+    if (fonts != NULL) {
+        ResourceDirIterator it(fonts, String8("font"));
+        while ((err=it.next()) == NO_ERROR) {
+            // fonts can be resources other than xml.
+            if (it.getFile()->getPath().getPathExtension() == ".xml") {
+                String8 src = it.getFile()->getPrintableSource();
+                err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                        it.getFile(), &table, xmlFlags);
+                if (err != NO_ERROR) {
+                    hasErrors = true;
+                }
+            }
+        }
+
+        if (err < NO_ERROR) {
+            hasErrors = true;
+        }
+        err = NO_ERROR;
+    }
+
     // Now compile any generated resources.
     std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
     while (!workQueue.empty()) {
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 661409e..63498f7 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -4730,6 +4730,32 @@
     return NO_ERROR;
 }
 
+const String16 kTransitionElements[] = {
+    String16("fade"),
+    String16("changeBounds"),
+    String16("slide"),
+    String16("explode"),
+    String16("changeImageTransform"),
+    String16("changeTransform"),
+    String16("changeClipBounds"),
+    String16("autoTransition"),
+    String16("recolor"),
+    String16("changeScroll"),
+    String16("transitionSet"),
+    String16("transition"),
+    String16("transitionManager"),
+};
+
+static bool IsTransitionElement(const String16& name) {
+    for (int i = 0, size = sizeof(kTransitionElements) / sizeof(kTransitionElements[0]);
+         i < size; ++i) {
+        if (name == kTransitionElements[i]) {
+            return true;
+        }
+    }
+    return false;
+}
+
 status_t ResourceTable::modifyForCompat(const Bundle* bundle,
                                         const String16& resourceName,
                                         const sp<AaptFile>& target,
@@ -4766,6 +4792,11 @@
             continue;
         }
 
+        if (bundle->getNoVersionTransitions() && (IsTransitionElement(node->getElementName()))) {
+            // We were told not to version transition tags, so skip the children here.
+            continue;
+        }
+
         const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
         for (size_t i = 0; i < attrs.size(); i++) {
             const XMLNode::attribute_entry& attr = attrs[i];
diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp
index 23f61e9..4f22fa5 100644
--- a/tools/aapt/tests/AaptConfig_test.cpp
+++ b/tools/aapt/tests/AaptConfig_test.cpp
@@ -103,13 +103,13 @@
     ConfigDescription config;
     EXPECT_TRUE(TestParse("widecg", &config));
     EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES,
-              config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+              config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
     EXPECT_EQ(SDK_O, config.sdkVersion);
     EXPECT_EQ(String8("widecg-v26"), config.toString());
 
     EXPECT_TRUE(TestParse("nowidecg", &config));
     EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO,
-              config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+              config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
     EXPECT_EQ(SDK_O, config.sdkVersion);
     EXPECT_EQ(String8("nowidecg-v26"), config.toString());
 }
@@ -118,13 +118,13 @@
     ConfigDescription config;
     EXPECT_TRUE(TestParse("highdr", &config));
     EXPECT_EQ(android::ResTable_config::HDR_YES,
-              config.colorimetry & android::ResTable_config::MASK_HDR);
+              config.colorMode & android::ResTable_config::MASK_HDR);
     EXPECT_EQ(SDK_O, config.sdkVersion);
     EXPECT_EQ(String8("highdr-v26"), config.toString());
 
     EXPECT_TRUE(TestParse("lowdr", &config));
     EXPECT_EQ(android::ResTable_config::HDR_NO,
-              config.colorimetry & android::ResTable_config::MASK_HDR);
+              config.colorMode & android::ResTable_config::MASK_HDR);
     EXPECT_EQ(SDK_O, config.sdkVersion);
     EXPECT_EQ(String8("lowdr-v26"), config.toString());
 }
\ No newline at end of file
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 5bea3ad..46098cb 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -209,20 +209,20 @@
 static bool parseWideColorGamut(const char* name, ResTable_config* out) {
   if (strcmp(name, kWildcardName) == 0) {
     if (out)
-      out->colorimetry =
-          (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
           ResTable_config::WIDE_COLOR_GAMUT_ANY;
     return true;
   } else if (strcmp(name, "widecg") == 0) {
     if (out)
-      out->colorimetry =
-          (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
           ResTable_config::WIDE_COLOR_GAMUT_YES;
     return true;
   } else if (strcmp(name, "nowidecg") == 0) {
     if (out)
-      out->colorimetry =
-          (out->colorimetry & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
           ResTable_config::WIDE_COLOR_GAMUT_NO;
     return true;
   }
@@ -232,20 +232,20 @@
 static bool parseHdr(const char* name, ResTable_config* out) {
   if (strcmp(name, kWildcardName) == 0) {
     if (out)
-      out->colorimetry =
-          (out->colorimetry & ~ResTable_config::MASK_HDR) |
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_HDR) |
           ResTable_config::HDR_ANY;
     return true;
   } else if (strcmp(name, "highdr") == 0) {
     if (out)
-      out->colorimetry =
-          (out->colorimetry & ~ResTable_config::MASK_HDR) |
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_HDR) |
           ResTable_config::HDR_YES;
     return true;
   } else if (strcmp(name, "lowdr") == 0) {
     if (out)
-      out->colorimetry =
-          (out->colorimetry & ~ResTable_config::MASK_HDR) |
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_HDR) |
           ResTable_config::HDR_NO;
     return true;
   }
@@ -840,8 +840,8 @@
   uint16_t min_sdk = 0;
   if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
                 == ResTable_config::UI_MODE_TYPE_VR_HEADSET ||
-            config->colorimetry & ResTable_config::MASK_WIDE_COLOR_GAMUT ||
-            config->colorimetry & ResTable_config::MASK_HDR) {
+            config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT ||
+            config->colorMode & ResTable_config::MASK_HDR) {
         min_sdk = SDK_O;
   } else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
     min_sdk = SDK_MARSHMALLOW;
@@ -912,11 +912,11 @@
   if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) {
     return !(o.screenLayout2 & MASK_SCREENROUND);
   }
-  if ((colorimetry | o.colorimetry) & MASK_HDR) {
-    return !(o.colorimetry & MASK_HDR);
+  if ((colorMode | o.colorMode) & MASK_HDR) {
+    return !(o.colorMode & MASK_HDR);
   }
-  if ((colorimetry | o.colorimetry) & MASK_WIDE_COLOR_GAMUT) {
-    return !(o.colorimetry & MASK_WIDE_COLOR_GAMUT);
+  if ((colorMode | o.colorMode) & MASK_WIDE_COLOR_GAMUT) {
+    return !(o.colorMode & MASK_WIDE_COLOR_GAMUT);
   }
   if (orientation || o.orientation) return (!o.orientation);
   if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) {
@@ -964,9 +964,9 @@
          !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||
          !pred(screenLayout2 & MASK_SCREENROUND,
                o.screenLayout2 & MASK_SCREENROUND) ||
-         !pred(colorimetry & MASK_HDR, o.colorimetry & MASK_HDR) ||
-         !pred(colorimetry & MASK_WIDE_COLOR_GAMUT,
-               o.colorimetry & MASK_WIDE_COLOR_GAMUT) ||
+         !pred(colorMode & MASK_HDR, o.colorMode & MASK_HDR) ||
+         !pred(colorMode & MASK_WIDE_COLOR_GAMUT,
+               o.colorMode & MASK_WIDE_COLOR_GAMUT) ||
          !pred(orientation, o.orientation) ||
          !pred(touchscreen, o.touchscreen) ||
          !pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN) ||
diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp
index b88838a..14a5656 100644
--- a/tools/aapt2/ConfigDescription_test.cpp
+++ b/tools/aapt2/ConfigDescription_test.cpp
@@ -106,13 +106,13 @@
   ConfigDescription config;
   EXPECT_TRUE(TestParse("widecg", &config));
   EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES,
-            config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+            config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
   EXPECT_EQ(SDK_O, config.sdkVersion);
   EXPECT_EQ(std::string("widecg-v26"), config.toString().string());
 
   EXPECT_TRUE(TestParse("nowidecg", &config));
   EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO,
-            config.colorimetry & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+            config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
   EXPECT_EQ(SDK_O, config.sdkVersion);
   EXPECT_EQ(std::string("nowidecg-v26"), config.toString().string());
 }
@@ -121,13 +121,13 @@
   ConfigDescription config;
   EXPECT_TRUE(TestParse("highdr", &config));
   EXPECT_EQ(android::ResTable_config::HDR_YES,
-            config.colorimetry & android::ResTable_config::MASK_HDR);
+            config.colorMode & android::ResTable_config::MASK_HDR);
   EXPECT_EQ(SDK_O, config.sdkVersion);
   EXPECT_EQ(std::string("highdr-v26"), config.toString().string());
 
   EXPECT_TRUE(TestParse("lowdr", &config));
   EXPECT_EQ(android::ResTable_config::HDR_NO,
-            config.colorimetry & android::ResTable_config::MASK_HDR);
+            config.colorMode & android::ResTable_config::MASK_HDR);
   EXPECT_EQ(SDK_O, config.sdkVersion);
   EXPECT_EQ(std::string("lowdr-v26"), config.toString().string());
 }
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 74d4019..15d7e2e 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
 static const char* sMajorVersion = "2";
 
 // Update minor version whenever a feature or flag is added.
-static const char* sMinorVersion = "4";
+static const char* sMinorVersion = "5";
 
 int PrintVersion() {
   std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk
index bc40a62..a6f32d4 100644
--- a/tools/aapt2/integration-tests/AppOne/Android.mk
+++ b/tools/aapt2/integration-tests/AppOne/Android.mk
@@ -24,5 +24,5 @@
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     AaptTestStaticLibOne \
     AaptTestStaticLibTwo
-LOCAL_AAPT_FLAGS := --no-version-vectors
+LOCAL_AAPT_FLAGS := --no-version-vectors --no-version-transitions
 include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml
new file mode 100644
index 0000000..e10e6c2
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+    android:transitionOrdering="sequential">
+    <fade android:fadingMode="fade_out" />
+    <changeBounds />
+    <fade android:fadingMode="fade_in" />
+</transitionSet>
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index c3ce076..f7e0f8f 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -80,6 +80,7 @@
   // Optimizations/features.
   bool no_auto_version = false;
   bool no_version_vectors = false;
+  bool no_version_transitions = false;
   bool no_resource_deduping = false;
   bool no_xml_namespaces = false;
   bool do_not_compress_anything = false;
@@ -250,6 +251,7 @@
 struct ResourceFileFlattenerOptions {
   bool no_auto_version = false;
   bool no_version_vectors = false;
+  bool no_version_transitions = false;
   bool no_xml_namespaces = false;
   bool keep_raw_values = false;
   bool do_not_compress_anything = false;
@@ -306,6 +308,23 @@
   return ArchiveEntry::kCompress;
 }
 
+static bool IsTransitionElement(const std::string& name) {
+  return
+    name == "fade" ||
+    name == "changeBounds" ||
+    name == "slide" ||
+    name == "explode" ||
+    name == "changeImageTransform" ||
+    name == "changeTransform" ||
+    name == "changeClipBounds" ||
+    name == "autoTransition" ||
+    name == "recolor" ||
+    name == "changeScroll" ||
+    name == "transitionSet" ||
+    name == "transition" ||
+    name == "transitionManager";
+}
+
 bool ResourceFileFlattener::LinkAndVersionXmlFile(
     ResourceTable* table, FileOperation* file_op,
     std::queue<FileOperation>* out_file_op_queue) {
@@ -345,6 +364,17 @@
         }
       }
     }
+    if (options_.no_version_transitions) {
+      // Skip this if it is a transition resource.
+      xml::Element* el = xml::FindRootElement(doc);
+      if (el && el->namespace_uri.empty()) {
+        if (IsTransitionElement(el->name)) {
+          // We are NOT going to version this file.
+          file_op->skip_version = true;
+          return true;
+        }
+      }
+    }
 
     const ConfigDescription& config = file_op->config;
 
@@ -1384,6 +1414,7 @@
         options_.extensions_to_not_compress;
     file_flattener_options.no_auto_version = options_.no_auto_version;
     file_flattener_options.no_version_vectors = options_.no_version_vectors;
+    file_flattener_options.no_version_transitions = options_.no_version_transitions;
     file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces;
     file_flattener_options.update_proguard_spec =
         static_cast<bool>(options_.generate_proguard_rules_path);
@@ -1863,6 +1894,11 @@
                           "Use this only\n"
                           "when building with vector drawable support library",
                           &options.no_version_vectors)
+          .OptionalSwitch("--no-version-transitions",
+                          "Disables automatic versioning of transition resources. "
+                          "Use this only\n"
+                          "when building with transition support library",
+                          &options.no_version_transitions)
           .OptionalSwitch("--no-resource-deduping",
                           "Disables automatic deduping of resources with\n"
                           "identical values across compatible configurations.",
@@ -2104,6 +2140,7 @@
   if (options.static_lib) {
     options.no_auto_version = true;
     options.no_version_vectors = true;
+    options.no_version_transitions = true;
   }
 
   LinkCommand cmd(&context, options);
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 8001033..e2a752e 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -1,5 +1,10 @@
 # Android Asset Packaging Tool 2.0 (AAPT2) release notes
 
+## Version 2.5
+### `aapt2 link ...`
+- Transition XML versioning: Adds a new flag `--no-version-transitions` to disable automatic
+  versioning of Transition XML resources.
+
 ## Version 2.4
 ### `aapt2 link ...`
 - Supports `<meta-data>` tags in `<manifest>`.
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index d0c9599..147ed99 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -249,14 +249,17 @@
     // ---- delegate methods ----
     @LayoutlibDelegate
     /*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) {
-        final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr);
+        if (thisFontFamily.mBuilderPtr == 0) {
+            throw new IllegalStateException("Unable to call addFont after freezing.");
+        }
+        final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr);
         return delegate != null && delegate.addFont(path, ttcIndex);
     }
 
     // ---- native methods ----
 
     @LayoutlibDelegate
-    /*package*/ static long nCreateFamily(String lang, int variant) {
+    /*package*/ static long nInitBuilder(String lang, int variant) {
         // TODO: support lang. This is required for japanese locale.
         FontFamily_Delegate delegate = new FontFamily_Delegate();
         // variant can be 0, 1 or 2.
@@ -271,6 +274,11 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static long nCreateFamily(long builderPtr) {
+        return builderPtr;
+    }
+
+    @LayoutlibDelegate
     /*package*/ static void nUnrefFamily(long nativePtr) {
         // Removing the java reference for the object doesn't mean that it's freed for garbage
         // collection. Typeface_Delegate may still hold a reference for it.
@@ -278,22 +286,22 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) {
+    /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
         assert false : "The only client of this method has been overriden.";
         return false;
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
+    /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
             int ttcIndex, List<FontConfig.Axis> listOfAxis,
             int weight, boolean isItalic) {
         assert false : "The only client of this method has been overriden.";
         return false;
     }
 
-    static boolean addFont(long nativeFamily, final String path, final int weight,
+    static boolean addFont(long builderPtr, final String path, final int weight,
             final boolean isItalic) {
-        final FontFamily_Delegate delegate = getDelegate(nativeFamily);
+        final FontFamily_Delegate delegate = getDelegate(builderPtr);
         if (delegate != null) {
             if (sFontLocation == null) {
                 delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic));
@@ -305,8 +313,8 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
-        FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+    /*package*/ static boolean nAddFontFromAsset(long builderPtr, AssetManager mgr, String path) {
+        FontFamily_Delegate ffd = sManager.getDelegate(builderPtr);
         if (ffd == null) {
             return false;
         }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 6e337d5..f6c463f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -213,9 +213,10 @@
             Map<String, ByteBuffer> bufferForPath) {
         FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
         for (FontConfig.Font font : family.getFonts()) {
-            FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.getFontName(),
+            FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.getFontName(),
                     font.getWeight(), font.isItalic());
         }
+        fontFamily.freeze();
         return fontFamily;
     }
 
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 5ed5460..56898f1 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -340,12 +340,6 @@
     }
 
     @Override
-    public Rect getBoundsForNewConfiguration(int stackId) throws RemoteException {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    @Override
     public void setScreenCaptureDisabled(int userId, boolean disabled) {
         // TODO Auto-generated method stub
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 68680d5..dff4f69 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1325,6 +1325,12 @@
     }
 
     @Override
+    public Context createContextForSplit(String splitName) {
+        // pass
+        return null;
+    }
+
+    @Override
     public String[] databaseList() {
         // pass
         return null;
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 7ba86fd..741eb27 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -339,7 +339,8 @@
      */
     private final static String[] PROMOTED_FIELDS = new String[] {
         "android.graphics.drawable.VectorDrawable#mVectorState",
-        "android.view.Choreographer#mLastFrameTimeNanos"
+        "android.view.Choreographer#mLastFrameTimeNanos",
+        "android.graphics.FontFamily#mBuilderPtr"
     };
 
     /**
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index 65a49ea..98fd0f3 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -21,11 +21,17 @@
 import android.net.wifi.hotspot2.pps.HomeSP;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.xml.sax.SAXException;
@@ -131,16 +137,34 @@
     private static final String NODE_FQDN = "FQDN";
     private static final String NODE_FRIENDLY_NAME = "FriendlyName";
     private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
+    private static final String NODE_NETWORK_ID = "NetworkID";
+    private static final String NODE_SSID = "SSID";
+    private static final String NODE_HESSID = "HESSID";
+    private static final String NODE_ICON_URL = "IconURL";
+    private static final String NODE_HOME_OI_LIST = "HomeOIList";
+    private static final String NODE_HOME_OI = "HomeOI";
+    private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired";
+    private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners";
 
     /**
      * Fields under Credential subtree.
      */
     private static final String NODE_CREDENTIAL = "Credential";
+    private static final String NODE_CREATION_DATE = "CreationDate";
+    private static final String NODE_EXPIRATION_DATE = "ExpirationDate";
     private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
     private static final String NODE_USERNAME = "Username";
     private static final String NODE_PASSWORD = "Password";
+    private static final String NODE_MACHINE_MANAGED = "MachineManaged";
+    private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp";
+    private static final String NODE_ABLE_TO_SHARE = "AbleToShare";
     private static final String NODE_EAP_METHOD = "EAPMethod";
     private static final String NODE_EAP_TYPE = "EAPType";
+    private static final String NODE_VENDOR_ID = "VendorId";
+    private static final String NODE_VENDOR_TYPE = "VendorType";
+    private static final String NODE_INNER_EAP_TYPE = "InnerEAPType";
+    private static final String NODE_INNER_VENDOR_ID = "InnerVendorID";
+    private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType";
     private static final String NODE_INNER_METHOD = "InnerMethod";
     private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
     private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
@@ -148,6 +172,7 @@
     private static final String NODE_REALM = "Realm";
     private static final String NODE_SIM = "SIM";
     private static final String NODE_SIM_IMSI = "IMSI";
+    private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
 
     /**
      * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
@@ -558,6 +583,20 @@
                     homeSp.roamingConsortiumOIs =
                             parseRoamingConsortiumOI(getPpsNodeValue(child));
                     break;
+                case NODE_ICON_URL:
+                    homeSp.iconUrl = getPpsNodeValue(child);
+                    break;
+                case NODE_NETWORK_ID:
+                    homeSp.homeNetworkIds = parseNetworkIds(child);
+                    break;
+                case NODE_HOME_OI_LIST:
+                    Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child);
+                    homeSp.matchAllOIs = convertFromLongList(homeOIs.first);
+                    homeSp.matchAnyOIs = convertFromLongList(homeOIs.second);
+                    break;
+                case NODE_OTHER_HOME_PARTNERS:
+                    homeSp.otherHomePartners = parseOtherHomePartners(child);
+                    break;
                 default:
                     throw new ParsingException("Unknown node under HomeSP: " + child.getName());
             }
@@ -587,6 +626,192 @@
     }
 
     /**
+     * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID
+     *             subtree
+     * @return HashMap<String, Long> representing list of <SSID, HESSID> pair.
+     * @throws ParsingException
+     */
+    static private Map<String, Long> parseNetworkIds(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for NetworkID");
+        }
+
+        Map<String, Long> networkIds = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<String, Long> networkId = parseNetworkIdInstance(child);
+            networkIds.put(networkId.first, networkId.second);
+        }
+        return networkIds;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree.
+     * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+     * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/NetworkID/<X+> subtree
+     * @return Pair<String, Long> representing <SSID, HESSID> pair.
+     * @throws ParsingException
+     */
+    static private Pair<String, Long> parseNetworkIdInstance(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for NetworkID instance");
+        }
+
+        String ssid = null;
+        Long hessid = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_SSID:
+                    ssid = getPpsNodeValue(child);
+                    break;
+                case NODE_HESSID:
+                    try {
+                        hessid = Long.parseLong(getPpsNodeValue(child), 16);
+                    } catch (NumberFormatException e) {
+                        throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
+                    }
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under NetworkID instance: " +
+                            child.getName());
+            }
+        }
+        if (ssid == null)
+            throw new ParsingException("NetworkID instance missing SSID");
+
+        return new Pair<String, Long>(ssid, hessid);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList
+     *             subtree
+     * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list.
+     * @throws ParsingException
+     */
+    private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for HomeOIList");
+        }
+
+        List<Long> matchAllOIs = new ArrayList<Long>();
+        List<Long> matchAnyOIs = new ArrayList<Long>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<Long, Boolean> homeOI = parseHomeOIInstance(child);
+            if (homeOI.second.booleanValue()) {
+                matchAllOIs.add(homeOI.first);
+            } else {
+                matchAnyOIs.add(homeOI.first);
+            }
+        }
+        return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree.
+     * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+     * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree
+     * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag
+     * @throws ParsingException
+     */
+    private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for HomeOI instance");
+        }
+
+        Long oi = null;
+        Boolean required = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_HOME_OI:
+                    try {
+                        oi = Long.valueOf(getPpsNodeValue(child), 16);
+                    } catch (NumberFormatException e) {
+                        throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child));
+                    }
+                    break;
+                case NODE_HOME_OI_REQUIRED:
+                    required = Boolean.valueOf(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under NetworkID instance: " +
+                            child.getName());
+            }
+        }
+        if (oi == null) {
+            throw new ParsingException("HomeOI instance missing OI field");
+        }
+        if (required == null) {
+            throw new ParsingException("HomeOI instance missing required field");
+        }
+        return new Pair<Long, Boolean>(oi, required);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree.
+     * This contains a list of FQDN (Fully Qualified Domain Name) that are considered
+     * home partners.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/OtherHomePartners subtree
+     * @return String[] list of partner's FQDN
+     * @throws ParsingException
+     */
+    private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for OtherHomePartners");
+        }
+        List<String> otherHomePartners = new ArrayList<String>();
+        for (PPSNode child : node.getChildren()) {
+            String fqdn = parseOtherHomePartnerInstance(child);
+            otherHomePartners.add(fqdn);
+        }
+        return otherHomePartners.toArray(new String[otherHomePartners.size()]);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree.
+     * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+     * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree
+     * @return String FQDN of the partner
+     * @throws ParsingException
+     */
+    private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for OtherHomePartner instance");
+        }
+        String fqdn = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_FQDN:
+                    fqdn = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException(
+                            "Unknown node under OtherHomePartner instance: " + child.getName());
+            }
+        }
+        if (fqdn == null) {
+            throw new ParsingException("OtherHomePartner instance missing FQDN field");
+        }
+        return fqdn;
+    }
+
+    /**
      * Parse configurations under PerProviderSubscription/Credential subtree.
      *
      * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
@@ -601,6 +826,12 @@
         Credential credential = new Credential();
         for (PPSNode child: node.getChildren()) {
             switch (child.getName()) {
+                case NODE_CREATION_DATE:
+                    credential.creationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_EXPIRATION_DATE:
+                    credential.expirationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
                 case NODE_USERNAME_PASSWORD:
                     credential.userCredential = parseUserCredential(child);
                     break;
@@ -610,6 +841,10 @@
                 case NODE_REALM:
                     credential.realm = getPpsNodeValue(child);
                     break;
+                case NODE_CHECK_AAA_SERVER_CERT_STATUS:
+                    credential.checkAAAServerCertStatus =
+                            Boolean.parseBoolean(getPpsNodeValue(child));
+                    break;
                 case NODE_SIM:
                     credential.simCredential = parseSimCredential(child);
                     break;
@@ -644,6 +879,15 @@
                 case NODE_PASSWORD:
                     userCred.password = getPpsNodeValue(child);
                     break;
+                case NODE_MACHINE_MANAGED:
+                    userCred.machineManaged = Boolean.parseBoolean(getPpsNodeValue(child));
+                    break;
+                case NODE_SOFT_TOKEN_APP:
+                    userCred.softTokenApp = getPpsNodeValue(child);
+                    break;
+                case NODE_ABLE_TO_SHARE:
+                    userCred.ableToShare = Boolean.parseBoolean(getPpsNodeValue(child));
+                    break;
                 case NODE_EAP_METHOD:
                     parseEAPMethod(child, userCred);
                     break;
@@ -678,6 +922,15 @@
                 case NODE_INNER_METHOD:
                     userCred.nonEapInnerMethod = getPpsNodeValue(child);
                     break;
+                case NODE_VENDOR_ID:
+                case NODE_VENDOR_TYPE:
+                case NODE_INNER_EAP_TYPE:
+                case NODE_INNER_VENDOR_ID:
+                case NODE_INNER_VENDOR_TYPE:
+                    // Only EAP-TTLS is currently supported for user credential, which doesn't
+                    // use any of these parameters.
+                    Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName());
+                    break;
                 default:
                     throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
             }
@@ -770,6 +1023,22 @@
     }
 
     /**
+     * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z'
+     * @return number of milliseconds
+     * @throws ParsingException
+     */
+    private static long parseDate(String dateStr) throws ParsingException {
+        try {
+            DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+            return format.parse(dateStr).getTime();
+        } catch (ParseException pe) {
+            throw new ParsingException("Badly formatted time: " + dateStr);
+        }
+    }
+
+    /**
      * Parse an integer string.
      *
      * @param value String of integer value
@@ -783,4 +1052,19 @@
             throw new ParsingException("Invalid integer value: " + value);
         }
     }
+
+    /**
+     * Convert a List<Long> to a primitive long array long[].
+     *
+     * @param list List to be converted
+     * @return long[]
+     */
+    private static long[] convertFromLongList(List<Long> list) {
+        Long[] objectArray = list.toArray(new Long[list.size()]);
+        long[] primitiveArray = new long[objectArray.length];
+        for (int i = 0; i < objectArray.length; i++) {
+            primitiveArray[i] = objectArray[i].longValue();
+        }
+        return primitiveArray;
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 790dfaf..3374f42d 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
@@ -41,8 +42,6 @@
  * In addition to the fields in the Credential subtree, this will also maintain necessary
  * information for the private key and certificates associated with this credential.
  *
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
  * @hide
  */
 public final class Credential implements Parcelable {
@@ -52,7 +51,21 @@
      * Max string length for realm.  Refer to Credential/Realm node in Hotspot 2.0 Release 2
      * Technical Specification Section 9.1 for more info.
      */
-    private static final int MAX_REALM_LENGTH = 253;
+    private static final int MAX_REALM_BYTES = 253;
+
+    /**
+     * The time this credential is created. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long creationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The time this credential will expire. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+    * Using Long.MIN_VALUE to indicate unset value.
+     */
+    public long expirationTimeInMs = Long.MIN_VALUE;
 
     /**
      * The realm associated with this credential.  It will be used to determine
@@ -62,6 +75,13 @@
     public String realm = null;
 
     /**
+     * When set to true, the device should check AAA (Authentication, Authorization,
+     * and Accounting) server's certificate during EAP (Extensible Authentication
+     * Protocol) authentication.
+     */
+    public boolean checkAAAServerCertStatus = false;
+
+    /**
      * Username-password based credential.
      * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
      */
@@ -70,13 +90,13 @@
          * Maximum string length for username.  Refer to Credential/UsernamePassword/Username
          * node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
          */
-        private static final int MAX_USERNAME_LENGTH = 63;
+        private static final int MAX_USERNAME_BYTES = 63;
 
         /**
          * Maximum string length for password.  Refer to Credential/UsernamePassword/Password
          * in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
          */
-        private static final int MAX_PASSWORD_LENGTH = 255;
+        private static final int MAX_PASSWORD_BYTES = 255;
 
         /**
          * Supported Non-EAP inner methods.  Refer to
@@ -97,6 +117,21 @@
         public String password = null;
 
         /**
+         * Flag indicating if the password is machine managed.
+         */
+        public boolean machineManaged = false;
+
+        /**
+         * The name of the application used to generate the password.
+         */
+        public String softTokenApp = null;
+
+        /**
+         * Flag indicating if this credential is usable on other mobile devices as well.
+         */
+        public boolean ableToShare = false;
+
+        /**
          * EAP (Extensible Authentication Protocol) method type.
          * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
          * for valid values.
@@ -123,6 +158,9 @@
             if (source != null) {
                 username = source.username;
                 password = source.password;
+                machineManaged = source.machineManaged;
+                softTokenApp = source.softTokenApp;
+                ableToShare = source.ableToShare;
                 eapType = source.eapType;
                 nonEapInnerMethod = source.nonEapInnerMethod;
             }
@@ -137,6 +175,9 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeString(username);
             dest.writeString(password);
+            dest.writeInt(machineManaged ? 1 : 0);
+            dest.writeString(softTokenApp);
+            dest.writeInt(ableToShare ? 1 : 0);
             dest.writeInt(eapType);
             dest.writeString(nonEapInnerMethod);
         }
@@ -151,10 +192,13 @@
             }
 
             UserCredential that = (UserCredential) thatObject;
-            return TextUtils.equals(username, that.username) &&
-                    TextUtils.equals(password, that.password) &&
-                    eapType == that.eapType &&
-                    TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
+            return TextUtils.equals(username, that.username)
+                    && TextUtils.equals(password, that.password)
+                    && machineManaged == that.machineManaged
+                    && TextUtils.equals(softTokenApp, that.softTokenApp)
+                    && ableToShare == that.ableToShare
+                    && eapType == that.eapType
+                    && TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
         }
 
         /**
@@ -167,8 +211,9 @@
                 Log.d(TAG, "Missing username");
                 return false;
             }
-            if (username.length() > MAX_USERNAME_LENGTH) {
-                Log.d(TAG, "username exceeding maximum length: " + username.length());
+            if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+                Log.d(TAG, "username exceeding maximum length: "
+                        + username.getBytes(StandardCharsets.UTF_8).length);
                 return false;
             }
 
@@ -176,8 +221,9 @@
                 Log.d(TAG, "Missing password");
                 return false;
             }
-            if (password.length() > MAX_PASSWORD_LENGTH) {
-                Log.d(TAG, "password exceeding maximum length: " + password.length());
+            if (password.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+                Log.d(TAG, "password exceeding maximum length: "
+                        + password.getBytes(StandardCharsets.UTF_8).length);
                 return false;
             }
 
@@ -202,6 +248,9 @@
                     UserCredential userCredential = new UserCredential();
                     userCredential.username = in.readString();
                     userCredential.password = in.readString();
+                    userCredential.machineManaged = in.readInt() != 0;
+                    userCredential.softTokenApp = in.readString();
+                    userCredential.ableToShare = in.readInt() != 0;
                     userCredential.eapType = in.readInt();
                     userCredential.nonEapInnerMethod = in.readString();
                     return userCredential;
@@ -281,8 +330,8 @@
             }
 
             CertificateCredential that = (CertificateCredential) thatObject;
-            return TextUtils.equals(certType, that.certType) &&
-                    Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
+            return TextUtils.equals(certType, that.certType)
+                    && Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
         }
 
         /**
@@ -295,8 +344,8 @@
                 Log.d(TAG, "Unsupported certificate type: " + certType);
                 return false;
             }
-            if (certSha256FingerPrint == null ||
-                    certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
+            if (certSha256FingerPrint == null
+                    || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
                 Log.d(TAG, "Invalid SHA-256 fingerprint");
                 return false;
             }
@@ -378,8 +427,8 @@
             }
 
             SimCredential that = (SimCredential) thatObject;
-            return TextUtils.equals(imsi, that.imsi) &&
-                    eapType == that.eapType;
+            return TextUtils.equals(imsi, that.imsi)
+                    && eapType == that.eapType;
         }
 
         @Override
@@ -400,8 +449,8 @@
             if (!verifyImsi()) {
                 return false;
             }
-            if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA &&
-                    eapType != EAPConstants.EAP_AKA_PRIME) {
+            if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA
+                    && eapType != EAPConstants.EAP_AKA_PRIME) {
                 Log.d(TAG, "Invalid EAP Type for SIM credential: " + eapType);
                 return false;
             }
@@ -490,7 +539,10 @@
      */
     public Credential(Credential source) {
         if (source != null) {
+            creationTimeInMs = source.creationTimeInMs;
+            expirationTimeInMs = source.expirationTimeInMs;
             realm = source.realm;
+            checkAAAServerCertStatus = source.checkAAAServerCertStatus;
             if (source.userCredential != null) {
                 userCredential = new UserCredential(source.userCredential);
             }
@@ -516,7 +568,10 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(creationTimeInMs);
+        dest.writeLong(expirationTimeInMs);
         dest.writeString(realm);
+        dest.writeInt(checkAAAServerCertStatus ? 1 : 0);
         dest.writeParcelable(userCredential, flags);
         dest.writeParcelable(certCredential, flags);
         dest.writeParcelable(simCredential, flags);
@@ -535,16 +590,19 @@
         }
 
         Credential that = (Credential) thatObject;
-        return TextUtils.equals(realm, that.realm) &&
-                (userCredential == null ? that.userCredential == null :
-                    userCredential.equals(that.userCredential)) &&
-                (certCredential == null ? that.certCredential == null :
-                    certCredential.equals(that.certCredential)) &&
-                (simCredential == null ? that.simCredential == null :
-                    simCredential.equals(that.simCredential)) &&
-                isX509CertificateEquals(caCertificate, that.caCertificate) &&
-                isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) &&
-                isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
+        return TextUtils.equals(realm, that.realm)
+                && creationTimeInMs == that.creationTimeInMs
+                && expirationTimeInMs == that.expirationTimeInMs
+                && checkAAAServerCertStatus == that.checkAAAServerCertStatus
+                && (userCredential == null ? that.userCredential == null
+                    : userCredential.equals(that.userCredential))
+                && (certCredential == null ? that.certCredential == null
+                    : certCredential.equals(that.certCredential))
+                && (simCredential == null ? that.simCredential == null
+                    : simCredential.equals(that.simCredential))
+                && isX509CertificateEquals(caCertificate, that.caCertificate)
+                && isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain)
+                && isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
     }
 
     /**
@@ -557,8 +615,9 @@
             Log.d(TAG, "Missing realm");
             return false;
         }
-        if (realm.length() > MAX_REALM_LENGTH) {
-            Log.d(TAG, "realm exceeding maximum length: " + realm.length());
+        if (realm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
+            Log.d(TAG, "realm exceeding maximum length: "
+                    + realm.getBytes(StandardCharsets.UTF_8).length);
             return false;
         }
 
@@ -588,7 +647,10 @@
             @Override
             public Credential createFromParcel(Parcel in) {
                 Credential credential = new Credential();
+                credential.creationTimeInMs = in.readLong();
+                credential.expirationTimeInMs = in.readLong();
                 credential.realm = in.readString();
+                credential.checkAAAServerCertStatus = in.readInt() != 0;
                 credential.userCredential = in.readParcelable(null);
                 credential.certCredential = in.readParcelable(null);
                 credential.simCredential = in.readParcelable(null);
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
index d4a5792..4ddf210 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -21,7 +21,11 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Class representing HomeSP subtree in PerProviderSubscription (PPS)
@@ -30,14 +34,22 @@
  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
  * Release 2 Technical Specification.
  *
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
  * @hide
  */
 public final class HomeSP implements Parcelable {
     private static final String TAG = "HomeSP";
 
     /**
+     * Maximum number of bytes allowed for a SSID.
+     */
+    private static final int MAX_SSID_BYTES = 32;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
+    /**
      * FQDN (Fully Qualified Domain Name) of this home service provider.
      */
     public String fqdn = null;
@@ -48,6 +60,55 @@
     public String friendlyName = null;
 
     /**
+     * Icon URL of this home service provider.
+     */
+    public String iconUrl = null;
+
+    /**
+     * <SSID, HESSID> duple of the networks that are consider home networks.
+     *
+     * According to the Section 9.1.2 of the Hotspot 2.0 Release 2 Technical Specification,
+     * all nodes in the PSS MO are encoded using UTF-8 unless stated otherwise.  Thus, the SSID
+     * string is assumed to be encoded using UTF-8.
+     */
+    public Map<String, Long> homeNetworkIds = null;
+
+    /**
+     * Used for determining if this provider is a member of a given Hotspot provider.
+     * Every Organization Identifiers (OIs) in this list are required to match an OI in the
+     * the Roaming Consortium advertised by a Hotspot, in order to consider this provider
+     * as a member of that Hotspot provider (e.g. successful authentication with such Hotspot
+     * is possible).
+     *
+     * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+     * (MO) tree for more detail.
+     */
+    public long[] matchAllOIs = null;
+
+    /**
+     * Used for determining if this provider is a member of a given Hotspot provider.
+     * Matching of any Organization Identifiers (OIs) in this list with an OI in the
+     * Roaming Consortium advertised by a Hotspot, will consider this provider as a member
+     * of that Hotspot provider (e.g. successful authentication with such Hotspot
+     * is possible).
+     *
+     * {@link #matchAllOIs} will have precedence over this one, meaning this list will
+     * only be used for matching if {@link #matchAllOIs} is null or empty.
+     *
+     * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+     * (MO) tree for more detail.
+     */
+    public long[] matchAnyOIs = null;
+
+    /**
+     * List of FQDN (Fully Qualified Domain Name) of partner providers.
+     * These providers should also be regarded as home Hotspot operators.
+     * This relationship is most likely achieved via a commercial agreement or
+     * operator merges between the providers.
+     */
+    public String[] otherHomePartners = null;
+
+    /**
      * List of Organization Identifiers (OIs) identifying a roaming consortium of
      * which this provider is a member.
      */
@@ -64,13 +125,28 @@
      * @param source The source to copy from
      */
     public HomeSP(HomeSP source) {
-        if (source != null) {
-            fqdn = source.fqdn;
-            friendlyName = source.friendlyName;
-            if (source.roamingConsortiumOIs != null) {
-                roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
-                                                     source.roamingConsortiumOIs.length);
-            }
+        if (source == null) {
+            return;
+        }
+        fqdn = source.fqdn;
+        friendlyName = source.friendlyName;
+        iconUrl = source.iconUrl;
+        if (source.homeNetworkIds != null) {
+            homeNetworkIds = Collections.unmodifiableMap(source.homeNetworkIds);
+        }
+        if (source.matchAllOIs != null) {
+            matchAllOIs = Arrays.copyOf(source.matchAllOIs, source.matchAllOIs.length);
+        }
+        if (source.matchAnyOIs != null) {
+            matchAnyOIs = Arrays.copyOf(source.matchAnyOIs, source.matchAnyOIs.length);
+        }
+        if (source.otherHomePartners != null) {
+            otherHomePartners = Arrays.copyOf(source.otherHomePartners,
+                    source.otherHomePartners.length);
+        }
+        if (source.roamingConsortiumOIs != null) {
+            roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
+                    source.roamingConsortiumOIs.length);
         }
     }
 
@@ -83,6 +159,11 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(fqdn);
         dest.writeString(friendlyName);
+        dest.writeString(iconUrl);
+        writeHomeNetworkIds(dest, homeNetworkIds);
+        dest.writeLongArray(matchAllOIs);
+        dest.writeLongArray(matchAnyOIs);
+        dest.writeStringArray(otherHomePartners);
         dest.writeLongArray(roamingConsortiumOIs);
     }
 
@@ -96,9 +177,15 @@
         }
         HomeSP that = (HomeSP) thatObject;
 
-        return TextUtils.equals(fqdn, that.fqdn) &&
-                TextUtils.equals(friendlyName, that.friendlyName) &&
-                Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
+        return TextUtils.equals(fqdn, that.fqdn)
+                && TextUtils.equals(friendlyName, that.friendlyName)
+                && TextUtils.equals(iconUrl, that.iconUrl)
+                && (homeNetworkIds == null ? that.homeNetworkIds == null
+                        : homeNetworkIds.equals(that.homeNetworkIds))
+                && Arrays.equals(matchAllOIs, that.matchAllOIs)
+                && Arrays.equals(matchAnyOIs, that.matchAnyOIs)
+                && Arrays.equals(otherHomePartners, that.otherHomePartners)
+                && Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
     }
 
     /**
@@ -115,6 +202,16 @@
             Log.d(TAG, "Missing friendly name");
             return false;
         }
+        // Verify SSIDs specified in the NetworkID
+        if (homeNetworkIds != null) {
+            for (Map.Entry<String, Long> entry : homeNetworkIds.entrySet()) {
+                if (entry.getKey() == null ||
+                        entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+                    Log.d(TAG, "Invalid SSID in HomeNetworkIDs");
+                    return false;
+                }
+            }
+        }
         return true;
     }
 
@@ -125,6 +222,11 @@
                 HomeSP homeSp = new HomeSP();
                 homeSp.fqdn = in.readString();
                 homeSp.friendlyName = in.readString();
+                homeSp.iconUrl = in.readString();
+                homeSp.homeNetworkIds = readHomeNetworkIds(in);
+                homeSp.matchAllOIs = in.createLongArray();
+                homeSp.matchAnyOIs = in.createLongArray();
+                homeSp.otherHomePartners = in.createStringArray();
                 homeSp.roamingConsortiumOIs = in.createLongArray();
                 return homeSp;
             }
@@ -133,5 +235,51 @@
             public HomeSP[] newArray(int size) {
                 return new HomeSP[size];
             }
+
+            /**
+             * Helper function for reading a Home Network IDs map from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return Map of home network IDs
+             */
+            private Map<String, Long> readHomeNetworkIds(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<String, Long> networkIds = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    String key = in.readString();
+                    Long value = null;
+                    long readValue = in.readLong();
+                    if (readValue != NULL_VALUE) {
+                        value = Long.valueOf(readValue);
+                    }
+                    networkIds.put(key, value);
+                }
+                return networkIds;
+            }
         };
+
+    /**
+     * Helper function for writing Home Network IDs map to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param networkIds The map of home network IDs
+     */
+    private static void writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds) {
+        if (networkIds == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(networkIds.size());
+        for (Map.Entry<String, Long> entry : networkIds.entrySet()) {
+            dest.writeString(entry.getKey());
+            if (entry.getValue() == null) {
+                dest.writeLong(NULL_VALUE);
+            } else {
+                dest.writeLong(entry.getValue());
+            }
+        }
+    }
 }
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 53d38ad..3969f69 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -23,14 +23,86 @@
           <NodeName>RoamingConsortiumOI</NodeName>
           <Value>112233,445566</Value>
         </Node>
+        <Node>
+          <NodeName>IconURL</NodeName>
+          <Value>icon.test.com</Value>
+        </Node>
+        <Node>
+          <NodeName>NetworkID</NodeName>
+          <Node>
+            <NodeName>n001</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>TestSSID</Value>
+            </Node>
+            <Node>
+              <NodeName>HESSID</NodeName>
+              <Value>12345678</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>n002</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>NullHESSID</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>HomeOIList</NodeName>
+          <Node>
+            <NodeName>h001</NodeName>
+            <Node>
+              <NodeName>HomeOI</NodeName>
+              <Value>11223344</Value>
+            </Node>
+            <Node>
+              <NodeName>HomeOIRequired</NodeName>
+              <Value>true</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>h002</NodeName>
+            <Node>
+              <NodeName>HomeOI</NodeName>
+              <Value>55667788</Value>
+            </Node>
+            <Node>
+              <NodeName>HomeOIRequired</NodeName>
+              <Value>false</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>OtherHomePartners</NodeName>
+          <Node>
+            <NodeName>o001</NodeName>
+            <Node>
+              <NodeName>FQDN</NodeName>
+              <Value>other.fqdn.com</Value>
+            </Node>
+          </Node>
+        </Node>
       </Node>
       <Node>
         <NodeName>Credential</NodeName>
         <Node>
+          <NodeName>CreationDate</NodeName>
+          <Value>2016-01-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>ExpirationDate</NodeName>
+          <Value>2016-02-01T10:00:00Z</Value>
+        </Node>
+        <Node>
           <NodeName>Realm</NodeName>
           <Value>shaken.stirred.com</Value>
         </Node>
         <Node>
+          <NodeName>CheckAAAServerCertStatus</NodeName>
+          <Value>true</Value>
+        </Node>
+        <Node>
           <NodeName>UsernamePassword</NodeName>
           <Node>
             <NodeName>Username</NodeName>
@@ -41,6 +113,18 @@
             <Value>Ym9uZDAwNw==</Value>
           </Node>
           <Node>
+            <NodeName>MachineManaged</NodeName>
+            <Value>true</Value>
+          </Node>
+          <Node>
+            <NodeName>SoftTokenApp</NodeName>
+            <Value>TestApp</Value>
+          </Node>
+          <Node>
+            <NodeName>AbleToShare</NodeName>
+            <Value>true</Value>
+          </Node>
+          <Node>
             <NodeName>EAPMethod</NodeName>
             <Node>
               <NodeName>EAPType</NodeName>
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
index 10b0267..1c7508e 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -31,7 +31,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
+import java.util.HashMap;
 
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.omadm.PPSMOParser}.
@@ -77,7 +80,7 @@
      *
      * @return {@link PasspointConfiguration}
      */
-    private PasspointConfiguration generateConfigurationFromPPSMOTree() {
+    private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
         PasspointConfiguration config = new PasspointConfiguration();
 
         // HomeSP configuration.
@@ -85,13 +88,27 @@
         config.homeSp.friendlyName = "Century House";
         config.homeSp.fqdn = "mi6.co.uk";
         config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L};
+        config.homeSp.iconUrl = "icon.test.com";
+        config.homeSp.homeNetworkIds = new HashMap<>();
+        config.homeSp.homeNetworkIds.put("TestSSID", 0x12345678L);
+        config.homeSp.homeNetworkIds.put("NullHESSID", null);
+        config.homeSp.matchAllOIs = new long[] {0x11223344};
+        config.homeSp.matchAnyOIs = new long[] {0x55667788};
+        config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"};
 
         // Credential configuration.
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
         config.credential = new Credential();
+        config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime();
+        config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
         config.credential.realm = "shaken.stirred.com";
+        config.credential.checkAAAServerCertStatus = true;
         config.credential.userCredential = new Credential.UserCredential();
         config.credential.userCredential.username = "james";
         config.credential.userCredential.password = "Ym9uZDAwNw==";
+        config.credential.userCredential.machineManaged = true;
+        config.credential.userCredential.softTokenApp = "TestApp";
+        config.credential.userCredential.ableToShare = true;
         config.credential.userCredential.eapType = 21;
         config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
         config.credential.certCredential = new Credential.CertificateCredential();
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 9c8b749..f571c7f 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -37,6 +37,17 @@
  */
 @SmallTest
 public class CredentialTest {
+    /**
+     * Helper function for generating Credential for testing.
+     *
+     * @param userCred Instance of UserCredential
+     * @param certCred Instance of CertificateCredential
+     * @param simCred Instance of SimCredential
+     * @param caCert CA certificate
+     * @param clientCertificateChain Chain of client certificates
+     * @param clientPrivateKey Client private key
+     * @return {@link Credential}
+     */
     private static Credential createCredential(Credential.UserCredential userCred,
                                                Credential.CertificateCredential certCred,
                                                Credential.SimCredential simCred,
@@ -44,7 +55,10 @@
                                                X509Certificate[] clientCertificateChain,
                                                PrivateKey clientPrivateKey) {
         Credential cred = new Credential();
+        cred.creationTimeInMs = 123455L;
+        cred.expirationTimeInMs = 2310093L;
         cred.realm = "realm";
+        cred.checkAAAServerCertStatus = true;
         cred.userCredential = userCred;
         cred.certCredential = certCred;
         cred.simCredential = simCred;
@@ -54,6 +68,11 @@
         return cred;
     }
 
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
     private static Credential createCredentialWithCertificateCredential() {
         Credential.CertificateCredential certCred = new Credential.CertificateCredential();
         certCred.certType = "x509v3";
@@ -62,6 +81,11 @@
                 new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
     }
 
+    /**
+     * Helper function for generating SIM credential for testing.
+     *
+     * @return {@link Credential}
+     */
     private static Credential createCredentialWithSimCredential() {
         Credential.SimCredential simCred = new Credential.SimCredential();
         simCred.imsi = "1234*";
@@ -69,10 +93,18 @@
         return createCredential(null, null, simCred, null, null, null);
     }
 
+    /**
+     * Helper function for generating user credential for testing.
+     *
+     * @return {@link Credential}
+     */
     private static Credential createCredentialWithUserCredential() {
         Credential.UserCredential userCred = new Credential.UserCredential();
         userCred.username = "username";
         userCred.password = "password";
+        userCred.machineManaged = true;
+        userCred.ableToShare = true;
+        userCred.softTokenApp = "TestApp";
         userCred.eapType = EAPConstants.EAP_TTLS;
         userCred.nonEapInnerMethod = "MS-CHAP";
         return createCredential(userCred, null, null, FakeKeys.CA_CERT0,
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
index c707993..45fdbea 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
@@ -24,19 +24,71 @@
 
 import org.junit.Test;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.pps.HomeSP}.
  */
 @SmallTest
 public class HomeSPTest {
-    private static HomeSP createHomeSp() {
+
+    /**
+     * Helper function for creating a map of home network IDs for testing.
+     *
+     * @return Map of home network IDs
+     */
+    private static Map<String, Long> createHomeNetworkIds() {
+        Map<String, Long> homeNetworkIds = new HashMap<>();
+        homeNetworkIds.put("ssid", 0x1234L);
+        homeNetworkIds.put("nullhessid", null);
+        return homeNetworkIds;
+    }
+
+    /**
+     * Helper function for creating a HomeSP for testing.
+     *
+     * @param homeNetworkIds The map of home network IDs associated with HomeSP
+     * @return {@link HomeSP}
+     */
+    private static HomeSP createHomeSp(Map<String, Long> homeNetworkIds) {
         HomeSP homeSp = new HomeSP();
         homeSp.fqdn = "fqdn";
         homeSp.friendlyName = "friendly name";
+        homeSp.iconUrl = "icon.url";
+        homeSp.homeNetworkIds = homeNetworkIds;
+        homeSp.matchAllOIs = new long[] {0x11L, 0x22L};
+        homeSp.matchAnyOIs = new long[] {0x33L, 0x44L};
+        homeSp.otherHomePartners = new String[] {"partner1", "partner2"};
         homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
         return homeSp;
     }
 
+    /**
+     * Helper function for creating a HomeSP with home network IDs for testing.
+     *
+     * @return {@link HomeSP}
+     */
+    private static HomeSP createHomeSpWithHomeNetworkIds() {
+        return createHomeSp(createHomeNetworkIds());
+    }
+
+    /**
+     * Helper function for creating a HomeSP without home network IDs for testing.
+     *
+     * @return {@link HomeSP}
+     */
+    private static HomeSP createHomeSpWithoutHomeNetworkIds() {
+        return createHomeSp(null);
+    }
+
+    /**
+     * Helper function for verifying HomeSP after parcel write then read.
+     * @param writeHomeSp
+     * @throws Exception
+     */
     private static void verifyParcel(HomeSP writeHomeSp) throws Exception {
         Parcel parcel = Parcel.obtain();
         writeHomeSp.writeToParcel(parcel, 0);
@@ -57,13 +109,23 @@
     }
 
     /**
-     * Verify parcel read/write for a valid HomeSP.
+     * Verify parcel read/write for a HomeSP containing Home Network IDs.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithValidHomeSP() throws Exception {
-        verifyParcel(createHomeSp());
+    public void verifyParcelWithHomeNetworkIds() throws Exception {
+        verifyParcel(createHomeSpWithHomeNetworkIds());
+    }
+
+    /**
+     * Verify parcel read/write for a HomeSP without Home Network IDs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutHomeNetworkIds() throws Exception {
+        verifyParcel(createHomeSpWithoutHomeNetworkIds());
     }
 
     /**
@@ -120,6 +182,49 @@
     }
 
     /**
+     * Verify that a HomeSP is valid when the optional Home Network IDs are
+     * provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateHomeSpWithHomeNetworkIds() throws Exception {
+        HomeSP homeSp = createHomeSpWithHomeNetworkIds();
+        assertTrue(homeSp.validate());
+    }
+
+    /**
+     * Verify that a HomeSP is valid when the optional Home Network IDs are
+     * not provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateHomeSpWithoutHomeNetworkIds() throws Exception {
+        HomeSP homeSp = createHomeSpWithoutHomeNetworkIds();
+        assertTrue(homeSp.validate());
+    }
+
+    /**
+     * Verify that a HomeSP is invalid when the optional Home Network IDs
+     * contained an invalid SSID (exceeding maximum number of bytes).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateHomeSpWithInvalidHomeNetworkIds() throws Exception {
+        HomeSP homeSp = new HomeSP();
+        homeSp.fqdn = "fqdn";
+        homeSp.friendlyName = "friendly name";
+        homeSp.homeNetworkIds = new HashMap<>();
+        byte[] rawSsidBytes = new byte[33];
+        Arrays.fill(rawSsidBytes, (byte) 'a');
+        homeSp.homeNetworkIds.put(
+                StringFactory.newStringFromBytes(rawSsidBytes, StandardCharsets.UTF_8), 0x1234L);
+        assertFalse(homeSp.validate());
+    }
+
+    /**
      * Verify that copy constructor works when pass in a null source.
      *
      * @throws Exception
@@ -138,10 +243,7 @@
      */
     @Test
     public void validateCopyConstructorFromValidSource() throws Exception {
-        HomeSP sourceSp = new HomeSP();
-        sourceSp.fqdn = "fqdn";
-        sourceSp.friendlyName = "friendlyName";
-        sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+        HomeSP sourceSp = createHomeSpWithHomeNetworkIds();
         HomeSP copySp = new HomeSP(sourceSp);
         assertTrue(copySp.equals(sourceSp));
     }