Merge "Fix issue with PiP hiding when dragging divider."
diff --git a/Android.mk b/Android.mk
index 71b77d5..fbcea90 100644
--- a/Android.mk
+++ b/Android.mk
@@ -425,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 \
@@ -537,6 +539,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 d811020..05ced6e 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
@@ -3066,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);
   }
 
@@ -3103,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);
@@ -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";
@@ -5045,6 +5052,7 @@
     field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
     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";
@@ -5244,6 +5252,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 +5319,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);
@@ -6120,16 +6131,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 +6187,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 +6215,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 +6271,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 +6281,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 +6294,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";
@@ -8355,6 +8377,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 +8576,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 +8885,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 +9600,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 +9664,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 +9761,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;
@@ -9814,6 +9844,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;
@@ -10176,6 +10207,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";
@@ -10611,16 +10643,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
@@ -10686,7 +10718,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;
@@ -13662,6 +13694,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);
@@ -14161,9 +14210,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
@@ -14184,6 +14234,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();
@@ -14197,6 +14248,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
@@ -14274,6 +14326,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;
@@ -14305,6 +14368,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[]);
@@ -14434,7 +14500,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();
@@ -15075,10 +15141,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
@@ -29926,6 +29994,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>);
@@ -29970,6 +30039,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>);
@@ -30726,6 +30796,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);
@@ -33054,7 +33125,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;
@@ -33443,6 +33513,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";
@@ -33495,8 +33566,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";
@@ -35846,6 +35919,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";
   }
@@ -35861,6 +35935,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();
@@ -35879,8 +35954,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
@@ -35905,9 +35978,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
@@ -39211,6 +39284,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;
@@ -43912,6 +43986,7 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final deprecated java.lang.CharSequence getTooltip();
     method public final java.lang.CharSequence getTooltipText();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
@@ -44211,6 +44286,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final deprecated void setTooltip(java.lang.CharSequence);
     method public final void setTooltipText(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
@@ -45006,6 +45082,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();
@@ -45062,6 +45139,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);
@@ -45260,8 +45338,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
@@ -62021,6 +62101,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>);
@@ -62031,7 +62114,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);
@@ -62060,12 +62147,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 5cd33ba..4a1fd39 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -217,6 +217,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 +225,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 +519,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 +850,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
@@ -3183,8 +3187,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);
   }
 
@@ -3220,6 +3226,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);
@@ -5174,6 +5182,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";
@@ -5203,6 +5212,7 @@
     field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
     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";
@@ -5404,6 +5414,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 +5481,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);
@@ -6314,16 +6327,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 +6394,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 +6429,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 +6486,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 +6499,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 +6512,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";
@@ -8735,6 +8759,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);
@@ -8945,6 +8970,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);
@@ -9258,6 +9284,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";
@@ -9987,7 +10014,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
@@ -10048,6 +10078,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;
@@ -10145,6 +10176,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;
@@ -10262,6 +10294,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;
@@ -10656,6 +10689,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";
@@ -11167,16 +11201,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
@@ -11242,7 +11276,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;
@@ -14218,6 +14252,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);
@@ -14717,9 +14768,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
@@ -14740,6 +14792,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();
@@ -14755,6 +14808,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
@@ -14836,6 +14890,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;
@@ -14867,6 +14932,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[]);
@@ -14998,7 +15066,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();
@@ -15641,11 +15709,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
@@ -25187,12 +25257,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();
@@ -32630,6 +32710,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>);
@@ -32674,6 +32755,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>);
@@ -33547,6 +33629,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);
@@ -35931,7 +36014,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;
@@ -36422,6 +36504,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";
@@ -36477,8 +36560,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";
@@ -38370,6 +38455,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);
@@ -38838,6 +38935,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";
   }
@@ -38855,6 +38953,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();
@@ -38875,9 +38974,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
@@ -38902,9 +38999,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
@@ -42550,6 +42647,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);
@@ -47270,6 +47368,7 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final deprecated java.lang.CharSequence getTooltip();
     method public final java.lang.CharSequence getTooltipText();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
@@ -47569,6 +47668,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final deprecated void setTooltip(java.lang.CharSequence);
     method public final void setTooltipText(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
@@ -48364,6 +48464,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();
@@ -48420,6 +48521,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);
@@ -48619,9 +48721,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);
@@ -65743,6 +65847,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>);
@@ -65753,7 +65860,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);
@@ -65782,12 +65893,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 94b4a00..8f4b504 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
@@ -3066,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);
   }
 
