Merge changes I50d2903e,Ieb347ce3

* changes:
  Add DPMS delegation scopes.
  Add block uninstall delegation in DPMS.
diff --git a/api/current.txt b/api/current.txt
index 9538253..faee5aa 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -739,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
@@ -3067,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);
   }
 
@@ -3104,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);
@@ -5017,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";
@@ -5245,6 +5251,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);
@@ -8367,6 +8374,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;
@@ -8565,6 +8573,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;
@@ -9749,6 +9758,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;
@@ -9831,6 +9841,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;
@@ -14196,9 +14207,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
@@ -29979,6 +29991,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>);
@@ -30023,6 +30036,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>);
@@ -35958,9 +35972,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
@@ -39264,6 +39278,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;
diff --git a/api/system-current.txt b/api/system-current.txt
index c649c23..510fb70 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -850,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
@@ -3186,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);
   }
 
@@ -3223,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);
@@ -5177,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";
@@ -5407,6 +5413,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);
@@ -8749,6 +8756,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);
@@ -8959,6 +8967,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);
@@ -10164,6 +10173,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;
@@ -10281,6 +10291,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;
@@ -14754,9 +14765,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
@@ -32695,6 +32707,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>);
@@ -32739,6 +32752,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>);
@@ -38979,9 +38993,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
@@ -42627,6 +42641,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);
diff --git a/api/test-current.txt b/api/test-current.txt
index ab421ce..d16be73 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -739,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
@@ -3067,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);
   }
 
@@ -3104,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);
@@ -5027,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";
@@ -5255,6 +5261,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);
@@ -8390,6 +8397,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;
@@ -8589,6 +8597,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;
@@ -9776,6 +9785,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;
@@ -9858,6 +9868,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;
@@ -14228,9 +14239,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
@@ -30090,6 +30102,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>);
@@ -30134,6 +30147,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>);
@@ -36091,9 +36105,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
@@ -39397,6 +39411,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;
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/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/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..4172ed7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1022,6 +1022,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 +1767,7 @@
         if (parcel.readInt() != 0) {
             mChannelId = parcel.readString();
         }
+        mTimeout = parcel.readLong();
     }
 
     @Override
@@ -1872,6 +1874,7 @@
         that.color = this.color;
 
         that.mChannelId = this.mChannelId;
+        that.mTimeout = this.mTimeout;
 
         if (!heavy) {
             that.lightenPayload(); // will clean out extras
@@ -2128,6 +2131,7 @@
         } else {
             parcel.writeInt(0);
         }
+        parcel.writeLong(mTimeout);
     }
 
     /**
@@ -2325,6 +2329,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 +2543,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
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/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/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/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index cd51bce..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;
@@ -5039,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/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/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/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/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 517b305..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.
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d1227eb..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" />
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_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 113ace3..0dde91b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1001,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
@@ -1266,6 +1273,7 @@
         <attr name="sharedUserId" />
         <attr name="sharedUserLabel" />
         <attr name="installLocation" />
+        <attr name="isolatedSplits" />
     </declare-styleable>
 
     <!-- The <code>application</code> tag describes application-level components
@@ -2462,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/public.xml b/core/res/res/values/public.xml
index e387650..1146871 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2789,6 +2789,7 @@
         <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/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/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/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/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/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/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/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 341438d..036b6c2 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3331,6 +3331,11 @@
     // 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;
+
     // ---- 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8b53b97..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(
@@ -3011,6 +3049,7 @@
         public void run() {
             synchronized (mNotificationLock) {
                 mEnqueuedNotifications.add(r);
+                scheduleTimeoutLocked(r);
 
                 if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) {
                     // TODO: log to event log
@@ -3241,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;
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/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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3082e8b..7558e3c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1630,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);
         }
@@ -5148,7 +5153,7 @@
         }
     }
 
-    private void wipeDataNoLock(boolean wipeExtRequested, String reason, boolean force) {
+    private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) {
         wtfIfInLock();
 
         if (wipeExtRequested) {
@@ -5157,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);
@@ -5384,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(
@@ -5410,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);
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 3ceab6f..5d4c3cf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -85,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;
 
 /**
@@ -3344,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";
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 5c4a658..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;
 
@@ -158,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;
@@ -267,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;
@@ -312,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/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/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;