@@ -3103,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);
@@ -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";
@@ -5055,6 +5062,7 @@
     field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
     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";
@@ -5254,6 +5262,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 +5329,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);
@@ -6137,16 +6148,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 +6208,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 +6237,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 +6293,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 +6303,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 +6316,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";
@@ -8378,6 +8400,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 +8600,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 +8910,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 +9625,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 +9689,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 +9788,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;
@@ -9841,6 +9871,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;
@@ -10206,6 +10237,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";
@@ -10643,16 +10675,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
@@ -10718,7 +10750,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;
@@ -13694,6 +13726,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);
@@ -14193,9 +14242,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
@@ -14216,6 +14266,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();
@@ -14229,6 +14280,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
@@ -14306,6 +14358,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;
@@ -14337,6 +14400,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[]);
@@ -14466,7 +14532,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();
@@ -15107,10 +15173,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
@@ -30037,6 +30105,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>);
@@ -30081,6 +30150,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>);
@@ -30839,6 +30909,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);
@@ -33170,7 +33241,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;
@@ -33559,6 +33629,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";
@@ -33612,8 +33683,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";
@@ -35499,6 +35572,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);
@@ -35967,6 +36052,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";
   }
@@ -35982,6 +36068,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();
@@ -36000,8 +36087,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
@@ -36026,9 +36111,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
@@ -39332,6 +39417,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;
@@ -44203,6 +44289,7 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final deprecated java.lang.CharSequence getTooltip();
     method public final java.lang.CharSequence getTooltipText();
     method public android.view.View getTooltipView();
     method public final int getTop();
@@ -44503,6 +44590,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final deprecated void setTooltip(java.lang.CharSequence);
     method public final void setTooltipText(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
@@ -45302,6 +45390,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();
@@ -45358,6 +45447,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);
@@ -45556,8 +45646,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
@@ -62334,6 +62426,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>);
@@ -62344,7 +62439,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);
@@ -62373,12 +62472,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/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 8f169c8..df4c4af 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6792,6 +6792,8 @@
         }
         mWindowManager = mWindow.getWindowManager();
         mCurrentConfig = config;
+
+        mWindow.setColorMode(info.colorMode);
     }
 
     /** @hide */
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/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 d674bfe..740af9c 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -75,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);
@@ -86,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);
@@ -100,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 82917d2..3270349 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -972,6 +972,15 @@
     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: the user that built the notification.
      *
      * @hide
@@ -1022,6 +1031,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.
@@ -1766,6 +1776,7 @@
         if (parcel.readInt() != 0) {
             mChannelId = parcel.readString();
         }
+        mTimeout = parcel.readLong();
     }
 
     @Override
@@ -1872,6 +1883,7 @@
         that.color = this.color;
 
         that.mChannelId = this.mChannelId;
+        that.mTimeout = this.mTimeout;
 
         if (!heavy) {
             that.lightenPayload(); // will clean out extras
@@ -2128,6 +2140,7 @@
         } else {
             parcel.writeInt(0);
         }
+        parcel.writeLong(mTimeout);
     }
 
     /**
@@ -2325,6 +2338,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.
@@ -2532,6 +2552,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
@@ -4789,6 +4818,7 @@
         CharSequence mUserDisplayName;
         CharSequence mConversationTitle;
         List<Message> mMessages = new ArrayList<>();
+        List<Message> mHistoricMessages = new ArrayList<>();
 
         MessagingStyle() {
         }
@@ -4845,15 +4875,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 +4896,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 +4924,13 @@
         }
 
         /**
+         * Gets the list of historic {@code Message}s in the notification.
+         */
+        public List<Message> getHistoricMessages() {
+            return mHistoricMessages;
+        }
+
+        /**
          * @hide
          */
         @Override
@@ -4887,6 +4945,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 +4987,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);
             }
         }
 
@@ -5015,6 +5081,21 @@
 
             int contractedChildId = View.NO_ID;
             Message contractedMessage = findLatestIncomingMessage();
+            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.setTextViewText(rowId, makeMessageLine(m));
+
+                if (contractedMessage == m) {
+                    contractedChildId = rowId;
+                }
+
+                i++;
+            }
+
             int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
             while (firstMessage + i < mMessages.size() && i < rowIds.length) {
                 Message m = mMessages.get(firstMessage + i);
@@ -5029,6 +5110,14 @@
 
                 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);
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..72daade 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1132,6 +1132,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 +1211,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 +3310,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 +3322,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 +3340,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 +3382,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 +3411,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 +3432,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 +3464,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 +3497,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 +3511,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 +3533,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 +3575,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 +3593,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 +3620,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 +4601,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 +4613,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 +4639,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 +4865,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 +4886,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 +4919,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 +4941,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 +4969,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 +4979,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 +5528,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 +5554,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 +5728,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 +5740,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 +5749,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 +5827,28 @@
     }
 
     /**
-     * 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.
+     * 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 +5857,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 +5882,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 +5905,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 +6200,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 +6504,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 +6521,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 +6536,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 +6556,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 +6576,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 +6611,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..7604af8 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,15 @@
     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);
+    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 +227,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 +271,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/content/Context.java b/core/java/android/content/Context.java
index 38e6fbe..f00f605 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4253,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 90f08cd..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.
      */
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 4a94688..92cb709 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -224,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
@@ -577,7 +615,7 @@
                     CONFIG_SMALLEST_SCREEN_SIZE,
                     CONFIG_DENSITY,
                     CONFIG_LAYOUT_DIRECTION,
-                    CONFIG_COLORIMETRY,
+                    CONFIG_COLOR_MODE,
                     CONFIG_FONT_SCALE,
             })
     @Retention(RetentionPolicy.SOURCE)
@@ -686,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}
@@ -724,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
     };
 
     /**
@@ -781,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;
@@ -884,6 +922,7 @@
         resizeMode = orig.resizeMode;
         requestedVrComponent = orig.requestedVrComponent;
         rotationAnimation = orig.rotationAnimation;
+        colorMode = orig.colorMode;
     }
 
     /**
@@ -1071,6 +1110,7 @@
         dest.writeInt(resizeMode);
         dest.writeString(requestedVrComponent);
         dest.writeInt(rotationAnimation);
+        dest.writeInt(colorMode);
     }
 
     public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -1106,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 b091d7e..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;
 
@@ -72,11 +78,6 @@
      */
     public boolean directBootAware = false;
 
-    /**
-     * The name of the split that contains the code for this component.
-     */
-    public String splitName;
-
     /** @removed */
     @Deprecated
     public boolean encryptionAware = false;
@@ -88,6 +89,7 @@
         super(orig);
         applicationInfo = orig.applicationInfo;
         processName = orig.processName;
+        splitName = orig.splitName;
         descriptionRes = orig.descriptionRes;
         enabled = orig.enabled;
         exported = orig.exported;
@@ -168,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) {
@@ -200,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);
@@ -213,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/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 43ebf46..8223726 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -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;
@@ -3657,6 +3732,8 @@
                 continue;
             }
 
+            ComponentInfo parsedComponent = null;
+
             String tagName = parser.getName();
             if (tagName.equals("activity")) {
                 Activity a = parseActivity(owner, res, parser, flags, outError, false,
@@ -3667,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);
@@ -3676,6 +3754,7 @@
                 }
 
                 owner.receivers.add(a);
+                parsedComponent = a.info;
 
             } else if (tagName.equals("service")) {
                 Service s = parseService(owner, res, parser, flags, outError);
@@ -3685,6 +3764,7 @@
                 }
 
                 owner.services.add(s);
+                parsedComponent = s.info;
 
             } else if (tagName.equals("provider")) {
                 Provider p = parseProvider(owner, res, parser, flags, outError);
@@ -3694,6 +3774,7 @@
                 }
 
                 owner.providers.add(p);
+                parsedComponent = p.info;
 
             } else if (tagName.equals("activity-alias")) {
                 Activity a = parseActivityAlias(owner, res, parser, flags, outError);
@@ -3703,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
@@ -3769,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;
@@ -4026,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;
@@ -5036,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/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/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..2d5c4ce 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,58 @@
     }
 
     /**
-     * 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.");
+        if (!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 +396,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 +404,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 +414,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 +428,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 +512,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 +540,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 +562,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 +592,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/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/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 0aef532..2bf841c 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -106,7 +106,7 @@
      * @return
      */
     public LogMaker addTaggedData(int tag, Object value) {
-        if (isValidValue(value)) {
+        if (!isValidValue(value)) {
             throw new IllegalArgumentException(
                     "Value must be loggable type - int, long, float, String");
         }
@@ -119,10 +119,14 @@
     }
 
     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) {
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/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/Settings.java b/core/java/android/provider/Settings.java
index e3da337..b92d293 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;
@@ -1274,6 +1275,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 +1283,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 +1321,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.
@@ -9514,10 +9544,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,
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/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/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 d930689..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.
      *
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/View.java b/core/java/android/view/View.java
index 3bd67c7..547f7d8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24617,6 +24617,16 @@
     }
 
     /**
+     * To be removed once the support library has stopped using it.
+     *
+     * @deprecated use {@link #setTooltipText} instead
+     */
+    @Deprecated
+    public final void setTooltip(@Nullable CharSequence tooltipText) {
+        setTooltipText(tooltipText);
+    }
+
+    /**
      * Returns the view's tooltip text.
      *
      * @return the tooltip text
@@ -24626,6 +24636,17 @@
         return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null;
     }
 
+    /**
+     * To be removed once the support library has stopped using it.
+     *
+     * @deprecated use {@link #getTooltipText} instead
+     */
+    @Deprecated
+    @Nullable
+    public final CharSequence getTooltip() {
+        return getTooltipText();
+    }
+
     private boolean showTooltip(int x, int y, boolean fromLongClick) {
         if (mAttachInfo == null) {
             return false;
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/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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6d48862..094e2b8 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                           -->
@@ -2645,6 +2647,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/values/attrs.xml b/core/res/res/values/attrs.xml
index 0789241..c333629 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5839,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. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 64a64f7..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
@@ -1267,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
@@ -2043,6 +2050,16 @@
         <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
@@ -2453,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/config.xml b/core/res/res/values/config.xml
index c36279c..6a8b556 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2625,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>
@@ -2712,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/public.xml b/core/res/res/values/public.xml
index 66dd1274..1146871 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2788,6 +2788,8 @@
         <public name="restartOnConfigChanges" />
         <public name="certDigest" />
         <public name="splitName" />
+        <public name="colorMode" />
+        <public name="isolatedSplits" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 16356c7..58c925f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1136,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" />
@@ -2799,6 +2801,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/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
index 0f75cb6..35d8d93 100644
--- a/core/tests/coretests/src/android/metrics/LogMakerTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -100,6 +100,7 @@
         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]);
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/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/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 4e863e3..38e8061 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -237,6 +237,7 @@
                 IoUtils.closeQuietly(fd);
             }
         }
+        fontFamily.freeze();
         callback.onTypefaceRetrieved(Typeface.createFromFamiliesWithDefault(
                 new FontFamily[] {fontFamily}));
     }
@@ -373,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();
                 }
             }
         }
@@ -422,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);
@@ -492,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/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/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/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/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/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/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/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/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/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/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/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/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/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_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/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/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/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/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/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/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/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index ff2febf..58f01c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -44,11 +44,16 @@
                 && ((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;
+            // 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.
+            int childCount = messagingContainer.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = messagingContainer.getChildAt(i);
+                if (child.getId() == messagingContainer.getContractedChildId()) {
+                    mContractedMessage = child;
+                } else if (child.getVisibility() == View.VISIBLE) {
+                    break;
+                }
             }
         }
     }
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 fdf7296..8dcc693 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3583,7 +3583,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);
         });
     }
 
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..a706408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -157,7 +157,7 @@
         // 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);
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 aa29e43..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;
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/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/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/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 341438d..a474759 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3331,6 +3331,14 @@
     // 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;
+
     // ---- End O Constants, all O constants go above this line ----
 
     // Add new aosp constants above this line.
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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f6fbaf9..a5742c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7625,12 +7625,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 +7677,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 {
@@ -10221,7 +10219,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.");
@@ -12044,14 +12044,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);
@@ -17755,6 +17753,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) {
@@ -19593,7 +19592,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);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 7bfea9a..98b5835c 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -80,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;
@@ -114,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;
@@ -128,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;
@@ -240,6 +243,7 @@
 
     final ActivityManagerService mService;
     private final WindowManagerService mWindowManager;
+    private StackWindowController mWindowContainerController;
     private final RecentTasks mRecentTasks;
 
     /**
@@ -328,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;
@@ -441,7 +445,7 @@
     }
 
     ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
-            RecentTasks recentTasks) {
+            RecentTasks recentTasks, boolean onTop) {
         mActivityContainer = activityContainer;
         mStackSupervisor = activityContainer.getOuter();
         mService = mStackSupervisor.mService;
@@ -452,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);
     }
 
     /**
@@ -467,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);
@@ -478,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.
@@ -487,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.
      */
@@ -518,7 +525,8 @@
     void remove() {
         removeFromDisplay();
         mStackSupervisor.deleteActivityContainerRecord(mStackId);
-        mWindowManager.removeStack(mStackId);
+        mWindowContainerController.removeContainer();
+        mWindowContainerController = null;
         onParentChanged();
     }
 
@@ -526,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()}.
@@ -546,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);
         }
@@ -765,7 +809,8 @@
 
         task = topTask();
         if (task != null) {
-            task.moveWindowContainerToTop(true /* includingParents */);
+            mWindowContainerController.positionChildAtTop(task.getWindowContainerController(),
+                    true /* includingParents */);
         }
     }
 
@@ -784,7 +829,7 @@
             mTaskHistory.remove(task);
             mTaskHistory.add(0, task);
             updateTaskMovement(task, false);
-            task.moveWindowContainerToBottom();
+            mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
         }
     }
 
@@ -2578,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);
     }
 
@@ -2590,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) {
@@ -2618,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
@@ -2868,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
@@ -3004,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
@@ -4373,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())) {
@@ -4436,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();
@@ -4454,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);
                 }
@@ -4469,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);
     }
 
 
@@ -4897,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 24ff1d7..29032f8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -376,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<>();
@@ -2198,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);
@@ -2236,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) {
@@ -2340,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.
@@ -2358,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);
@@ -2394,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();
@@ -2404,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;
     }
 
@@ -2770,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) {
@@ -2839,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;
 
@@ -2849,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.
@@ -2891,7 +2879,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        mWindowManager.animateResizePinnedStack(bounds, -1);
+        stack.animateResizePinnedStack(bounds, -1);
         mService.mTaskChangeNotificationController.notifyActivityPinned();
     }
 
@@ -3356,7 +3344,7 @@
                 stack.switchUserLocked(userId);
                 TaskRecord task = stack.topTask();
                 if (task != null) {
-                    task.moveWindowContainerToTop(true /* includingParents */);
+                    stack.positionChildWindowContainerAtTop(task);
                 }
             }
         }
@@ -3794,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;
@@ -4320,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;
 
@@ -4334,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);
             }
@@ -4345,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
@@ -4369,7 +4355,7 @@
                 if (activityDisplay == null) {
                     return;
                 }
-                addToDisplayLocked(activityDisplay, true /* onTop */);
+                addToDisplayLocked(activityDisplay);
             }
         }
 
@@ -4446,7 +4432,7 @@
             if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
                     + " current displayId=" + mActivityDisplay.mDisplayId);
 
-            mActivityDisplay.detachActivitiesLocked(mStack);
+            mActivityDisplay.detachStack(mStack);
             mActivityDisplay = null;
         }
 
@@ -4462,8 +4448,7 @@
             removeFromDisplayLocked();
 
             mActivityDisplay = activityDisplay;
-            mStack.moveToDisplay(activityDisplay);
-            activityDisplay.attachActivities(mStack, ON_TOP);
+            mStack.reparent(activityDisplay, ON_TOP);
         }
 
         @Override
@@ -4553,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;
@@ -4585,7 +4571,7 @@
                         new VirtualActivityDisplay(width, height, density);
                 mActivityDisplay = virtualActivityDisplay;
                 mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
-                addToDisplayLocked(virtualActivityDisplay, true /* onTop */);
+                addToDisplayLocked(virtualActivityDisplay);
             }
 
             if (mSurface != null) {
@@ -4674,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 {
@@ -4685,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);
         }
@@ -4764,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 8d32778..b913a23 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1835,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);
     }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 2373ea8..f12d7b7 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -421,9 +421,10 @@
 
         final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
         final Configuration overrideConfig = getOverrideConfiguration();
-        mWindowContainerController = new TaskWindowContainerController(taskId, this, getStackId(),
-                userId, bounds, overrideConfig, mResizeMode, mSupportsPictureInPicture,
-                isHomeTask(), isOnTopLauncher(), onTop, showForAllUsers, lastTaskDescription);
+        mWindowContainerController = new TaskWindowContainerController(taskId, this,
+                getStack().getWindowContainerController(), userId, bounds, overrideConfig,
+                mResizeMode, mSupportsPictureInPicture, isHomeTask(), isOnTopLauncher(),
+                onTop, showForAllUsers, lastTaskDescription);
     }
 
     void removeWindowContainer() {
@@ -457,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.");
@@ -527,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);
     }
@@ -563,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);
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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 96459be..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(
@@ -1683,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
@@ -1944,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);
@@ -2039,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();
@@ -2975,9 +3031,6 @@
 
         // setup local book-keeping
         final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
-        synchronized (mNotificationLock) {
-            mEnqueuedNotifications.add(r);
-        }
         mHandler.post(new EnqueueNotificationRunnable(userId, r));
 
         idOut[0] = id;
@@ -2995,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) {
@@ -3224,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;
@@ -3982,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
@@ -4002,8 +4074,6 @@
                         if (snoozeCriterionId != null) {
                             mNotificationAssistants.notifyAssistantSnoozedLocked(r.sbn,
                                     snoozeCriterionId);
-                        }
-                        if (until == SNOOZE_UNTIL_UNSPECIFIED) {
                             mSnoozeHelper.snooze(r);
                         } else {
                             mSnoozeHelper.snooze(r, until);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 2a5a25f..8998128 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -41,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;
@@ -318,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;
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/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 50309be..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;
@@ -236,8 +237,8 @@
                     // throw new SecurityException(message + " for another profile " + targetUserId);
 
                     // TODO: Report caller package name.
-                    Slog.wtfStack(TAG, message + " for another profile " + targetUserId
-                            + " from " + callingUserId);
+                    Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile "
+                            + targetUserId + " from " + callingUserId);
                 }
 
                 UserInfo targetUserInfo = mUm.getUserInfo(targetUserId);
@@ -286,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),
@@ -296,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;
             }
@@ -316,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;
             }
@@ -363,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;
             }
@@ -384,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;
             }
@@ -408,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)) {
@@ -484,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 "
@@ -535,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;
             }
@@ -556,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);
             }
@@ -609,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 49b96b0..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) {
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/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 c5ed6ec..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;
@@ -567,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 fbc3389..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,43 +45,38 @@
 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,
-            boolean supportsPictureInPicture, boolean homeTask, boolean isOnTopLauncher,
-            boolean toTop, boolean showForAllUsers, TaskDescription taskDescription) {
-        super(listener, WindowManagerService.getInstance());
+            StackWindowController stackController, int userId, Rect bounds,
+            Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
+            boolean homeTask, boolean isOnTopLauncher, boolean toTop, boolean showForAllUsers,
+            TaskDescription taskDescription) {
+        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,
                     supportsPictureInPicture, homeTask, isOnTopLauncher, taskDescription);
             final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
@@ -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 0a56323..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();
                             }
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..7558e3c 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 {
@@ -8891,10 +9170,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 +9195,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 +9241,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 +9406,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 +9434,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/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 92d9810..250aab8 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -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/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
index b7931d4..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())
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..9316516
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockStorageManager.java
@@ -0,0 +1,475 @@
+/*
+ * 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 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 ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void fstrim(int flags) 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..5d4c3cf 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());
         }
     }
 
@@ -2358,6 +2473,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 +2519,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 +3345,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 +3533,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/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 847f34d..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,9 +178,13 @@
 
     /** 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}. */
@@ -235,6 +244,8 @@
 
         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,
@@ -258,6 +269,16 @@
             super.onDisplayChanged(dc);
             mOnDisplayChangedCalled = true;
         }
+
+        @Override
+        boolean isAnimating() {
+            return mUseLocalIsAnimating ? mIsAnimating : super.isAnimating();
+        }
+
+        void setLocalIsAnimating(boolean isAnimating) {
+            mUseLocalIsAnimating = true;
+            mIsAnimating = isAnimating;
+        }
     }
 
     /**
@@ -267,15 +288,25 @@
     class TestTaskWindowContainerController extends TaskWindowContainerController {
 
         TestTaskWindowContainerController() {
-            this(createTaskStackOnDisplay(sDisplayContent).mStackId);
+            this(createStackControllerOnDisplay(sDisplayContent));
         }
 
-        TestTaskWindowContainerController(int stackId) {
-            super(sNextTaskId++, snapshot -> {}, stackId, 0 /* userId */, null /* bounds */,
+        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
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/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));
     }