Merge "Update Emoji.java to use ICU"
diff --git a/Android.mk b/Android.mk
index 0e5dfed..03b2533 100644
--- a/Android.mk
+++ b/Android.mk
@@ -944,27 +944,8 @@
 		-werror -hide 111 -hide 113 \
 		-overview $(LOCAL_PATH)/core/java/overview.html
 
-SUPPORT_API_DIR := ./frameworks/support/api
-
-# More API Level information for the Support Library, which is currently
-# included as part of the core framework docs build.
-framework_docs_LOCAL_DROIDDOC_OPTIONS += \
-    -since $(SUPPORT_API_DIR)/22.0.0.txt 22.0.0 \
-    -since $(SUPPORT_API_DIR)/22.0.0.txt 22.0.0 \
-    -since $(SUPPORT_API_DIR)/22.1.0.txt 22.1.0 \
-    -since $(SUPPORT_API_DIR)/22.2.0.txt 22.2.0 \
-    -since $(SUPPORT_API_DIR)/22.2.1.txt 22.2.1 \
-    -since $(SUPPORT_API_DIR)/23.0.0.txt 23.0.0 \
-    -since $(SUPPORT_API_DIR)/23.1.0.txt 23.1.0 \
-    -since $(SUPPORT_API_DIR)/23.1.1.txt 23.1.1 \
-    -since $(SUPPORT_API_DIR)/23.2.0.txt 23.2.0 \
-    -since $(SUPPORT_API_DIR)/23.2.1.txt 23.2.1 \
-    -since $(SUPPORT_API_DIR)/23.4.0.txt 23.4.0 \
-    -since $(SUPPORT_API_DIR)/24.0.0.txt 24.0.0 \
-    -since $(SUPPORT_API_DIR)/24.1.0.txt 24.1.0 \
-    -since $(SUPPORT_API_DIR)/24.2.0.txt 24.2.0 \
-    -since $(SUPPORT_API_DIR)/25.0.0.txt 25.0.0 \
-    -since $(SUPPORT_API_DIR)/25.1.0.txt 25.1.0
+# Allow the support library to add its own droiddoc options.
+include $(LOCAL_PATH)/../support/droiddoc.mk
 
 framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR:= \
 	$(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON)
@@ -1472,10 +1453,6 @@
 LOCAL_SRC_FILES := \
     $(call all-proto-files-under, core/proto) \
     $(call all-proto-files-under, libs/incident/proto)
-LOCAL_C_INCLUDES := \
-    $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
-LOCAL_EXPORT_C_INCLUDES := \
-    $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
 include $(BUILD_STATIC_LIBRARY)
 
 # ====  c++ proto host library  ==============================
@@ -1490,10 +1467,6 @@
 LOCAL_SRC_FILES := \
     $(call all-proto-files-under, core/proto) \
     $(call all-proto-files-under, libs/incident/proto)
-LOCAL_C_INCLUDES := \
-    $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
-LOCAL_EXPORT_C_INCLUDES := \
-    $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
 include $(BUILD_HOST_SHARED_LIBRARY)
 
 
diff --git a/api/current.txt b/api/current.txt
index 2276012..aab29be 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -746,6 +746,7 @@
     field public static final int isAsciiCapable = 16843753; // 0x10103e9
     field public static final int isAuxiliary = 16843647; // 0x101037f
     field public static final int isDefault = 16843297; // 0x1010221
+    field public static final int isFeatureSplit = 16844126; // 0x101055e
     field public static final int isGame = 16843764; // 0x10103f4
     field public static final int isIndicator = 16843079; // 0x1010147
     field public static final int isModifier = 16843334; // 0x1010246
@@ -7937,7 +7938,7 @@
   }
 
   public final class AdvertisingSet {
-    method public void enableAdvertising(boolean);
+    method public void enableAdvertising(boolean, int);
     method public void periodicAdvertisingEnable(boolean);
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
@@ -7970,7 +7971,6 @@
     method public int getInterval();
     method public int getPrimaryPhy();
     method public int getSecondaryPhy();
-    method public int getTimeout();
     method public int getTxPowerLevel();
     method public boolean includeTxPower();
     method public boolean isAnonymous();
@@ -8004,7 +8004,6 @@
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setTimeout(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
   }
 
@@ -8013,6 +8012,8 @@
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
     method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
     method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
     method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
     method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 00fe0b6..0f42a54 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -859,6 +859,7 @@
     field public static final int isAsciiCapable = 16843753; // 0x10103e9
     field public static final int isAuxiliary = 16843647; // 0x101037f
     field public static final int isDefault = 16843297; // 0x1010221
+    field public static final int isFeatureSplit = 16844126; // 0x101055e
     field public static final int isGame = 16843764; // 0x10103f4
     field public static final int isIndicator = 16843079; // 0x1010147
     field public static final int isModifier = 16843334; // 0x1010246
@@ -8412,7 +8413,7 @@
   }
 
   public final class AdvertisingSet {
-    method public void enableAdvertising(boolean);
+    method public void enableAdvertising(boolean, int);
     method public void periodicAdvertisingEnable(boolean);
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
@@ -8445,7 +8446,6 @@
     method public int getInterval();
     method public int getPrimaryPhy();
     method public int getSecondaryPhy();
-    method public int getTimeout();
     method public int getTxPowerLevel();
     method public boolean includeTxPower();
     method public boolean isAnonymous();
@@ -8479,7 +8479,6 @@
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setTimeout(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
   }
 
@@ -8488,6 +8487,8 @@
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
     method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
     method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
     method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
     method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
   }
@@ -34433,6 +34434,7 @@
     method public deprecated void setUserRestrictions(android.os.Bundle);
     method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
     method public static boolean supportsMultipleUsers();
+    field public static final java.lang.String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
     field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
diff --git a/api/test-current.txt b/api/test-current.txt
index dd25eb7..f91bbb9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -746,6 +746,7 @@
     field public static final int isAsciiCapable = 16843753; // 0x10103e9
     field public static final int isAuxiliary = 16843647; // 0x101037f
     field public static final int isDefault = 16843297; // 0x1010221
+    field public static final int isFeatureSplit = 16844126; // 0x101055e
     field public static final int isGame = 16843764; // 0x10103f4
     field public static final int isIndicator = 16843079; // 0x1010147
     field public static final int isModifier = 16843334; // 0x1010246
@@ -7964,7 +7965,7 @@
   }
 
   public final class AdvertisingSet {
-    method public void enableAdvertising(boolean);
+    method public void enableAdvertising(boolean, int);
     method public void periodicAdvertisingEnable(boolean);
     method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
     method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
@@ -7997,7 +7998,6 @@
     method public int getInterval();
     method public int getPrimaryPhy();
     method public int getSecondaryPhy();
-    method public int getTimeout();
     method public int getTxPowerLevel();
     method public boolean includeTxPower();
     method public boolean isAnonymous();
@@ -8031,7 +8031,6 @@
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
-    method public android.bluetooth.le.AdvertisingSetParameters.Builder setTimeout(int);
     method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
   }
 
@@ -8040,6 +8039,8 @@
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
     method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
     method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
     method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
     method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
   }
@@ -31735,6 +31736,7 @@
     method public deprecated void setUserRestrictions(android.os.Bundle);
     method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
     method public static boolean supportsMultipleUsers();
+    field public static final java.lang.String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
     field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
@@ -35019,6 +35021,7 @@
     field public static final java.lang.String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
     field public static final java.lang.String SETTINGS_CLASSNAME = "settings_classname";
     field public static final java.lang.String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
+    field public static final java.lang.String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
     field public static final java.lang.String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
     field public static final deprecated java.lang.String TTS_DEFAULT_COUNTRY = "tts_default_country";
     field public static final deprecated java.lang.String TTS_DEFAULT_LANG = "tts_default_lang";
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 91520f1..827a77f 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -411,7 +411,8 @@
             if (file.isFile()) {
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
+                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+                            null, null);
                     params.sessionParams.setSize(
                             PackageHelper.calculateInstalledSize(pkgLite, false,
                             params.sessionParams.abiOverride));
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ede9281..8a3d9b1 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -156,13 +156,13 @@
     @GuardedBy("ContextImpl.class")
     private ArrayMap<String, File> mSharedPrefsPaths;
 
-    final ActivityThread mMainThread;
-    final LoadedApk mPackageInfo;
-    private ClassLoader mClassLoader;
+    final @NonNull ActivityThread mMainThread;
+    final @NonNull LoadedApk mPackageInfo;
+    private @Nullable ClassLoader mClassLoader;
 
-    private final IBinder mActivityToken;
+    private final @Nullable IBinder mActivityToken;
 
-    private final UserHandle mUser;
+    private final @Nullable UserHandle mUser;
 
     private final ApplicationContentResolver mContentResolver;
 
@@ -181,6 +181,9 @@
     private PackageManager mPackageManager;
     private Context mReceiverRestrictedContext = null;
 
+    // The name of the split this Context is representing. May be null.
+    private @Nullable String mSplitName = null;
+
     private final Object mSync = new Object();
 
     @GuardedBy("mSync")
@@ -1914,17 +1917,25 @@
         }
     }
 
-    private static Resources createResources(IBinder activityToken, LoadedApk pi, int displayId,
-            Configuration overrideConfig, CompatibilityInfo compatInfo) {
+    private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
+            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
+        final String[] splitResDirs;
+        final ClassLoader classLoader;
+        try {
+            splitResDirs = pi.getSplitPaths(splitName);
+            classLoader = pi.getSplitClassLoader(splitName);
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
         return ResourcesManager.getInstance().getResources(activityToken,
                 pi.getResDir(),
-                pi.getSplitResDirs(),
+                splitResDirs,
                 pi.getOverlayDirs(),
                 pi.getApplicationInfo().sharedLibraryFiles,
                 displayId,
                 overrideConfig,
                 compatInfo,
-                pi.getClassLoader());
+                classLoader);
     }
 
     @Override
@@ -1933,14 +1944,13 @@
         LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
                 flags | CONTEXT_REGISTER_PACKAGE);
         if (pi != null) {
-            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
-                    new UserHandle(UserHandle.getUserId(application.uid)), flags,
-                    null);
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,
+                    new UserHandle(UserHandle.getUserId(application.uid)), flags, null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
 
-            c.setResources(createResources(mActivityToken, pi, displayId, null,
+            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo()));
             if (c.mResources != null) {
                 return c;
@@ -1964,20 +1974,20 @@
         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,
-                    null);
+            return new ContextImpl(this, mMainThread, mPackageInfo, null, 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,
-                    null);
+            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,
+                    flags, null);
 
             final int displayId = mDisplay != null
                     ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
 
-            c.setResources(createResources(mActivityToken, pi, displayId, null,
+            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                     getDisplayAdjustments(displayId).getCompatibilityInfo()));
             if (c.mResources != null) {
                 return c;
@@ -1999,7 +2009,7 @@
         final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName);
         final String[] paths = mPackageInfo.getSplitPaths(splitName);
 
-        final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo,
+        final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName,
                 mActivityToken, mUser, mFlags, classLoader);
 
         final int displayId = mDisplay != null
@@ -2024,11 +2034,11 @@
             throw new IllegalArgumentException("overrideConfiguration must not be null");
         }
 
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags, mClassLoader);
+        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
+                mActivityToken, mUser, mFlags, mClassLoader);
 
         final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
-        context.setResources(createResources(mActivityToken, mPackageInfo, displayId,
+        context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
         return context;
     }
@@ -2039,12 +2049,12 @@
             throw new IllegalArgumentException("display must not be null");
         }
 
-        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
-                mUser, mFlags, mClassLoader);
+        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
+                mActivityToken, mUser, mFlags, mClassLoader);
 
         final int displayId = display.getDisplayId();
-        context.setResources(createResources(mActivityToken, mPackageInfo, displayId, null,
-                getDisplayAdjustments(displayId).getCompatibilityInfo()));
+        context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
+                null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
         context.mDisplay = display;
         return context;
     }
@@ -2053,16 +2063,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,
-                mClassLoader);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, 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,
-                mClassLoader);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser,
+                flags, mClassLoader);
     }
 
     @Override
@@ -2149,7 +2159,7 @@
 
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                 null);
         context.setResources(packageInfo.getResources());
         context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
@@ -2159,7 +2169,7 @@
 
     static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
         if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, 0,
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                 null);
         context.setResources(packageInfo.getResources());
         return context;
@@ -2186,8 +2196,8 @@
             }
         }
 
-        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityToken, null,
-                0, classLoader);
+        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
+                activityToken, null, 0, classLoader);
 
         // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
         displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
@@ -2214,9 +2224,10 @@
         return context;
     }
 
-    private ContextImpl(ContextImpl container, ActivityThread mainThread,
-            LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
-            ClassLoader classLoader) {
+    private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
+            @NonNull LoadedApk packageInfo, @Nullable String splitName,
+            @Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
+            @Nullable ClassLoader classLoader) {
         mOuterContext = this;
 
         // If creator didn't specify which storage to use, use the default
@@ -2241,6 +2252,7 @@
         mUser = user;
 
         mPackageInfo = packageInfo;
+        mSplitName = splitName;
         mClassLoader = classLoader;
         mResourcesManager = ResourcesManager.getInstance();
 
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index cf41e4e..dbed1be 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -27,7 +27,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.split.SplitDependencyLoaderHelper;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.split.SplitDependencyLoader;
 import android.content.res.AssetManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Resources;
@@ -49,7 +50,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
@@ -304,7 +304,7 @@
                 final String[] splitPaths;
                 try {
                     splitPaths = getSplitPaths(null);
-                } catch (PackageManager.NameNotFoundException e) {
+                } catch (NameNotFoundException e) {
                     // This should NEVER fail.
                     throw new AssertionError("null split not found");
                 }
@@ -336,7 +336,7 @@
         mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
 
         if (aInfo.requestsIsolatedSplitLoading() && !ArrayUtils.isEmpty(mSplitNames)) {
-            mSplitLoader = new SplitDependencyLoader(aInfo.splitDependencies);
+            mSplitLoader = new SplitDependencyLoaderImpl(aInfo.splitDependencies);
         }
     }
 
@@ -465,110 +465,88 @@
         }
     }
 
-    private class SplitDependencyLoader
-            extends SplitDependencyLoaderHelper<PackageManager.NameNotFoundException> {
-        private String[] mCachedBaseResourcePath;
+    /*
+     * All indices received by the super class should be shifted by 1 when accessing mSplitNames,
+     * etc. The super class assumes the base APK is index 0, while the PackageManager APIs don't
+     * include the base APK in the list of splits.
+     */
+    private class SplitDependencyLoaderImpl extends SplitDependencyLoader<NameNotFoundException> {
         private final String[][] mCachedResourcePaths;
-        private final ClassLoader[] mCachedSplitClassLoaders;
+        private final ClassLoader[] mCachedClassLoaders;
 
-        SplitDependencyLoader(SparseIntArray dependencies) {
+        SplitDependencyLoaderImpl(@NonNull SparseArray<int[]> dependencies) {
             super(dependencies);
-            mCachedResourcePaths = new String[mSplitNames.length][];
-            mCachedSplitClassLoaders = new ClassLoader[mSplitNames.length];
+            mCachedResourcePaths = new String[mSplitNames.length + 1][];
+            mCachedClassLoaders = new ClassLoader[mSplitNames.length + 1];
         }
 
         @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]);
-                }
-            }
+            return mCachedClassLoaders[splitIdx] != null;
         }
 
         @Override
-        protected void constructSplit(int splitIdx, int parentSplitIdx) throws
-                PackageManager.NameNotFoundException {
+        protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+                int parentSplitIdx) throws NameNotFoundException {
             final ArrayList<String> splitPaths = new ArrayList<>();
-            if (splitIdx == -1) {
+            if (splitIdx == 0) {
                 createOrUpdateClassLoaderLocked(null);
-                addAllConfigSplits(null, splitPaths);
-                mCachedBaseResourcePath = splitPaths.toArray(new String[splitPaths.size()]);
+                mCachedClassLoaders[0] = mClassLoader;
+
+                // Never add the base resources here, they always get added no matter what.
+                for (int configSplitIdx : configSplitIndices) {
+                    splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
+                }
+                mCachedResourcePaths[0] = 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]);
+            // Since we handled the special base case above, parentSplitIdx is always valid.
+            final ClassLoader parent = mCachedClassLoaders[parentSplitIdx];
+            mCachedClassLoaders[splitIdx] = ApplicationLoaders.getDefault().getClassLoader(
+                    mSplitAppDirs[splitIdx - 1], getTargetSdkVersion(), false, null, null, parent);
+
+            Collections.addAll(splitPaths, mCachedResourcePaths[parentSplitIdx]);
+            splitPaths.add(mSplitResDirs[splitIdx - 1]);
+            for (int configSplitIdx : configSplitIndices) {
+                splitPaths.add(mSplitResDirs[configSplitIdx - 1]);
             }
-
-            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 {
+        private int ensureSplitLoaded(String splitName) throws NameNotFoundException {
+            int idx = 0;
+            if (splitName != null) {
                 idx = Arrays.binarySearch(mSplitNames, splitName);
                 if (idx < 0) {
                     throw new PackageManager.NameNotFoundException(
                             "Split name '" + splitName + "' is not installed");
                 }
+                idx += 1;
             }
-
             loadDependenciesForSplit(idx);
             return idx;
         }
 
-        ClassLoader getClassLoaderForSplit(String splitName)
-                throws PackageManager.NameNotFoundException {
-            final int idx = ensureSplitLoaded(splitName);
-            if (idx < 0) {
-                return mClassLoader;
-            }
-            return mCachedSplitClassLoaders[idx];
+        ClassLoader getClassLoaderForSplit(String splitName) throws NameNotFoundException {
+            return mCachedClassLoaders[ensureSplitLoaded(splitName)];
         }
 
-        String[] getSplitPathsForSplit(String splitName)
-                throws PackageManager.NameNotFoundException {
-            final int idx = ensureSplitLoaded(splitName);
-            if (idx < 0) {
-                return mCachedBaseResourcePath;
-            }
-            return mCachedResourcePaths[idx];
+        String[] getSplitPathsForSplit(String splitName) throws NameNotFoundException {
+            return mCachedResourcePaths[ensureSplitLoaded(splitName)];
         }
     }
 
-    private SplitDependencyLoader mSplitLoader;
+    private SplitDependencyLoaderImpl mSplitLoader;
 
-    ClassLoader getSplitClassLoader(String splitName) throws PackageManager.NameNotFoundException {
+    ClassLoader getSplitClassLoader(String splitName) throws NameNotFoundException {
         if (mSplitLoader == null) {
             return mClassLoader;
         }
         return mSplitLoader.getClassLoaderForSplit(splitName);
     }
 
-    String[] getSplitPaths(String splitName) throws PackageManager.NameNotFoundException {
+    String[] getSplitPaths(String splitName) throws NameNotFoundException {
         if (mSplitLoader == null) {
             return mSplitResDirs;
         }
@@ -925,7 +903,7 @@
             final String[] splitPaths;
             try {
                 splitPaths = getSplitPaths(null);
-            } catch (PackageManager.NameNotFoundException e) {
+            } catch (NameNotFoundException e) {
                 // This should never fail.
                 throw new AssertionError("null split not found");
             }
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 29f29e7..c281c7f 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -52,10 +52,10 @@
 
     void startAdvertisingSet(in AdvertisingSetParameters parameters, in AdvertiseData advertiseData,
                                 in AdvertiseData scanResponse, in PeriodicAdvertisingParameters periodicParameters,
-                                in AdvertiseData periodicData, in IAdvertisingSetCallback callback);
+                                in AdvertiseData periodicData, in int timeout, in IAdvertisingSetCallback callback);
     void stopAdvertisingSet(in IAdvertisingSetCallback callback);
 
-    void enableAdverisingSet(in int advertiserId, in boolean enable);
+    void enableAdverisingSet(in int advertiserId, in boolean enable, in int timeout);
     void setAdvertisingData(in int advertiserId, in AdvertiseData data);
     void setScanResponseData(in int advertiserId, in AdvertiseData data);
     void setAdvertisingParameters(in int advertiserId, in AdvertisingSetParameters parameters);
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 1524022..5524a2b 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -63,9 +63,9 @@
      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
      */
-    public void enableAdvertising(boolean enable) {
+    public void enableAdvertising(boolean enable, int timeout) {
         try {
-            gatt.enableAdverisingSet(this.advertiserId, enable);
+            gatt.enableAdverisingSet(this.advertiserId, enable, timeout);
         } catch (RemoteException e) {
             Log.e(TAG, "remote exception - ", e);
         }
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index 453dd70..59fef8d 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -118,13 +118,11 @@
     private final boolean connectable;
     private final int interval;
     private final int txPowerLevel;
-    private final int timeoutMillis;
 
     private AdvertisingSetParameters(boolean connectable, boolean isLegacy,
                                      boolean isAnonymous, boolean includeTxPower,
                                      int primaryPhy, int secondaryPhy,
-                                     int interval, int txPowerLevel,
-                                     int timeoutMillis) {
+                                     int interval, int txPowerLevel) {
         this.connectable = connectable;
         this.isLegacy = isLegacy;
         this.isAnonymous = isAnonymous;
@@ -133,7 +131,6 @@
         this.secondaryPhy = secondaryPhy;
         this.interval = interval;
         this.txPowerLevel = txPowerLevel;
-        this.timeoutMillis = timeoutMillis;
     }
 
     private AdvertisingSetParameters(Parcel in) {
@@ -145,7 +142,6 @@
         secondaryPhy = in.readInt();
         interval = in.readInt();
         txPowerLevel = in.readInt();
-        timeoutMillis = in.readInt();
     }
 
     /**
@@ -188,11 +184,6 @@
      */
     public int getTxPowerLevel() { return txPowerLevel; }
 
-    /**
-     * Returns the advertising time limit in milliseconds.
-     */
-    public int getTimeout() { return timeoutMillis; }
-
     @Override
     public String toString() {
         return "AdvertisingSetParameters [connectable=" + connectable
@@ -202,8 +193,7 @@
              + ", primaryPhy=" + primaryPhy
              + ", secondaryPhy=" + secondaryPhy
              + ", interval=" + interval
-             + ", txPowerLevel=" + txPowerLevel
-             + ", timeoutMillis=" + timeoutMillis + "]";
+             + ", txPowerLevel=" + txPowerLevel + "]";
     }
 
     @Override
@@ -221,7 +211,6 @@
         dest.writeInt(secondaryPhy);
         dest.writeInt(interval);
         dest.writeInt(txPowerLevel);
-        dest.writeInt(timeoutMillis);
     }
 
     public static final Parcelable.Creator<AdvertisingSetParameters> CREATOR =
@@ -250,7 +239,6 @@
         private int secondaryPhy = PHY_LE_1M;
         private int interval = INTERVAL_LOW;
         private int txPowerLevel = TX_POWER_MEDIUM;
-        private int timeoutMillis = 0;
 
         /**
          * Set whether the advertisement type should be connectable or
@@ -380,30 +368,12 @@
         }
 
         /**
-         * Limit advertising to a given amount of time.
-         * @param timeoutMillis Advertising time limit. May not exceed 180000
-         * milliseconds. A value of 0 will disable the time limit.
-         * @throws IllegalArgumentException If the provided timeout is over 180000
-         * ms.
-         */
-        public Builder setTimeout(int timeoutMillis) {
-            if (timeoutMillis < 0 || timeoutMillis > LIMITED_ADVERTISING_MAX_MILLIS) {
-                throw new IllegalArgumentException("timeoutMillis invalid (must be 0-" +
-                                                   LIMITED_ADVERTISING_MAX_MILLIS +
-                                                   " milliseconds)");
-            }
-            this.timeoutMillis = timeoutMillis;
-            return this;
-        }
-
-        /**
          * Build the {@link AdvertisingSetParameters} object.
          */
         public AdvertisingSetParameters build() {
             return new AdvertisingSetParameters(connectable, isLegacy, isAnonymous,
                                                 includeTxPower, primaryPhy,
-                                                secondaryPhy, interval, txPowerLevel,
-                                                timeoutMillis);
+                                                secondaryPhy, interval, txPowerLevel);
         }
     }
 }
\ No newline at end of file
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index c9f1d7a..67fd1c8 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -130,7 +130,6 @@
             AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
             parameters.setLegacyMode(true);
             parameters.setConnectable(isConnectable);
-            parameters.setTimeout(settings.getTimeout());
             if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
                 parameters.setInterval(1600); // 1s
             } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
@@ -152,7 +151,7 @@
             AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings);
             mLegacyAdvertisers.put(callback, wrapped);
             startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null,
-                                wrapped);
+                                settings.getTimeout(), wrapped);
         }
     }
 
@@ -216,8 +215,8 @@
                                     AdvertiseData advertiseData, AdvertiseData scanResponse,
                                     PeriodicAdvertisingParameters periodicParameters,
                                     AdvertiseData periodicData, AdvertisingSetCallback callback) {
-        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
-                            periodicData, callback, new Handler(Looper.getMainLooper()));
+            startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                            periodicData, 0, callback, new Handler(Looper.getMainLooper()));
     }
 
     /**
@@ -237,6 +236,49 @@
                                     PeriodicAdvertisingParameters periodicParameters,
                                     AdvertiseData periodicData, AdvertisingSetCallback callback,
                                     Handler handler) {
+        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                            periodicData, 0, callback, handler);
+    }
+
+    /**
+    * Creates a new advertising set. If operation succeed, device will start advertising. This
+    * method returns immediately, the operation status is delivered through
+    * {@code callback.onAdvertisingSetStarted()}.
+    * <p>
+    * @param parameters advertising set parameters.
+    * @param advertiseData Advertisement data to be broadcasted.
+    * @param scanResponse Scan response associated with the advertisement data.
+    * @param periodicData Periodic advertising data.
+    * @param timeoutMillis Advertising time limit. May not exceed 180000
+    * @param callback Callback for advertising set.
+    */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+                                    AdvertiseData advertiseData, AdvertiseData scanResponse,
+                                    PeriodicAdvertisingParameters periodicParameters,
+                                    AdvertiseData periodicData, int timeoutMillis,
+                                    AdvertisingSetCallback callback) {
+        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                            periodicData, timeoutMillis, callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+    * Creates a new advertising set. If operation succeed, device will start advertising. This
+    * method returns immediately, the operation status is delivered through
+    * {@code callback.onAdvertisingSetStarted()}.
+    * <p>
+    * @param parameters advertising set parameters.
+    * @param advertiseData Advertisement data to be broadcasted.
+    * @param scanResponse Scan response associated with the advertisement data.
+    * @param periodicData Periodic advertising data.
+    * @param timeoutMillis Advertising time limit. May not exceed 180000
+    * @param callback Callback for advertising set.
+    * @param handler thread upon which the callbacks will be invoked.
+    */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+                                    AdvertiseData advertiseData, AdvertiseData scanResponse,
+                                    PeriodicAdvertisingParameters periodicParameters,
+                                    AdvertiseData periodicData, int timeoutMillis,
+                                    AdvertisingSetCallback callback, Handler handler) {
         BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
 
         if (callback == null) {
@@ -259,7 +301,7 @@
 
         try {
             gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
-                                     periodicData, wrapped);
+                                     periodicData, timeoutMillis, wrapped);
         } catch (RemoteException e) {
           Log.e(TAG, "Failed to start advertising set - ", e);
           throw new IllegalStateException("Failed to start advertising set");
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4dc6fd2..bd31b03 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1104,18 +1104,7 @@
      * @hide
      */
     public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
-    /**
-     * Activity action: Activate the current SIM card.  If SIM cards do not require activation,
-     * sending this intent is a no-op.
-     * <p>Input: No data should be specified.  get*Extra may have an optional
-     * {@link #EXTRA_SIM_ACTIVATION_RESPONSE} field containing a PendingIntent through which to
-     * send the activation result.
-     * <p>Output: nothing.
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_SIM_ACTIVATION_REQUEST =
-            "android.intent.action.SIM_ACTIVATION_REQUEST";
+
     /**
      * Activity Action: Main entry point for carrier setup apps.
      * <p>Carrier apps that provide an implementation for this action may be invoked to configure
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b4d77a0..0b3742f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -31,7 +31,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Printer;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -656,13 +656,15 @@
      *
      * 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.
+     * Each key represents a split and its value is an array of splits. The first element of this
+     * array is the parent split, and the rest are configuration splits. These configuration splits
+     * have no dependencies themselves.
      * 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;
+    public SparseArray<int[]> splitDependencies;
 
     /**
      * Full paths to the locations of extra resource packages (runtime overlays)
@@ -1182,7 +1184,7 @@
         dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
-        dest.writeSparseIntArray(splitDependencies);
+        dest.writeSparseArray((SparseArray) splitDependencies);
         dest.writeString(nativeLibraryDir);
         dest.writeString(secondaryNativeLibraryDir);
         dest.writeString(nativeLibraryRootDir);
@@ -1244,7 +1246,7 @@
         splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
-        splitDependencies = source.readSparseIntArray();
+        splitDependencies = source.readSparseArray(null);
         nativeLibraryDir = source.readString();
         secondaryNativeLibraryDir = source.readString();
         nativeLibraryRootDir = source.readString();
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 59c5307..f6f1be6 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -82,13 +82,15 @@
      *
      * 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.
+     * Each key represents a split and its value is an array of splits. The first element of this
+     * array is the parent split, and the rest are configuration splits. These configuration splits
+     * have no dependencies themselves.
      * 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;
+    public SparseArray<int[]> splitDependencies;
 
     /**
      * Full path to a directory assigned to the package for its persistent data.
@@ -155,7 +157,7 @@
         dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
-        dest.writeSparseIntArray(splitDependencies);
+        dest.writeSparseArray((SparseArray) splitDependencies);
         dest.writeString(dataDir);
         dest.writeString(deviceProtectedDataDir);
         dest.writeString(credentialProtectedDataDir);
@@ -185,7 +187,7 @@
         splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
-        splitDependencies = source.readSparseIntArray();
+        splitDependencies = source.readSparseArray(null);
         dataDir = source.readString();
         deviceProtectedDataDir = source.readString();
         credentialProtectedDataDir = source.readString();
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index be6dc05..99aa1bc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -76,7 +76,7 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.apk.ApkSignatureSchemeV2Verifier;
 import android.util.jar.StrictJarFile;
@@ -109,7 +109,6 @@
 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;
@@ -368,8 +367,12 @@
         /** Names of any split APKs, ordered by parsed splitName */
         public final String[] splitNames;
 
+        /** Names of any split APKs that are features. Ordered by splitName */
+        public final boolean[] isFeatureSplits;
+
         /** Dependencies of any split APKs, ordered by parsed splitName */
         public final String[] usesSplitNames;
+        public final String[] configForSplit;
 
         /**
          * Path where this package was found on disk. For monolithic packages
@@ -396,14 +399,16 @@
         public final boolean isolatedSplits;
 
         public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
-                String[] usesSplitNames, String[] splitCodePaths,
-                int[] splitRevisionCodes) {
+                boolean[] isFeatureSplits, String[] usesSplitNames, String[] configForSplit,
+                String[] splitCodePaths, int[] splitRevisionCodes) {
             this.packageName = baseApk.packageName;
             this.versionCode = baseApk.versionCode;
             this.installLocation = baseApk.installLocation;
             this.verifiers = baseApk.verifiers;
             this.splitNames = splitNames;
+            this.isFeatureSplits = isFeatureSplits;
             this.usesSplitNames = usesSplitNames;
+            this.configForSplit = configForSplit;
             this.codePath = codePath;
             this.baseCodePath = baseApk.codePath;
             this.splitCodePaths = splitCodePaths;
@@ -434,6 +439,8 @@
         public final String codePath;
         public final String packageName;
         public final String splitName;
+        public boolean isFeatureSplit;
+        public final String configForSplit;
         public final String usesSplitName;
         public final int versionCode;
         public final int revisionCode;
@@ -448,14 +455,17 @@
         public final boolean extractNativeLibs;
         public final boolean isolatedSplits;
 
-        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 isolatedSplits) {
+        public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit,
+                String configForSplit, 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 isolatedSplits) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
+            this.isFeatureSplit = isFeatureSplit;
+            this.configForSplit = configForSplit;
             this.usesSplitName = usesSplitName;
             this.versionCode = versionCode;
             this.revisionCode = revisionCode;
@@ -811,10 +821,10 @@
         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, null);
+        return new PackageLite(packagePath, baseApk, null, null, null, null, null, null);
     }
 
-    private static PackageLite parseClusterPackageLite(File packageDir, int flags)
+    static PackageLite parseClusterPackageLite(File packageDir, int flags)
             throws PackageParserException {
         final File[] files = packageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -869,12 +879,16 @@
         final int size = apks.size();
 
         String[] splitNames = null;
+        boolean[] isFeatureSplits = null;
         String[] usesSplitNames = null;
+        String[] configForSplits = null;
         String[] splitCodePaths = null;
         int[] splitRevisionCodes = null;
         if (size > 0) {
             splitNames = new String[size];
+            isFeatureSplits = new boolean[size];
             usesSplitNames = new String[size];
+            configForSplits = new String[size];
             splitCodePaths = new String[size];
             splitRevisionCodes = new int[size];
 
@@ -884,14 +898,16 @@
             for (int i = 0; i < size; i++) {
                 final ApkLite apk = apks.get(splitNames[i]);
                 usesSplitNames[i] = apk.usesSplitName;
+                isFeatureSplits[i] = apk.isFeatureSplit;
+                configForSplits[i] = apk.configForSplit;
                 splitCodePaths[i] = apk.codePath;
                 splitRevisionCodes[i] = apk.revisionCode;
             }
         }
 
         final String codePath = packageDir.getAbsolutePath();
-        return new PackageLite(codePath, baseApk, splitNames, usesSplitNames, splitCodePaths,
-                splitRevisionCodes);
+        return new PackageLite(codePath, baseApk, splitNames, isFeatureSplits, usesSplitNames,
+                configForSplits, splitCodePaths, splitRevisionCodes);
     }
 
     /**
@@ -1069,42 +1085,6 @@
         }
     }
 
-    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
@@ -1122,11 +1102,15 @@
         }
 
         // Build the split dependency tree.
-        SparseIntArray splitDependencies = null;
+        SparseArray<int[]> splitDependencies = null;
         final SplitAssetLoader assetLoader;
         if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
-            splitDependencies = buildSplitDependencyTree(lite);
-            assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+            try {
+                splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
+                assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
+            } catch (SplitAssetDependencyLoader.IllegalDependencyException e) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());
+            }
         } else {
             assetLoader = new DefaultSplitAssetLoader(lite, flags);
         }
@@ -1762,6 +1746,8 @@
         boolean use32bitAbi = false;
         boolean extractNativeLibs = true;
         boolean isolatedSplits = false;
+        boolean isFeatureSplit = false;
+        String configForSplit = null;
         String usesSplitName = null;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
@@ -1777,6 +1763,10 @@
                 coreApp = attrs.getAttributeBooleanValue(i, false);
             } else if (attr.equals("isolatedSplits")) {
                 isolatedSplits = attrs.getAttributeBooleanValue(i, false);
+            } else if (attr.equals("configForSplit")) {
+                configForSplit = attrs.getAttributeValue(i);
+            } else if (attr.equals("isFeatureSplit")) {
+                isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
             }
         }
 
@@ -1831,10 +1821,10 @@
             }
         }
 
-        return new ApkLite(codePath, packageSplit.first, packageSplit.second, usesSplitName,
-                versionCode, revisionCode, installLocation, verifiers, signatures,
-                certificates, coreApp, debuggable, multiArch, use32bitAbi, extractNativeLibs,
-                isolatedSplits);
+        return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+                configForSplit, usesSplitName, versionCode, revisionCode, installLocation,
+                verifiers, signatures, certificates, coreApp, debuggable, multiArch, use32bitAbi,
+                extractNativeLibs, isolatedSplits);
     }
 
     /**
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 4df90eb..16023f0 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -18,10 +18,11 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 
+import android.annotation.NonNull;
 import android.content.pm.PackageParser;
 import android.content.res.AssetManager;
 import android.os.Build;
-import android.util.SparseIntArray;
+import android.util.SparseArray;
 
 import libcore.io.IoUtils;
 
@@ -34,49 +35,31 @@
  * @hide
  */
 public class SplitAssetDependencyLoader
-        extends SplitDependencyLoaderHelper<PackageParser.PackageParserException>
+        extends SplitDependencyLoader<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 String[][] mCachedPaths;
     private AssetManager[] mCachedAssetManagers;
 
-    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, SparseIntArray dependencies,
-            int flags) {
+    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
+            SparseArray<int[]> dependencies, int flags) {
         super(dependencies);
-        mBasePath = pkg.baseCodePath;
-        mSplitNames = pkg.splitNames;
-        mSplitPaths = pkg.splitCodePaths;
+
+        // The base is inserted into index 0, so we need to shift all the splits by 1.
+        mSplitPaths = new String[pkg.splitCodePaths.length + 1];
+        mSplitPaths[0] = pkg.baseCodePath;
+        System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+
         mFlags = flags;
-        mCachedBasePaths = null;
-        mCachedBaseAssetManager = null;
-        mCachedSplitPaths = new String[mSplitNames.length][];
-        mCachedAssetManagers = new AssetManager[mSplitNames.length];
+        mCachedPaths = new String[mSplitPaths.length][];
+        mCachedAssetManagers = new AssetManager[mSplitPaths.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]);
-            }
-        }
+        return mCachedAssetManagers[splitIdx] != null;
     }
 
     private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
@@ -107,45 +90,38 @@
     }
 
     @Override
-    protected void constructSplit(int splitIdx, int parentSplitIdx) throws
-            PackageParser.PackageParserException {
+    protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+            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]);
+        if (parentSplitIdx >= 0) {
+            Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
         }
 
         assetPaths.add(mSplitPaths[splitIdx]);
-        addAllConfigSplits(mSplitNames[splitIdx], assetPaths);
-        mCachedSplitPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
-        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedSplitPaths[splitIdx],
+        for (int configSplitIdx : configSplitIndices) {
+            assetPaths.add(mSplitPaths[configSplitIdx]);
+        }
+        mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
+        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
                 mFlags);
     }
 
     @Override
     public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
-        loadDependenciesForSplit(BASE_ASSET_PATH_IDX);
-        return mCachedBaseAssetManager;
+        loadDependenciesForSplit(0);
+        return mCachedAssetManagers[0];
     }
 
     @Override
     public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
-        loadDependenciesForSplit(idx);
-        return mCachedAssetManagers[idx];
+        // Since we insert the base at position 0, and PackageParser keeps splits separate from
+        // the base, we need to adjust the index.
+        loadDependenciesForSplit(idx + 1);
+        return mCachedAssetManagers[idx + 1];
     }
 
     @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/SplitDependencyLoader.java b/core/java/android/content/pm/split/SplitDependencyLoader.java
new file mode 100644
index 0000000..3586546
--- /dev/null
+++ b/core/java/android/content/pm/split/SplitDependencyLoader.java
@@ -0,0 +1,242 @@
+/*
+ * 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.IntRange;
+import android.annotation.NonNull;
+import android.content.pm.PackageParser;
+import android.util.IntArray;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * 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 SplitDependencyLoader<E extends Exception> {
+    private final @NonNull SparseArray<int[]> mDependencies;
+
+    /**
+     * Construct a new SplitDependencyLoader. Meant to be called from the
+     * subclass constructor.
+     * @param dependencies The dependency tree of splits.
+     */
+    protected SplitDependencyLoader(@NonNull SparseArray<int[]> 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[], int)}.
+     * @param splitIdx The index of the split to load. 0 represents the base Application.
+     */
+    protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E {
+        // Quick check before any allocations are done.
+        if (isSplitCached(splitIdx)) {
+            return;
+        }
+
+        // Special case the base, since it has no dependencies.
+        if (splitIdx == 0) {
+            final int[] configSplitIndices = collectConfigSplitIndices(0);
+            constructSplit(0, configSplitIndices, -1);
+            return;
+        }
+
+        // Build up the dependency hierarchy.
+        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 (true) {
+            // Only follow the first index into the array. The others are config splits and
+            // get loaded with the split.
+            final int[] deps = mDependencies.get(splitIdx);
+            if (deps != null && deps.length > 0) {
+                splitIdx = deps[0];
+            } else {
+                splitIdx = -1;
+            }
+
+            if (splitIdx < 0 || 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);
+            final int[] configSplitIndices = collectConfigSplitIndices(idx);
+            constructSplit(idx, configSplitIndices, parentIdx);
+            parentIdx = idx;
+        }
+    }
+
+    private @NonNull int[] collectConfigSplitIndices(int splitIdx) {
+        // The config splits appear after the first element.
+        final int[] deps = mDependencies.get(splitIdx);
+        if (deps == null || deps.length <= 1) {
+            return EmptyArray.INT;
+        }
+        return Arrays.copyOfRange(deps, 1, deps.length);
+    }
+
+    /**
+     * 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(@IntRange(from = 0) 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. 0 represents the base Application.
+     * @param configSplitIndices The array of configuration splits to load along with this split.
+     *                           May be empty (length == 0) but never null.
+     * @param parentSplitIdx The index of the parent split. -1 if there is no parent.
+     * @throws E Subclass defined exception representing failure to construct a split.
+     */
+    protected abstract void constructSplit(@IntRange(from = 0) int splitIdx,
+            @NonNull @IntRange(from = 1) int[] configSplitIndices,
+            @IntRange(from = -1) int parentSplitIdx) throws E;
+
+    public static class IllegalDependencyException extends Exception {
+        private IllegalDependencyException(String message) {
+            super(message);
+        }
+    }
+
+    private static int[] append(int[] src, int elem) {
+        if (src == null) {
+            return new int[] { elem };
+        }
+        int[] dst = Arrays.copyOf(src, src.length + 1);
+        dst[src.length] = elem;
+        return dst;
+    }
+
+    public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
+            PackageParser.PackageLite pkg) throws IllegalDependencyException {
+        // The data structure that holds the dependencies. In PackageParser, splits are stored
+        // in their own array, separate from the base. We treat all paths as equals, so
+        // we need to insert the base as index 0, and shift all other splits.
+        final SparseArray<int[]> splitDependencies = new SparseArray<>();
+
+        // The base depends on nothing.
+        splitDependencies.put(0, new int[] {-1});
+
+        // First write out the <uses-split> dependencies. These must appear first in the
+        // array of ints, as is convention in this class.
+        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+            if (!pkg.isFeatureSplits[splitIdx]) {
+                // Non-feature splits don't have dependencies.
+                continue;
+            }
+
+            // Implicit dependency on the base.
+            final int targetIdx;
+            final String splitDependency = pkg.usesSplitNames[splitIdx];
+            if (splitDependency != null) {
+                final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+                if (depIdx < 0) {
+                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                            + "' requires split '" + splitDependency + "', which is missing.");
+                }
+                targetIdx = depIdx + 1;
+            } else {
+                // Implicitly depend on the base.
+                targetIdx = 0;
+            }
+            splitDependencies.put(splitIdx + 1, new int[] {targetIdx});
+        }
+
+        // Write out the configForSplit reverse-dependencies. These appear after the <uses-split>
+        // dependencies and are considered leaves.
+        //
+        // At this point, all splits in splitDependencies have the first element in their array set.
+        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+            if (pkg.isFeatureSplits[splitIdx]) {
+                // Feature splits are not configForSplits.
+                continue;
+            }
+
+            // Implicit feature for the base.
+            final int targetSplitIdx;
+            final String configForSplit = pkg.configForSplit[splitIdx];
+            if (configForSplit != null) {
+                final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+                if (depIdx < 0) {
+                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                            + "' targets split '" + configForSplit + "', which is missing.");
+                }
+
+                if (!pkg.isFeatureSplits[depIdx]) {
+                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                            + "' declares itself as configuration split for a non-feature split '"
+                            + pkg.splitNames[depIdx] + "'");
+                }
+                targetSplitIdx = depIdx + 1;
+            } else {
+                targetSplitIdx = 0;
+            }
+            splitDependencies.put(targetSplitIdx,
+                    append(splitDependencies.get(targetSplitIdx), splitIdx + 1));
+        }
+
+        // Verify that there are no cycles.
+        final BitSet bitset = new BitSet();
+        for (int i = 0, size = splitDependencies.size(); i < size; i++) {
+            int splitIdx = splitDependencies.keyAt(i);
+
+            bitset.clear();
+            while (splitIdx != -1) {
+                // Check if this split has been visited yet.
+                if (bitset.get(splitIdx)) {
+                    throw new IllegalDependencyException("Cycle detected in split dependencies.");
+                }
+
+                // Mark the split so that if we visit it again, we no there is a cycle.
+                bitset.set(splitIdx);
+
+                // Follow the first dependency only, the others are leaves by definition.
+                final int[] deps = splitDependencies.get(splitIdx);
+                splitIdx = deps != null ? deps[0] : -1;
+            }
+        }
+        return splitDependencies;
+    }
+}
diff --git a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java b/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
deleted file mode 100644
index b493480..0000000
--- a/core/java/android/content/pm/split/SplitDependencyLoaderHelper.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.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/os/UserManager.java b/core/java/android/os/UserManager.java
index a638cd4..f6712f8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.Activity;
@@ -763,6 +764,16 @@
     public static final int PIN_VERIFICATION_SUCCESS = -1;
 
     /**
+     * Sent when user restrictions have changed.
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi // To allow seeing it from CTS.
+    public static final String ACTION_USER_RESTRICTIONS_CHANGED =
+            "android.os.action.USER_RESTRICTIONS_CHANGED";
+
+    /**
      * Error result indicating that this user is not allowed to add other users on this device.
      * This is a result code returned from the activity created by the intent
      * {@link #createUserCreationIntent(String, String, String, PersistableBundle)}.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 391ee83..7b84f689 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6630,6 +6630,8 @@
          * This value is only used for managed profiles.
          * @hide
          */
+        @TestApi
+        @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
         public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
 
         /** @hide */
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 3822138..519a7dd 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -58,6 +58,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.AdapterView.OnItemSelectedListener;
@@ -159,20 +160,6 @@
     private SearchableInfo mSearchable;
     private Bundle mAppSearchData;
 
-    /*
-     * SearchView can be set expanded before the IME is ready to be shown during
-     * initial UI setup. The show operation is asynchronous to account for this.
-     */
-    private Runnable mShowImeRunnable = new Runnable() {
-        public void run() {
-            InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-
-            if (imm != null) {
-                imm.showSoftInputUnchecked(0, null);
-            }
-        }
-    };
-
     private Runnable mUpdateDrawableStateRunnable = new Runnable() {
         public void run() {
             updateFocusedState();
@@ -497,9 +484,9 @@
     @Override
     public void clearFocus() {
         mClearingFocus = true;
-        setImeVisibility(false);
         super.clearFocus();
         mSearchSrcTextView.clearFocus();
+        mSearchSrcTextView.setImeVisibility(false);
         mClearingFocus = false;
     }
 
@@ -967,19 +954,6 @@
         super.onDetachedFromWindow();
     }
 
-    private void setImeVisibility(final boolean visible) {
-        if (visible) {
-            post(mShowImeRunnable);
-        } else {
-            removeCallbacks(mShowImeRunnable);
-            InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-
-            if (imm != null) {
-                imm.hideSoftInputFromWindow(getWindowToken(), 0);
-            }
-        }
-    }
-
     /**
      * Called by the SuggestionsAdapter
      * @hide
@@ -1286,7 +1260,7 @@
                 if (mSearchable != null) {
                     launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
                 }
-                setImeVisibility(false);
+                mSearchSrcTextView.setImeVisibility(false);
                 dismissSuggestions();
             }
         }
@@ -1311,7 +1285,7 @@
         } else {
             mSearchSrcTextView.setText("");
             mSearchSrcTextView.requestFocus();
-            setImeVisibility(true);
+            mSearchSrcTextView.setImeVisibility(true);
         }
 
     }
@@ -1319,7 +1293,7 @@
     private void onSearchClicked() {
         updateViewsVisibility(false);
         mSearchSrcTextView.requestFocus();
-        setImeVisibility(true);
+        mSearchSrcTextView.setImeVisibility(true);
         if (mOnSearchClickListener != null) {
             mOnSearchClickListener.onClick(this);
         }
@@ -1477,7 +1451,7 @@
         if (mOnSuggestionListener == null
                 || !mOnSuggestionListener.onSuggestionClick(position)) {
             launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
-            setImeVisibility(false);
+            mSearchSrcTextView.setImeVisibility(false);
             dismissSuggestions();
             return true;
         }
@@ -1910,6 +1884,9 @@
         private int mThreshold;
         private SearchView mSearchView;
 
+        private boolean mHasPendingShowSoftInputRequest;
+        final Runnable mRunShowSoftInputIfNecessary = () -> showSoftInputIfNecessary();
+
         public SearchAutoComplete(Context context) {
             super(context);
             mThreshold = getThreshold();
@@ -1983,11 +1960,13 @@
             super.onWindowFocusChanged(hasWindowFocus);
 
             if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
-                InputMethodManager inputManager =
-                        getContext().getSystemService(InputMethodManager.class);
-                inputManager.showSoftInput(this, 0);
-                // If in landscape mode, then make sure that
-                // the ime is in front of the dropdown.
+                // Since InputMethodManager#onPostWindowFocus() will be called after this callback,
+                // it is a bit too early to call InputMethodManager#showSoftInput() here. We still
+                // need to wait until the system calls back onCreateInputConnection() to call
+                // InputMethodManager#showSoftInput().
+                mHasPendingShowSoftInputRequest = true;
+
+                // If in landscape mode, then make sure that the ime is in front of the dropdown.
                 if (isLandscapeMode(getContext())) {
                     ensureImeVisible(true);
                 }
@@ -2027,7 +2006,7 @@
                     }
                     if (event.isTracking() && !event.isCanceled()) {
                         mSearchView.clearFocus();
-                        mSearchView.setImeVisibility(false);
+                        setImeVisibility(false);
                         return true;
                     }
                 }
@@ -2051,5 +2030,51 @@
             };
             return 160;
         }
+
+        /**
+         * We override {@link View#onCreateInputConnection(EditorInfo)} as a signal to schedule a
+         * pending {@link InputMethodManager#showSoftInput(View, int)} request (if any).
+         */
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
+            final InputConnection ic = super.onCreateInputConnection(editorInfo);
+            if (mHasPendingShowSoftInputRequest) {
+                removeCallbacks(mRunShowSoftInputIfNecessary);
+                post(mRunShowSoftInputIfNecessary);
+            }
+            return ic;
+        }
+
+        private void showSoftInputIfNecessary() {
+            if (mHasPendingShowSoftInputRequest) {
+                final InputMethodManager imm =
+                        getContext().getSystemService(InputMethodManager.class);
+                imm.showSoftInput(this, 0);
+                mHasPendingShowSoftInputRequest = false;
+            }
+        }
+
+        private void setImeVisibility(final boolean visible) {
+            final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
+            if (!visible) {
+                mHasPendingShowSoftInputRequest = false;
+                removeCallbacks(mRunShowSoftInputIfNecessary);
+                imm.hideSoftInputFromWindow(getWindowToken(), 0);
+                return;
+            }
+
+            if (imm.isActive(this)) {
+                // This means that SearchAutoComplete is already connected to the IME.
+                // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
+                mHasPendingShowSoftInputRequest = false;
+                removeCallbacks(mRunShowSoftInputIfNecessary);
+                imm.showSoftInput(this, 0);
+                return;
+            }
+
+            // Otherwise, InputMethodManager#showSoftInput() should be deferred after
+            // onCreateInputConnection().
+            mHasPendingShowSoftInputRequest = true;
+        }
     }
 }
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 67050f7..cf6bd9e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1014,6 +1014,15 @@
          <p>The default value of this attribute is <code>false</code>. -->
     <attr name="isolatedSplits" format="boolean" />
 
+    <!-- If set to <code>true</code>, indicates to the platform that this APK is
+         a 'feature' split and that it implicitly depends on the base APK. This distinguishes
+         this split APK from a 'configuration' split, which provides resource overrides
+         for a particular 'feature' split. Only useful when the base APK specifies
+         <code>android:isolatedSplits="true"</code>.
+
+         <p>The default value of this attribute is <code>false</code>. -->
+    <attr name="isFeatureSplit" 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
@@ -1286,6 +1295,7 @@
         <attr name="sharedUserLabel" />
         <attr name="installLocation" />
         <attr name="isolatedSplits" />
+        <attr name="isFeatureSplit" />
         <attr name="targetSandboxVersion" />
     </declare-styleable>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2897c62..f965c69 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2807,6 +2807,7 @@
         <public name="importantForAutofill" />
         <public name="recycleEnabled"/>
         <public name="isStatic" />
+        <public name="isFeatureSplit" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/docs/html/topic/libraries/support-library/revisions.jd b/docs/html/topic/libraries/support-library/revisions.jd
index 9a24d15..adb1af5 100644
--- a/docs/html/topic/libraries/support-library/revisions.jd
+++ b/docs/html/topic/libraries/support-library/revisions.jd
@@ -316,14 +316,13 @@
   implementations, as well as any calls to this method, should be removed.
   </li>
 
-  <li>{@link android.support.v4.media.session.MediaSessionCompat#obtain
+  <li>{@code
   MediaSessionCompat.obtain()} has been deprecated and replaced with the more
   appropriately-named method
   <a href="/reference/android/support/v4/media/session/MediaSessionCompat.html#fromMediaSession"><code>fromMediaSession()</code></a>.
   </li>
 
-  <li>{@link
-  android.support.v4.media.session.MediaSessionCompat.QueueItem#obtain
+  <li>{@code
   MediaSessionCompat.QueueItem.obtain()} has been deprecated and replaced with
   the more appropriately-named method
   <a href="/reference/android/support/v4/media/session/MediaSessionCompat.QueueItem.html#fromQueueItem"><code>fromQueueItem()</code></a>.
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index b8d95e4..359cfac 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4765,7 +4765,6 @@
                     && (targetTypeLen = attrPrivate.size())
             );
         }
-        break;
     }
     return 0;
 }
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
index 8e9741e..94a2a14 100644
--- a/libs/androidfw/tests/data/basic/R.h
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -44,8 +44,8 @@
       density = 0x7f030002,
 
       // From feature
-      test3 = 0x7f080000,
-      test4 = 0x7f080001,
+      test3 = 0x80020000,
+      test4 = 0x80020001,
     };
   };
 
@@ -57,7 +57,7 @@
       ref2 = 0x7f040003,
 
       // From feature
-      number3 = 0x7f090000,
+      number3 = 0x80030000,
     };
   };
 
diff --git a/libs/androidfw/tests/data/feature/AndroidManifest.xml b/libs/androidfw/tests/data/feature/AndroidManifest.xml
index c972372..12ca5b6 100644
--- a/libs/androidfw/tests/data/feature/AndroidManifest.xml
+++ b/libs/androidfw/tests/data/feature/AndroidManifest.xml
@@ -15,5 +15,6 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.basic">
+    package="com.android.basic"
+    featureName="feature">
 </manifest>
diff --git a/libs/androidfw/tests/data/feature/build b/libs/androidfw/tests/data/feature/build
index 6ed3e41..aa2f716 100755
--- a/libs/androidfw/tests/data/feature/build
+++ b/libs/androidfw/tests/data/feature/build
@@ -19,4 +19,10 @@
 
 PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
 
-aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --feature-of ../basic/basic.apk -F feature.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link -o feature.apk \
+    --manifest AndroidManifest.xml \
+    -I $PATH_TO_FRAMEWORK_RES \
+    -I ../basic/basic.apk \
+    --package-id 0x80 \
+    compiled.flata
diff --git a/libs/androidfw/tests/data/feature/feature.apk b/libs/androidfw/tests/data/feature/feature.apk
index 767fed6..a0dae38 100644
--- a/libs/androidfw/tests/data/feature/feature.apk
+++ b/libs/androidfw/tests/data/feature/feature.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/feature/res/values/values.xml b/libs/androidfw/tests/data/feature/res/values/values.xml
index 59f7d93..43e1517 100644
--- a/libs/androidfw/tests/data/feature/res/values/values.xml
+++ b/libs/androidfw/tests/data/feature/res/values/values.xml
@@ -15,13 +15,12 @@
 -->
 
 <resources>
-    <!-- Features are offset, so 7f020000 will become 7f080000 at runtime. -->
-    <public type="string" name="test3" id="0x7f020000" />
+    <public type="string" name="test3" id="0x80020000" />
     <string name="test3">test3</string>
 
-    <public type="string" name="test4" id="0x7f020001" />
+    <public type="string" name="test4" id="0x80020001" />
     <string name="test4">test4</string>
 
-    <public type="integer" name="number3" id="0x7f030000" />
+    <public type="integer" name="number3" id="0x80030000" />
     <integer name="number3">200</integer>
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index c64574f..1010a8a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -775,7 +775,7 @@
     <string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
 
     <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging/discharging -->
-    <string name="power_remaining_duration_only">Approx. <xliff:g id="time">%1$s</xliff:g> left</string>
+    <string name="power_remaining_duration_only">about <xliff:g id="time">%1$s</xliff:g> left</string>
 
     <!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
     <string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
@@ -793,25 +793,25 @@
             <xliff:g id="state">%2$s</xliff:g></string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> -
-            <xliff:g id="time">%2$s</xliff:g> until full</string>
+            <xliff:g id="time">%2$s</xliff:g> until fully charged</string>
     <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration -->
     <string name="power_charging_duration_short"><xliff:g id="level">%1$s</xliff:g> -
         <xliff:g id="time">%2$s</xliff:g></string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration_ac"><xliff:g id="level">%1$s</xliff:g> -
-            <xliff:g id="time">%2$s</xliff:g> until full on AC</string>
+            <xliff:g id="time">%2$s</xliff:g> until fully charged on AC</string>
     <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration -->
     <string name="power_charging_duration_ac_short"><xliff:g id="level">%1$s</xliff:g> -
         <xliff:g id="time">%2$s</xliff:g></string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration_usb"><xliff:g id="level">%1$s</xliff:g> -
-            <xliff:g id="time">%2$s</xliff:g> until full over USB</string>
+            <xliff:g id="time">%2$s</xliff:g> until fully charged over USB</string>
     <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration -->
     <string name="power_charging_duration_usb_short"><xliff:g id="level">%1$s</xliff:g> -
         <xliff:g id="time">%2$s</xliff:g></string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration_wireless"><xliff:g id="level">%1$s</xliff:g> -
-            <xliff:g id="time">%2$s</xliff:g> until full from wireless</string>
+            <xliff:g id="time">%2$s</xliff:g> until fully charged from wireless</string>
     <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
     <string name="power_charging_duration_wireless_short"><xliff:g id="level">%1$s</xliff:g> -
         <xliff:g id="time">%2$s</xliff:g></string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
index 3fc999f..f6f8168 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -48,7 +48,7 @@
     /**
      * List of the category's children
      */
-    public List<Tile> tiles = new ArrayList<Tile>();
+    public List<Tile> tiles = new ArrayList<>();
 
 
     public DashboardCategory() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 457ce76..af247bd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -30,7 +30,6 @@
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -40,10 +39,8 @@
 import android.widget.Toolbar;
 
 import com.android.settingslib.R;
-import com.android.settingslib.applications.InterestingConfigChanges;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 
 public class SettingsDrawerActivity extends Activity {
@@ -63,15 +60,6 @@
 
     private FrameLayout mContentHeaderContainer;
 
-    // Remove below after new IA
-    @Deprecated
-    private static List<DashboardCategory> sDashboardCategories;
-    @Deprecated
-    private static HashMap<Pair<String, String>, Tile> sTileCache;
-    @Deprecated
-    private static InterestingConfigChanges sConfigTracker;
-    // Remove above after new IA
-
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -175,17 +163,6 @@
         getActionBar().setDisplayHomeAsUpEnabled(true);
     }
 
-    public List<DashboardCategory> getDashboardCategories() {
-        if (sDashboardCategories == null) {
-            sTileCache = new HashMap<>();
-            sConfigTracker = new InterestingConfigChanges();
-            // Apply initial current config.
-            sConfigTracker.applyNewConfig(getResources());
-            sDashboardCategories = TileUtils.getCategories(this, sTileCache);
-        }
-        return sDashboardCategories;
-    }
-
     protected void onCategoriesChanged() {
         final int N = mCategoryListeners.size();
         for (int i = 0; i < N; i++) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 48f3e2a..fed48b4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -394,8 +394,9 @@
      *
      * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned.
      * If the given AccessPoint is not active, a value will be calculated from previous scan
-     * results, returning the best RSSI for all matching AccessPoints. If the access point is not
-     * connected and there are no scan results, the rssi will be set to {@link #UNREACHABLE_RSSI}.
+     * results, returning the best RSSI for all matching AccessPoints averaged with the previous
+     * value. If the access point is not connected and there are no scan results, the rssi will be
+     * set to {@link #UNREACHABLE_RSSI}.
      *
      * <p>Old scan results will be evicted from the cache when this method is invoked.
      */
@@ -413,7 +414,11 @@
             }
         }
 
-        mRssi = rssi;
+        if (rssi != UNREACHABLE_RSSI && mRssi != UNREACHABLE_RSSI) {
+            mRssi = (mRssi + rssi) / 2; // half-life previous value
+        } else {
+            mRssi = rssi;
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index c9fa017..a9aaa05 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -43,10 +43,6 @@
     private static final int[] STATE_SECURED = {
             R.attr.state_encrypted
     };
-    private static final int[] STATE_SAVED = {
-            R.attr.state_encrypted,
-            R.attr.state_saved
-    };
 
     private static final int[] wifi_friction_attributes = { R.attr.wifi_friction };
 
@@ -152,10 +148,8 @@
         }
         view.itemView.setContentDescription(mContentDescription);
 
-        if (!mForSavedNetworks) {
-            ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
-            bindFrictionImage(frictionImageView);
-        }
+        ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
+        bindFrictionImage(frictionImageView);
     }
 
     protected void updateIcon(int level, Context context) {
@@ -184,11 +178,7 @@
             return;
         }
         if (mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
-            if (mAccessPoint.isSaved()) {
-                mFrictionSld.setState(STATE_SAVED);
-            } else {
-                mFrictionSld.setState(STATE_SECURED);
-            }
+            mFrictionSld.setState(STATE_SECURED);
         }
         Drawable drawable = mFrictionSld.getCurrent();
         frictionImageView.setImageDrawable(drawable);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index e8a58c1..b9b4ef3 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -195,6 +195,26 @@
         assertThat(ap.getRssi()).isEqualTo(newRssi);
     }
 
+    @Test
+    public void testUpdateWithScanResultShouldAverageRssi() {
+        String ssid = "ssid";
+        int originalRssi = -65;
+        int newRssi = -80;
+        int expectedRssi = (originalRssi + newRssi) / 2;
+        AccessPoint ap =
+                new TestAccessPointBuilder(mContext).setSsid(ssid).setRssi(originalRssi).build();
+
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.level = newRssi;
+        scanResult.BSSID = "bssid";
+        scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+        scanResult.capabilities = "";
+        assertThat(ap.update(scanResult)).isTrue();
+
+        assertThat(ap.getRssi()).isEqualTo(expectedRssi);
+    }
+
     private AccessPoint createAccessPointWithScanResultCache() {
         Bundle bundle = new Bundle();
         ArrayList<ScanResult> scanResults = new ArrayList<>();
@@ -203,6 +223,7 @@
             scanResult.level = i;
             scanResult.BSSID = "bssid-" + i;
             scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
+            scanResult.capabilities = "";
             scanResults.add(scanResult);
         }
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 665c439..81bd723 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -73,25 +73,28 @@
         return this;
     }
 
+    public TestAccessPointBuilder setRssi(int rssi) {
+        mRssi = rssi;
+        return this;
+    }
+
     /**
-    * Set the signal level.
-    * Side effect: if this AccessPoint was previously unreachable,
+    * Set the rssi based upon the desired signal level.
+     *
+    * <p>Side effect: if this AccessPoint was previously unreachable,
     * setting the level will also make it reachable.
     */
     public TestAccessPointBuilder setLevel(int level) {
-        int outputRange = AccessPoint.SIGNAL_LEVELS - 1;
-
-        if (level > outputRange) {
-            level = outputRange;
-        } else if (level < 0) {
-            level = 0;
+        // Reversal of WifiManager.calculateSignalLevels
+        if (level == 0) {
+            mRssi = MIN_RSSI;
+        } else if (level >= AccessPoint.SIGNAL_LEVELS) {
+            mRssi = MAX_RSSI;
+        } else {
+            float inputRange = MAX_RSSI - MIN_RSSI;
+            float outputRange = AccessPoint.SIGNAL_LEVELS - 1;
+            mRssi = (int) (level * inputRange / outputRange + MIN_RSSI);
         }
-
-        int inputRange = MAX_RSSI - MIN_RSSI;
-
-        // calculate the rssi required to get the level we want.
-        // this is a rearrangement of the formula from WifiManager.calculateSignalLevel()
-        mRssi = (int)((float)(level * inputRange) / (float)outputRange) + MIN_RSSI;
         return this;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8da17fa..b7b4a3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,7 +23,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.internal.widget.CachingIconView;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
@@ -127,12 +126,7 @@
         super.setDark(dark, fade, delay);
         if (mDark == dark) return;
         mDark = dark;
-        if (fade) {
-            mViewInvertHelper.fade(dark, delay);
-        } else {
-            mViewInvertHelper.update(dark);
-        }
-        mShelfIcons.setAmbient(dark);
+        mShelfIcons.setDark(dark, fade, delay);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 1101701..ffc4e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,6 +27,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -46,6 +47,7 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 
 import java.text.NumberFormat;
@@ -99,7 +101,6 @@
     private int mDensity;
     private float mIconScale = 1.0f;
     private final Paint mDotPaint = new Paint();
-    private boolean mDotVisible;
     private float mDotRadius;
     private int mStaticDotRadius;
     private int mVisibleState = STATE_ICON;
@@ -110,6 +111,8 @@
     private OnVisibilityChangedListener mOnVisibilityChangedListener;
     private int mDrawableColor;
     private int mIconColor;
+    private int mDecorColor;
+    private float mDarkAmount;
     private ValueAnimator mColorAnimator;
     private int mCurrentSetColor = NO_COLOR;
     private int mAnimationStartColor = NO_COLOR;
@@ -119,6 +122,7 @@
                 animation.getAnimatedFraction());
         setColorInternal(newColor);
     };
+    private final NotificationIconDozeHelper mDozer;
 
     public StatusBarIconView(Context context, String slot, Notification notification) {
         this(context, slot, notification, false);
@@ -127,6 +131,7 @@
     public StatusBarIconView(Context context, String slot, Notification notification,
             boolean blocked) {
         super(context);
+        mDozer = new NotificationIconDozeHelper(context);
         mBlocked = blocked;
         mSlot = slot;
         mNumberPain = new Paint();
@@ -190,6 +195,7 @@
 
     public StatusBarIconView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mDozer = new NotificationIconDozeHelper(context);
         mBlocked = false;
         mAlwaysScaleIcon = true;
         updateIconScale();
@@ -466,7 +472,19 @@
      * to the drawable.
      */
     public void setDecorColor(int iconTint) {
-        mDotPaint.setColor(iconTint);
+        mDecorColor = iconTint;
+        updateDecorColor();
+    }
+
+    private void updateDecorColor() {
+        int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
+        if (mDotPaint.getColor() != color) {
+            mDotPaint.setColor(color);
+
+            if (mDotAppearAmount != 0) {
+                invalidate();
+            }
+        }
     }
 
     /**
@@ -477,6 +495,7 @@
         mDrawableColor = color;
         setColorInternal(color);
         mIconColor = color;
+        mDozer.setColor(color);
     }
 
     private void setColorInternal(int color) {
@@ -649,6 +668,14 @@
         mOnVisibilityChangedListener = listener;
     }
 
+    public void setDark(boolean dark, boolean fade, long delay) {
+        mDozer.setImageDark(this, dark, fade, delay, mIconColor != NO_COLOR);
+        mDozer.setIntensityDark(f -> {
+            mDarkAmount = f;
+            updateDecorColor();
+        }, dark, fade, delay);
+    }
+
     public interface OnVisibilityChangedListener {
         void onVisibilityChanged(int newVisibility);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 3efa29f..bca4b43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -18,7 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
+import android.content.Context;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.view.View;
@@ -38,8 +38,8 @@
     private boolean mIsLegacy;
     private int mLegacyColor;
 
-    protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
-        super(view, row);
+    protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
+        super(ctx, view, row);
         mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION);
         mLegacyColor = row.getContext().getColor(R.color.notification_legacy_background_color);
     }
@@ -67,13 +67,11 @@
     }
 
     protected void fadeGrayscale(final boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                updateGrayscaleMatrix((float) animation.getAnimatedValue());
-                mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
-                mView.setLayerPaint(mGreyPaint);
-            }
+        getDozer().startIntensityAnimation(animation -> {
+            getDozer().updateGrayscaleMatrix((float) animation.getAnimatedValue());
+            mGreyPaint.setColorFilter(
+                    new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
+            mView.setLayerPaint(mGreyPaint);
         }, dark, delay, new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -86,9 +84,9 @@
 
     protected void updateGrayscale(boolean dark) {
         if (dark) {
-            updateGrayscaleMatrix(1f);
+            getDozer().updateGrayscaleMatrix(1f);
             mGreyPaint.setColorFilter(
-                    new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+                    new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
             mView.setLayerPaint(mGreyPaint);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
new file mode 100644
index 0000000..d592c5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.widget.ImageView;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+
+import java.util.function.Consumer;
+
+public class NotificationDozeHelper {
+    private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
+
+    public void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateGrayscaleMatrix((float) animation.getAnimatedValue());
+                target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+            }
+        }, dark, delay, new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!dark) {
+                    target.setColorFilter(null);
+                }
+            }
+        });
+    }
+
+    public void updateGrayscale(ImageView target, boolean dark) {
+        if (dark) {
+            updateGrayscaleMatrix(1f);
+            target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+        } else {
+            target.setColorFilter(null);
+        }
+    }
+
+    public void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
+            boolean dark, long delay, Animator.AnimatorListener listener) {
+        float startIntensity = dark ? 0f : 1f;
+        float endIntensity = dark ? 1f : 0f;
+        ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+        animator.addUpdateListener(updateListener);
+        animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+        animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        animator.setStartDelay(delay);
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        animator.start();
+    }
+
+    public void setIntensityDark(Consumer<Float> listener, boolean dark,
+            boolean animate, long delay) {
+        if (animate) {
+            startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dark, delay,
+                    null /* listener */);
+        } else {
+            listener.accept(dark ? 1f : 0f);
+        }
+    }
+
+    public void updateGrayscaleMatrix(float intensity) {
+        mGrayscaleColorMatrix.setSaturation(1 - intensity);
+    }
+
+    public ColorMatrix getGrayscaleColorMatrix() {
+        return mGrayscaleColorMatrix;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 38e4ec1..1ffc944 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -16,17 +16,10 @@
 
 package com.android.systemui.statusbar.notification;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.app.Notification;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.ColorFilter;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Drawable;
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -37,7 +30,6 @@
 import android.widget.TextView;
 
 import com.android.systemui.Interpolators;
-import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
@@ -55,10 +47,6 @@
 
     private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
             = new PathInterpolator(0.4f, 0f, 0.7f, 1f);
-    private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
-            0, PorterDuff.Mode.SRC_ATOP);
-    private final int mIconDarkAlpha;
-    private final int mIconDarkColor = 0xffffffff;
 
     protected final ViewInvertHelper mInvertHelper;
     protected final ViewTransformationHelper mTransformationHelper;
@@ -74,8 +62,7 @@
     private boolean mTransformLowPriorityTitle;
 
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
-        super(view, row);
-        mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
+        super(ctx, view, row);
         mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
         mTransformationHelper = new ViewTransformationHelper();
 
@@ -108,6 +95,16 @@
         updateInvertHelper();
     }
 
+    @Override
+    protected NotificationDozeHelper createDozer(Context ctx) {
+        return new NotificationIconDozeHelper(ctx);
+    }
+
+    @Override
+    protected NotificationIconDozeHelper getDozer() {
+        return (NotificationIconDozeHelper) super.getDozer();
+    }
+
     protected void resolveHeaderViews() {
         mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
         mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
@@ -116,6 +113,7 @@
         mColor = resolveColor(mExpandButton);
         mNotificationHeader = (NotificationHeaderView) mView.findViewById(
                 com.android.internal.R.id.notification_header);
+        getDozer().setColor(mColor);
     }
 
     private int resolveColor(ImageView icon) {
@@ -223,90 +221,8 @@
             // It also may lead to bugs where the icon isn't correctly greyed out.
             boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
                     != NotificationHeaderView.NO_COLOR;
-            if (fade) {
-                if (hadColorFilter) {
-                    fadeIconColorFilter(mIcon, dark, delay);
-                    fadeIconAlpha(mIcon, dark, delay);
-                } else {
-                    fadeGrayscale(mIcon, dark, delay);
-                }
-            } else {
-                if (hadColorFilter) {
-                    updateIconColorFilter(mIcon, dark);
-                    updateIconAlpha(mIcon, dark);
-                } else {
-                    updateGrayscale(mIcon, dark);
-                }
-            }
-        }
-    }
 
-    private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                updateIconColorFilter(target, (Float) animation.getAnimatedValue());
-            }
-        }, dark, delay, null /* listener */);
-    }
-
-    private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float t = (float) animation.getAnimatedValue();
-                target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
-            }
-        }, dark, delay, null /* listener */);
-    }
-
-    protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                updateGrayscaleMatrix((float) animation.getAnimatedValue());
-                target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
-            }
-        }, dark, delay, new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!dark) {
-                    target.setColorFilter(null);
-                }
-            }
-        });
-    }
-
-    private void updateIconColorFilter(ImageView target, boolean dark) {
-        updateIconColorFilter(target, dark ? 1f : 0f);
-    }
-
-    private void updateIconColorFilter(ImageView target, float intensity) {
-        int color = interpolateColor(mColor, mIconDarkColor, intensity);
-        mIconColorFilter.setColor(color);
-        Drawable iconDrawable = target.getDrawable();
-
-        // Also, the notification might have been modified during the animation, so background
-        // might be null here.
-        if (iconDrawable != null) {
-            Drawable d = iconDrawable.mutate();
-            // DrawableContainer ignores the color filter if it's already set, so clear it first to
-            // get it set and invalidated properly.
-            d.setColorFilter(null);
-            d.setColorFilter(mIconColorFilter);
-        }
-    }
-
-    private void updateIconAlpha(ImageView target, boolean dark) {
-        target.setImageAlpha(dark ? mIconDarkAlpha : 255);
-    }
-
-    protected void updateGrayscale(ImageView target, boolean dark) {
-        if (dark) {
-            updateGrayscaleMatrix(1f);
-            target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
-        } else {
-            target.setColorFilter(null);
+            getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter);
         }
     }
 
@@ -316,22 +232,6 @@
         mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
     }
 
-    private static int interpolateColor(int source, int target, float t) {
-        int aSource = Color.alpha(source);
-        int rSource = Color.red(source);
-        int gSource = Color.green(source);
-        int bSource = Color.blue(source);
-        int aTarget = Color.alpha(target);
-        int rTarget = Color.red(target);
-        int gTarget = Color.green(target);
-        int bTarget = Color.blue(target);
-        return Color.argb(
-                (int) (aSource * (1f - t) + aTarget * t),
-                (int) (rSource * (1f - t) + rTarget * t),
-                (int) (gSource * (1f - t) + gTarget * t),
-                (int) (bSource * (1f - t) + bTarget * t));
-    }
-
     @Override
     public NotificationHeaderView getNotificationHeader() {
         return mNotificationHeader;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
new file mode 100644
index 0000000..9f79ef2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+public class NotificationIconDozeHelper extends NotificationDozeHelper {
+
+    private final int mImageDarkAlpha;
+    private final int mImageDarkColor = 0xffffffff;
+    private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter(
+            0, PorterDuff.Mode.SRC_ATOP);
+
+    private int mColor = Color.BLACK;
+
+    public NotificationIconDozeHelper(Context ctx) {
+        mImageDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
+    }
+
+    public void setColor(int color) {
+        mColor = color;
+    }
+
+    public void setImageDark(ImageView target, boolean dark, boolean fade, long delay,
+            boolean useGrayscale) {
+        if (fade) {
+            if (!useGrayscale) {
+                fadeImageColorFilter(target, dark, delay);
+                fadeImageAlpha(target, dark, delay);
+            } else {
+                fadeGrayscale(target, dark, delay);
+            }
+        } else {
+            if (!useGrayscale) {
+                updateImageColorFilter(target, dark);
+                updateImageAlpha(target, dark);
+            } else {
+                updateGrayscale(target, dark);
+            }
+        }
+    }
+
+    private void fadeImageColorFilter(final ImageView target, boolean dark, long delay) {
+        startIntensityAnimation(animation -> {
+            updateImageColorFilter(target, (Float) animation.getAnimatedValue());
+        }, dark, delay, null /* listener */);
+    }
+
+    private void fadeImageAlpha(final ImageView target, boolean dark, long delay) {
+        startIntensityAnimation(animation -> {
+            float t = (float) animation.getAnimatedValue();
+            target.setImageAlpha((int) (255 * (1f - t) + mImageDarkAlpha * t));
+        }, dark, delay, null /* listener */);
+    }
+
+    private void updateImageColorFilter(ImageView target, boolean dark) {
+        updateImageColorFilter(target, dark ? 1f : 0f);
+    }
+
+    private void updateImageColorFilter(ImageView target, float intensity) {
+        int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity);
+        mImageColorFilter.setColor(color);
+        Drawable imageDrawable = target.getDrawable();
+
+        // Also, the notification might have been modified during the animation, so background
+        // might be null here.
+        if (imageDrawable != null) {
+            Drawable d = imageDrawable.mutate();
+            // DrawableContainer ignores the color filter if it's already set, so clear it first to
+            // get it set and invalidated properly.
+            d.setColorFilter(null);
+            d.setColorFilter(mImageColorFilter);
+        }
+    }
+
+    private void updateImageAlpha(ImageView target, boolean dark) {
+        target.setImageAlpha(dark ? mImageDarkAlpha : 255);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 846d03a..f0b6b2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification;
 
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Color;
 import android.service.notification.StatusBarNotification;
@@ -46,7 +45,8 @@
     private int mContentHeight;
     private int mMinHeightHint;
 
-    protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
+    protected NotificationTemplateViewWrapper(Context ctx, View view,
+            ExpandableNotificationRow row) {
         super(ctx, view, row);
         mTransformationHelper.setCustomTransformation(
                 new ViewTransformationHelper.CustomTransformation() {
@@ -154,16 +154,20 @@
         // This also clears the existing types
         super.updateTransformedTypes();
         if (mTitle != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
+                    mTitle);
         }
         if (mText != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
+                    mText);
         }
         if (mPicture != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
+                    mPicture);
         }
         if (mProgressBar != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar);
+            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
+                    mProgressBar);
         }
     }
 
@@ -173,7 +177,7 @@
             return;
         }
         super.setDark(dark, fade, delay);
-        setPictureGrayscale(dark, fade, delay);
+        setPictureDark(dark, fade, delay);
         setProgressBarDark(dark, fade, delay);
     }
 
@@ -188,12 +192,9 @@
     }
 
     private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float t = (float) animation.getAnimatedValue();
-                updateProgressDark(target, t);
-            }
+        getDozer().startIntensityAnimation(animation -> {
+            float t = (float) animation.getAnimatedValue();
+            updateProgressDark(target, t);
         }, dark, delay, null /* listener */);
     }
 
@@ -207,13 +208,9 @@
         updateProgressDark(target, dark ? 1f : 0f);
     }
 
-    protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
+    private void setPictureDark(boolean dark, boolean fade, long delay) {
         if (mPicture != null) {
-            if (fade) {
-                fadeGrayscale(mPicture, grayscale, delay);
-            } else {
-                updateGrayscale(mPicture, grayscale);
-            }
+            getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index c85e8d8..c86616b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -16,24 +16,17 @@
 
 package com.android.systemui.statusbar.notification;
 
-import android.animation.Animator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Color;
-import android.graphics.ColorMatrix;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
 import android.support.v4.graphics.ColorUtils;
 import android.view.NotificationHeaderView;
 import android.view.View;
 
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 /**
  * Wraps the actual notification content view; used to implement behaviors which are different for
@@ -41,14 +34,14 @@
  */
 public abstract class NotificationViewWrapper implements TransformableView {
 
-    protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
     protected final View mView;
     protected final ExpandableNotificationRow mRow;
+    private final NotificationDozeHelper mDozer;
+
     protected boolean mDark;
     private int mBackgroundColor = 0;
     protected boolean mShouldInvertDark;
     protected boolean mDarkInitialized = false;
-    private boolean mForcedInvisible;
 
     public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
@@ -65,13 +58,22 @@
         } else if (v instanceof NotificationHeaderView) {
             return new NotificationHeaderViewWrapper(ctx, v, row);
         } else {
-            return new NotificationCustomViewWrapper(v, row);
+            return new NotificationCustomViewWrapper(ctx, v, row);
         }
     }
 
-    protected NotificationViewWrapper(View view, ExpandableNotificationRow row) {
+    protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         mView = view;
         mRow = row;
+        mDozer = createDozer(ctx);
+    }
+
+    protected NotificationDozeHelper createDozer(Context ctx) {
+        return new NotificationIconDozeHelper(mView.getContext());
+    }
+
+    protected NotificationDozeHelper getDozer() {
+        return mDozer;
     }
 
     /**
@@ -112,26 +114,6 @@
                 || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
     }
 
-
-    protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
-            boolean dark, long delay, Animator.AnimatorListener listener) {
-        float startIntensity = dark ? 0f : 1f;
-        float endIntensity = dark ? 1f : 0f;
-        ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
-        animator.addUpdateListener(updateListener);
-        animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
-        animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        animator.setStartDelay(delay);
-        if (listener != null) {
-            animator.addListener(listener);
-        }
-        animator.start();
-    }
-
-    protected void updateGrayscaleMatrix(float intensity) {
-        mGrayscaleColorMatrix.setSaturation(1 - intensity);
-    }
-
     /**
      * Update the appearance of the expand button.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 820638c..c30bb9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -74,7 +74,7 @@
         if (mUserListener == null) {
             return false;
         }
-        return mUserListener.getUserCount() > 1;
+        return mUserListener.getUserCount() != 0;
     }
 
     public void setUserSwitcherController(UserSwitcherController userSwitcherController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 3706dc8..dee15d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -95,7 +95,7 @@
     private int mActualLayoutWidth = NO_VALUE;
     private float mActualPaddingEnd = NO_VALUE;
     private float mActualPaddingStart = NO_VALUE;
-    private boolean mCentered;
+    private boolean mDark;
     private boolean mChangingViewPositions;
     private int mAddAnimationStartIndex = -1;
     private int mCannedAnimationStartIndex = -1;
@@ -183,6 +183,9 @@
                 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
             }
         }
+        if (mDark && child instanceof StatusBarIconView) {
+            ((StatusBarIconView) child).setDark(mDark, false, 0);
+        }
     }
 
     @Override
@@ -312,7 +315,8 @@
                 numDots++;
             }
         }
-        if (mCentered && translationX < getLayoutEnd()) {
+        boolean center = mDark;
+        if (center && translationX < getLayoutEnd()) {
             float delta = (getLayoutEnd() - translationX) / 2;
             for (int i = 0; i < childCount; i++) {
                 View view = getChildAt(i);
@@ -390,9 +394,15 @@
         mChangingViewPositions = changingViewPositions;
     }
 
-    public void setAmbient(boolean ambient) {
-        mCentered = ambient;
+    public void setDark(boolean dark, boolean fade, long delay) {
+        mDark = dark;
         mDisallowNextAnimation = true;
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            if (view instanceof StatusBarIconView) {
+                ((StatusBarIconView) view).setDark(dark, fade, delay);
+            }
+        }
     }
 
     public IconState getIconState(StatusBarIconView icon) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 55d661c..b40e709 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5384,9 +5384,10 @@
             ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
         // Use a FileObserver to detect when traces finish writing.
         // The order of traces is considered important to maintain for legibility.
+        final boolean[] closed = new boolean[1];
         FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
             @Override
-            public synchronized void onEvent(int event, String path) { notify(); }
+            public synchronized void onEvent(int event, String path) { closed[0] = true; notify(); }
         };
 
         try {
@@ -5403,6 +5404,7 @@
                             final long sime = SystemClock.elapsedRealtime();
                             Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
                             observer.wait(1000);  // Wait for write-close, give up after 1 sec
+                            if (!closed[0]) Slog.w(TAG, "Didn't see close of " + tracesPath);
                             if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
                                     + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                         }
@@ -21493,6 +21495,15 @@
         return success;
     }
 
+    private boolean isEphemeralLocked(int uid) {
+        String packages[] = mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
+            return false;
+        }
+        return getPackageManagerInternalLocked().isPackageEphemeral(UserHandle.getUserId(uid),
+                packages[0]);
+    }
+
     private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
         final UidRecord.ChangeItem pendingChange;
         if (uidRec == null || uidRec.pendingChange == null) {
@@ -21533,7 +21544,7 @@
         pendingChange.change = change;
         pendingChange.processState = uidRec != null
                 ? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
-        pendingChange.ephemeral = uidRec.ephemeral;
+        pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
         pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
 
         // Directly update the power manager, since we sit on top of it and it is critical
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 36a913f..f927cce 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -857,17 +857,26 @@
 
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
 
-        String[] nativeProcs = NATIVE_STACKS_OF_INTEREST;
-        // don't dump native PIDs for background ANRs
-        File tracesFile = null;
+        // don't dump native PIDs for background ANRs unless it is the process of interest
+        String[] nativeProcs = null;
         if (isSilentANR) {
-            tracesFile = mService.dumpStackTraces(true, firstPids, null, lastPids,
-                null);
+            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
+                if (NATIVE_STACKS_OF_INTEREST[i].equals(app.processName)) {
+                    nativeProcs = new String[] { app.processName };
+                    break;
+                }
+            }
         } else {
-            tracesFile = mService.dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
-                nativeProcs);
+            nativeProcs = NATIVE_STACKS_OF_INTEREST;
         }
 
+        // For background ANRs, don't pass the ProcessCpuTracker to
+        // avoid spending 1/2 second collecting stats to rank lastPids.
+        File tracesFile = mService.dumpStackTraces(true, firstPids,
+                                                   (isSilentANR) ? null : processCpuTracker,
+                                                   (isSilentANR) ? null : lastPids,
+                                                   nativeProcs);
+
         String cpuInfo = null;
         if (ActivityManagerService.MONITOR_CPU_USAGE) {
             mService.updateCpuStatsNow();
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index b316a3b..940f621 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -1106,8 +1106,7 @@
                         JobStatus runNow = (JobStatus) message.obj;
                         // runNow can be null, which is a controller's way of indicating that its
                         // state is such that all ready jobs should be run immediately.
-                        if (runNow != null && !mPendingJobs.contains(runNow)
-                                && mJobs.containsJob(runNow)) {
+                        if (runNow != null && isReadyToBeExecutedLocked(runNow)) {
                             mJobPackageTracker.notePending(runNow);
                             mPendingJobs.add(runNow);
                         }
@@ -1310,12 +1309,27 @@
          *      - The component is enabled and runnable.
          */
         private boolean isReadyToBeExecutedLocked(JobStatus job) {
+            final boolean jobExists = mJobs.containsJob(job);
             final boolean jobReady = job.isReady();
             final boolean jobPending = mPendingJobs.contains(job);
             final boolean jobActive = isCurrentlyActiveLocked(job);
 
             final int userId = job.getUserId();
             final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
+
+            if (DEBUG) {
+                Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+                        + " exists=" + jobExists
+                        + " ready=" + jobReady + " pending=" + jobPending
+                        + " active=" + jobActive + " userStarted=" + userStarted);
+            }
+
+            // Short circuit: don't do the expensive PM check unless we really think
+            // we might need to run this job now.
+            if (!jobExists || !userStarted || !jobReady || jobPending || jobActive) {
+                return false;
+            }
+
             final boolean componentPresent;
             try {
                 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
@@ -1327,11 +1341,11 @@
 
             if (DEBUG) {
                 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
-                        + " ready=" + jobReady + " pending=" + jobPending
-                        + " active=" + jobActive + " userStarted=" + userStarted
                         + " componentPresent=" + componentPresent);
             }
-            return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
+
+            // Everything else checked out so far, so this is the final yes/no check
+            return componentPresent;
         }
 
         /**
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 6af1c3b..db133f8 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -130,7 +130,7 @@
         // installed and should be removed
         final int storedOverlayInfosSize = storedOverlayInfos.size();
         for (int i = 0; i < storedOverlayInfosSize; i++) {
-            final OverlayInfo oi = storedOverlayInfos.get(i);
+            final OverlayInfo oi = storedOverlayInfos.valueAt(i);
             mSettings.remove(oi.packageName, oi.userId);
             removeIdmapIfPossible(oi);
             packagesToUpdateAssets.add(oi.targetPackageName);
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index d8900c0..d364d17 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -290,8 +290,8 @@
                             PackageManagerService.REASON_BACKGROUND_DEXOPT,
                             /* force */ false)
                     : pm.performDexOptSecondary(pkg,
-                            PackageManagerServiceCompilerMapping.getFullCompilerFilter(),
-                            /* force */ true);
+                            PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                            /* force */ false);
             if (success) {
                 // Dexopt succeeded, remove package from the list of failing ones.
                 synchronized (failedPackageNames) {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index aac04da..126f8c4 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -214,6 +214,7 @@
         if (getAvailableSpace() > 0) {
             dexoptCommandCountExecuted++;
 
+            Log.d(TAG, "Next command: " + next);
             return next;
         } else {
             if (DEBUG_DEXOPT) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0ec85aa..2e4a3a3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -915,7 +915,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, null,
+        final PackageLite pkg = new PackageLite(null, baseApk, null, null, 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 1d1bf0d..2882d20 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8458,6 +8458,11 @@
         return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
     }
 
+    public boolean performDexOptSecondary(String packageName, int compileReason,
+            boolean force) {
+        return mDexManager.dexoptSecondaryDex(packageName, compileReason, force);
+    }
+
     /**
      * Reconcile the information we have about the secondary dex files belonging to
      * {@code packagName} and the actual dex files. For all dex files that were
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 751d9af..25a596a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -171,7 +171,8 @@
             if (file.isFile()) {
                 try {
                     ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null);
+                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+                            null, null);
                     params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
                             pkgLite, false, params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1f97d7d..a31258c3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1493,6 +1493,10 @@
                     listeners[i].onUserRestrictionsChanged(userId,
                             newRestrictionsFinal, prevRestrictionsFinal);
                 }
+
+                final Intent broadcast = new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)
+                        .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                mContext.sendBroadcastAsUser(broadcast, UserHandle.of(userId));
             }
         });
     }
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 83dd392..a904d17 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -279,6 +279,17 @@
      * @return true if all secondary dex files were processed successfully (compiled or skipped
      *         because they don't need to be compiled)..
      */
+    public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force) {
+        return dexoptSecondaryDex(packageName,
+                PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason),
+                force);
+    }
+
+    /**
+     * Perform dexopt on the package {@code packageName} secondary dex files.
+     * @return true if all secondary dex files were processed successfully (compiled or skipped
+     *         because they don't need to be compiled)..
+     */
     public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
         // Select the dex optimizer based on the force parameter.
         // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
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 8987ac1..721d337 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -157,7 +157,6 @@
         super.setUp();
 
         mContext = getContext();
-
         when(mContext.packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
                 .thenReturn(true);
 
@@ -174,6 +173,12 @@
         setUpUserManager();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        flushTasks();
+        super.tearDown();
+    }
+
     private void initializeDpms() {
         // Need clearCallingIdentity() to pass permission checks.
         final long ident = mContext.binder.clearCallingIdentity();
@@ -1246,7 +1251,7 @@
         mContext.applicationInfo = new ApplicationInfo();
         mContext.callerPermissions.add(permission.MANAGE_USERS);
         mContext.packageName = "com.android.frameworks.servicestests";
-        mContext.userContexts.put(user, mContext);
+        mContext.addPackageContext(user, mContext);
         when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
 
         StringParceledListSlice oneCert = asSlice(new String[] {"1"});
@@ -3966,121 +3971,168 @@
     }
 
     public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
+        mContext.packageName = mRealTestContext.getPackageName();
         setDeviceOwner();
 
-        mContext.packageName = admin1.getPackageName();
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-        verifyCanGetOwnerInstalledCaCerts(admin1);
+        final DpmMockContext caller = new DpmMockContext(mRealTestContext, "test-caller");
+        caller.packageName = admin1.getPackageName();
+        caller.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+
+        verifyCanGetOwnerInstalledCaCerts(admin1, caller);
     }
 
     public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
+        mContext.packageName = mRealTestContext.getPackageName();
         setAsProfileOwner(admin1);
 
-        mContext.packageName = admin1.getPackageName();
-        verifyCanGetOwnerInstalledCaCerts(admin1);
-        verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1);
+        final DpmMockContext caller = new DpmMockContext(mRealTestContext, "test-caller");
+        caller.packageName = admin1.getPackageName();
+        caller.binder.callingUid = DpmMockContext.CALLER_UID;
+
+        verifyCanGetOwnerInstalledCaCerts(admin1, caller);
+        verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1, caller);
     }
 
     public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
+        mContext.packageName = mRealTestContext.getPackageName();
         setAsProfileOwner(admin1);
 
         final String delegate = "com.example.delegate";
         final int delegateUid = setupPackageInPackageManager(delegate, 20988);
         dpm.setCertInstallerPackage(admin1, delegate);
 
-        mContext.packageName = delegate;
-        mContext.binder.callingUid = delegateUid;
-        verifyCanGetOwnerInstalledCaCerts(null);
-        verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null);
+        final DpmMockContext caller = new DpmMockContext(mRealTestContext, "test-caller");
+        caller.packageName = delegate;
+        caller.binder.callingUid = delegateUid;
+
+        verifyCanGetOwnerInstalledCaCerts(null, caller);
+        verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null, caller);
     }
 
-    private void verifyCanGetOwnerInstalledCaCerts(ComponentName caller) throws Exception {
-        final UserHandle user = UserHandle.getUserHandleForUid(mContext.binder.callingUid);
-        final int ownerUid = user.equals(UserHandle.SYSTEM) ?
-                DpmMockContext.CALLER_SYSTEM_USER_UID : DpmMockContext.CALLER_UID;
-
-        mContext.applicationInfo = new ApplicationInfo();
-        mContext.userContexts.put(user, mContext);
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
-
-        // Install a CA cert.
+    private void verifyCanGetOwnerInstalledCaCerts(
+            final ComponentName caller, final DpmMockContext callerContext) throws Exception {
         final String alias = "cert";
         final byte[] caCert = TEST_CA.getBytes();
-        when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
-                .thenReturn(alias);
-        assertTrue(dpm.installCaCert(caller, caCert));
-        when(mContext.keyChainConnection.getService().getUserCaAliases())
-                .thenReturn(asSlice(new String[] {alias}));
-        mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+
+        // device admin (used for posting the tls notification)
+        final DpmMockContext admin1Context;
+        if (admin1.getPackageName().equals(callerContext.getPackageName())) {
+            admin1Context = callerContext;
+        } else {
+            admin1Context = new DpmMockContext(mRealTestContext, "test-admin");
+            admin1Context.packageName = admin1.getPackageName();
+            admin1Context.applicationInfo = new ApplicationInfo();
+        }
+        when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+        // caller: device admin or delegated certificate installer
+        callerContext.applicationInfo = new ApplicationInfo();
+        final UserHandle callerUser = callerContext.binder.getCallingUserHandle();
+
+        // system_server
+        final DpmMockContext serviceContext = mContext;
+        serviceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        serviceContext.addPackageContext(callerUser, admin1Context);
+        serviceContext.addPackageContext(callerUser, callerContext);
+
+        // Install a CA cert.
+        runAsCaller(callerContext, dpms, (dpm) -> {
+            when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+                        .thenReturn(alias);
+            assertTrue(dpm.installCaCert(caller, caCert));
+            when(mContext.keyChainConnection.getService().getUserCaAliases())
+                    .thenReturn(asSlice(new String[] {alias}));
+
+        });
+
+        serviceContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()));
         flushTasks();
 
+        final List<String> ownerInstalledCaCerts = new ArrayList<>();
+
         // Device Owner / Profile Owner can find out which CA certs were installed by itself.
-        final String packageName = mContext.packageName;
-        mContext.packageName = admin1.getPackageName();
-        final long callerIdentity = mContext.binder.clearCallingIdentity();
-        mContext.binder.callingUid = ownerUid;
-        List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
-        assertNotNull(ownerInstalledCaCerts);
-        assertEquals(1, ownerInstalledCaCerts.size());
-        assertTrue(ownerInstalledCaCerts.contains(alias));
+        runAsCaller(admin1Context, dpms, (dpm) -> {
+            final List<String> installedCaCerts = dpm.getOwnerInstalledCaCerts(callerUser);
+            assertEquals(Arrays.asList(alias), installedCaCerts);
+            ownerInstalledCaCerts.addAll(installedCaCerts);
+        });
 
         // Restarting the DPMS should not lose information.
         initializeDpms();
-        assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+        runAsCaller(admin1Context, dpms, (dpm) -> {
+            assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(callerUser));
+        });
 
         // System can find out which CA certs were installed by the Device Owner / Profile Owner.
-        mContext.packageName = "com.android.frameworks.servicestests";
-        mContext.binder.clearCallingIdentity();
-        assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+        runAsCaller(serviceContext, dpms, (dpm) -> {
+            assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(callerUser));
 
-        // Remove the CA cert.
-        mContext.packageName = packageName;
-        mContext.binder.restoreCallingIdentity(callerIdentity);
-        reset(mContext.keyChainConnection.getService());
-        mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+            // Remove the CA cert.
+            reset(mContext.keyChainConnection.getService());
+        });
+
+        serviceContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()));
         flushTasks();
 
         // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
         // Owner.
-        mContext.packageName = admin1.getPackageName();
-        mContext.binder.callingUid = ownerUid;
-        ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
-        assertNotNull(ownerInstalledCaCerts);
-        assertTrue(ownerInstalledCaCerts.isEmpty());
-
-        mContext.packageName = packageName;
-        mContext.binder.restoreCallingIdentity(callerIdentity);
+        runAsCaller(admin1Context, dpms, (dpm) -> {
+            MoreAsserts.assertEmpty(dpm.getOwnerInstalledCaCerts(callerUser));
+        });
     }
 
-    private void verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(ComponentName caller)
-            throws Exception {
-        final UserHandle user = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
-
-        mContext.applicationInfo = new ApplicationInfo();
-        mContext.userContexts.put(user, mContext);
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
-
-        // Install a CA cert.
+    private void verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(
+            final ComponentName callerName, final DpmMockContext callerContext) throws Exception {
         final String alias = "cert";
         final byte[] caCert = TEST_CA.getBytes();
-        when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
-                .thenReturn(alias);
-        assertTrue(dpm.installCaCert(caller, caCert));
-        when(mContext.keyChainConnection.getService().getUserCaAliases())
+
+        // device admin (used for posting the tls notification)
+        final DpmMockContext admin1Context;
+        if (admin1.getPackageName().equals(callerContext.getPackageName())) {
+            admin1Context = callerContext;
+        } else {
+            admin1Context = new DpmMockContext(mRealTestContext, "test-admin");
+            admin1Context.packageName = admin1.getPackageName();
+            admin1Context.applicationInfo = new ApplicationInfo();
+        }
+        when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+        // caller: device admin or delegated certificate installer
+        callerContext.applicationInfo = new ApplicationInfo();
+        final UserHandle callerUser = callerContext.binder.getCallingUserHandle();
+
+        // system_server
+        final DpmMockContext serviceContext = mContext;
+        serviceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        serviceContext.addPackageContext(callerUser, admin1Context);
+        serviceContext.addPackageContext(callerUser, callerContext);
+
+        // Install a CA cert as caller
+        runAsCaller(callerContext, dpms, (dpm) -> {
+            when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+                    .thenReturn(alias);
+            assertTrue(dpm.installCaCert(callerName, caCert));
+        });
+
+        // Fake the CA cert as having been installed
+        when(serviceContext.keyChainConnection.getService().getUserCaAliases())
                 .thenReturn(asSlice(new String[] {alias}));
-        mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+        serviceContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, callerUser.getIdentifier()));
         flushTasks();
 
-        // Removing the Profile Owner should clear the information which CA certs were installed
-        // by it.
-        mContext.packageName = admin1.getPackageName();
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-        dpm.clearProfileOwner(admin1);
-        mContext.packageName = "com.android.frameworks.servicestests";
-        mContext.binder.clearCallingIdentity();
-        final List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
-        assertNotNull(ownerInstalledCaCerts);
-        assertTrue(ownerInstalledCaCerts.isEmpty());
+        // Removing the Profile Owner should clear the information on which CA certs were installed
+        runAsCaller(admin1Context, dpms, (dpm) -> {
+            dpm.clearProfileOwner(admin1);
+        });
+
+        runAsCaller(serviceContext, dpms, (dpm) -> {
+            final List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(callerUser);
+            assertNotNull(ownerInstalledCaCerts);
+            assertTrue(ownerInstalledCaCerts.isEmpty());
+        });
     }
 
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
@@ -4147,29 +4199,11 @@
     }
 
     private void flushTasks() throws Exception {
-        Boolean tasksFlushed[] = new Boolean[] {false};
-        final Runnable tasksFlushedNotifier = () -> {
-            synchronized (tasksFlushed) {
-                tasksFlushed[0] = true;
-                tasksFlushed.notify();
-            }
-        };
+        dpms.mHandler.runWithScissors(() -> {}, 0 /*now*/);
+        dpms.mBackgroundHandler.runWithScissors(() -> {}, 0 /*now*/);
 
-        // Flush main thread handler.
-        dpms.mHandler.post(tasksFlushedNotifier);
-        synchronized (tasksFlushed) {
-            if (!tasksFlushed[0]) {
-                tasksFlushed.wait();
-            }
-        }
-
-        // Flush background thread handler.
-        tasksFlushed[0] = false;
-        dpms.mBackgroundHandler.post(tasksFlushedNotifier);
-        synchronized (tasksFlushed) {
-            if (!tasksFlushed[0]) {
-                tasksFlushed.wait();
-            }
-        }
+        // We can't let exceptions happen on the background thread. Throw them here if they happen
+        // so they still cause the test to fail despite being suppressed.
+        mContext.rethrowBackgroundBroadcastExceptions();
     }
 }
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 7d017c5..1ea12d8 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -48,6 +48,7 @@
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 import android.util.ArrayMap;
+import android.util.Pair;
 import android.view.IWindowManager;
 
 import com.android.internal.widget.LockPatternUtils;
@@ -61,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -111,6 +113,7 @@
     public static class MockBinder {
         public int callingUid = CALLER_UID;
         public int callingPid = CALLER_PID;
+        public final Map<Integer, List<String>> callingPermissions = new ArrayMap<>();
 
         public long clearCallingIdentity() {
             final long token = (((long) callingUid) << 32) | (callingPid);
@@ -303,14 +306,19 @@
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
 
+    /** TODO: Migrate everything to use {@link #permissions} to avoid confusion. */
+    @Deprecated
     public final List<String> callerPermissions = new ArrayList<>();
 
+    /** Less confusing alias for {@link #callerPermissions}. */
+    public final List<String> permissions = callerPermissions;
+
     private final ArrayList<UserInfo> mUserInfos = new ArrayList<>();
 
     public final BuildMock buildMock = new BuildMock();
 
     /** Optional mapping of other user contexts for {@link #createPackageContextAsUser} to return */
-    public final Map<UserHandle, Context> userContexts = new ArrayMap<>();
+    public final Map<Pair<UserHandle, String>, Context> userPackageContexts = new ArrayMap<>();
 
     public String packageName = null;
 
@@ -324,6 +332,9 @@
         public final IntentFilter filter;
         public final Handler scheduler;
 
+        // Exceptions thrown in a background thread kill the whole test. Save them instead.
+        public final AtomicReference<Exception> backgroundException = new AtomicReference<>();
+
         public BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter,
                 Handler scheduler) {
             this.receiver = receiver;
@@ -337,20 +348,30 @@
                     0 /* type */, false /* ordered */, false /* sticky */, null /* token */, userId,
                     0 /* flags */);
             if (filter.match(null, intent, false, "DpmMockContext") > 0) {
-                if (scheduler != null) {
-                    scheduler.post(() -> {
-                        receiver.setPendingResult(result);
-                        receiver.onReceive(DpmMockContext.this, intent);
-                    });
-                } else {
+                final Runnable send = () -> {
                     receiver.setPendingResult(result);
                     receiver.onReceive(DpmMockContext.this, intent);
+                };
+                if (scheduler != null) {
+                    scheduler.post(() -> {
+                        try {
+                            send.run();
+                        } catch (Exception e) {
+                            backgroundException.compareAndSet(null, e);
+                        }
+                    });
+                } else {
+                    send.run();
                 }
             }
         }
     }
     private List<BroadcastReceiverRegistration> mBroadcastReceivers = new ArrayList<>();
 
+    public DpmMockContext(Context realTestContext, String name) {
+        this(realTestContext, new File(realTestContext.getCacheDir(), name));
+    }
+
     public DpmMockContext(Context context, File dataDir) {
         realTestContext = context;
 
@@ -511,13 +532,29 @@
                 .thenReturn(isRunning);
     }
 
-    public void injectBroadcast(Intent intent) {
+    public void injectBroadcast(final Intent intent) {
         final int userId = UserHandle.getUserId(binder.getCallingUid());
         for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
             receiver.sendBroadcastIfApplicable(userId, intent);
         }
     }
 
+    public void rethrowBackgroundBroadcastExceptions() throws Exception {
+        for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
+            final Exception e = receiver.backgroundException.getAndSet(null);
+            if (e != null) {
+                throw e;
+            }
+        }
+    }
+
+    public void addPackageContext(UserHandle user, Context context) {
+        if (context.getPackageName() == null) {
+            throw new NullPointerException("getPackageName() == null");
+        }
+        userPackageContexts.put(new Pair<>(user, context.getPackageName()), context);
+    }
+
     @Override
     public Resources getResources() {
         return resources;
@@ -576,7 +613,16 @@
         if (binder.getCallingUid() == SYSTEM_UID) {
             return; // Assume system has all permissions.
         }
-        if (!callerPermissions.contains(permission)) {
+
+        List<String> permissions = binder.callingPermissions.get(binder.getCallingUid());
+        if (permissions == null) {
+            // TODO: delete the following line. to do this without breaking any tests, first it's
+            //       necessary to remove all tests that set it directly.
+            permissions = callerPermissions;
+//            throw new UnsupportedOperationException(
+//                    "Caller UID " + binder.getCallingUid() + " doesn't exist");
+        }
+        if (!permissions.contains(permission)) {
             throw new SecurityException("Caller doesn't have " + permission + " : " + message);
         }
     }
@@ -751,14 +797,11 @@
     @Override
     public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
             throws PackageManager.NameNotFoundException {
-        if (!userContexts.containsKey(user)) {
-            return super.createPackageContextAsUser(packageName, flags, user);
+        final Pair<UserHandle, String> key = new Pair<>(user, packageName);
+        if (userPackageContexts.containsKey(key)) {
+            return userPackageContexts.get(key);
         }
-        if (!getPackageName().equals(packageName)) {
-            throw new UnsupportedOperationException(
-                    "Creating a context as another package is not implemented");
-        }
-        return userContexts.get(user);
+        throw new UnsupportedOperationException("No package " + packageName + " for user " + user);
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 43e2610..5d68edd 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -16,6 +16,7 @@
 
 package com.android.server.devicepolicy;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -70,6 +71,34 @@
         return mMockContext;
     }
 
+    protected interface DpmRunnable {
+        public void run(DevicePolicyManager dpm) throws Exception;
+    }
+
+    /**
+     * Simulate an RPC from {@param caller} to the service context ({@link #mContext}).
+     *
+     * The caller sees its own context. The server also sees its own separate context, with the
+     * appropriate calling UID and calling permissions fields already set up.
+     */
+    protected void runAsCaller(DpmMockContext caller, DevicePolicyManagerServiceTestable dpms,
+            DpmRunnable action) {
+        final DpmMockContext serviceContext = mMockContext;
+
+        final long origId = serviceContext.binder.clearCallingIdentity();
+        try {
+            serviceContext.binder.callingUid = caller.binder.callingUid;
+            serviceContext.binder.callingPid = caller.binder.callingPid;
+            serviceContext.binder.callingPermissions.put(caller.binder.callingUid,
+                    caller.permissions);
+            action.run(new DevicePolicyManagerTestable(caller, dpms));
+        } catch (Exception e) {
+            throw new AssertionError(e);
+        } finally {
+            serviceContext.binder.restoreCallingIdentity(origId);
+        }
+    }
+
     protected void markPackageAsInstalled(String packageName, ApplicationInfo ai, int userId)
             throws Exception {
         final PackageInfo pi = DpmTestUtils.cloneParcelable(
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 4178f4d..4da2853 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -169,6 +169,7 @@
         public AdbSettingsObserver() {
             super(null);
         }
+
         @Override
         public void onChange(boolean selfChange) {
             boolean enable = (Settings.Global.getInt(mContentResolver,
@@ -208,9 +209,9 @@
     private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-             int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
-             boolean usbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
-             mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging);
+            int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+            boolean usbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
+            mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging);
         }
     };
 
@@ -261,9 +262,10 @@
             // TV-specific notification channel
             mNotificationManager.createNotificationChannel(
                     new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV,
-                        mContext.getString(
-                            com.android.internal.R.string.adb_debugging_notification_channel_tv),
-                        NotificationManager.IMPORTANCE_HIGH));
+                            mContext.getString(
+                                    com.android.internal.R.string
+                                            .adb_debugging_notification_channel_tv),
+                            NotificationManager.IMPORTANCE_HIGH));
         }
 
         // We do not show the USB notification if the primary volume supports mass storage.
@@ -309,8 +311,8 @@
         boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
         // don't start accessory mode if our mandatory strings have not been set
         boolean enableAccessory = (mAccessoryStrings != null &&
-                        mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
-                        mAccessoryStrings[UsbAccessory.MODEL_STRING] != null);
+                mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
+                mAccessoryStrings[UsbAccessory.MODEL_STRING] != null);
         String functions = null;
 
         if (enableAccessory && enableAudio) {
@@ -341,14 +343,14 @@
         int serialLength = serial.length();
         // XOR the USB serial across the remaining 5 bytes
         for (int i = 0; i < serialLength; i++) {
-            address[i % (ETH_ALEN - 1) + 1] ^= (int)serial.charAt(i);
+            address[i % (ETH_ALEN - 1) + 1] ^= (int) serial.charAt(i);
         }
         String addrString = String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
-            address[0], address[1], address[2], address[3], address[4], address[5]);
+                address[0], address[1], address[2], address[3], address[4], address[5]);
         try {
             FileUtils.stringToFile(RNDIS_ETH_ADDR_PATH, addrString);
         } catch (IOException e) {
-           Slog.e(TAG, "failed to write to " + RNDIS_ETH_ADDR_PATH);
+            Slog.e(TAG, "failed to write to " + RNDIS_ETH_ADDR_PATH);
         }
     }
 
@@ -400,7 +402,7 @@
                 // register observer to listen for settings changes
                 mContentResolver.registerContentObserver(
                         Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
-                                false, new AdbSettingsObserver());
+                        false, new AdbSettingsObserver());
 
                 // Watch for USB configuration changes
                 mUEventObserver.startObserving(USB_STATE_MATCH);
@@ -466,9 +468,9 @@
             }
 
             SomeArgs args = SomeArgs.obtain();
-            args.argi1 = hostConnected ? 1 :0;
-            args.argi2 = sourcePower ? 1 :0;
-            args.argi3 = sinkPower ? 1 :0;
+            args.argi1 = hostConnected ? 1 : 0;
+            args.argi2 = sourcePower ? 1 : 0;
+            args.argi3 = sinkPower ? 1 : 0;
 
             removeMessages(MSG_UPDATE_HOST_STATE);
             Message msg = obtainMessage(MSG_UPDATE_HOST_STATE, args);
@@ -506,7 +508,7 @@
 
                 // Persist the adb setting
                 String newFunction = applyAdbFunction(SystemProperties.get(
-                            USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE));
+                        USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE));
                 SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, newFunction);
 
                 // Remove mtp from the config if file transfer is not enabled
@@ -529,8 +531,10 @@
          */
         private void setEnabledFunctions(String functions, boolean forceRestart,
                 boolean usbDataUnlocked) {
-            if (DEBUG) Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
-                    + "forceRestart=" + forceRestart);
+            if (DEBUG) {
+                Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
+                        + "forceRestart=" + forceRestart);
+            }
 
             if (usbDataUnlocked != mUsbDataUnlocked) {
                 mUsbDataUnlocked = usbDataUnlocked;
@@ -680,7 +684,7 @@
                 }
                 for (String key : keySet) {
                     if (intent.getBooleanExtra(key, false) !=
-                        mBroadcastedIntent.getBooleanExtra(key, false)) {
+                            mBroadcastedIntent.getBooleanExtra(key, false)) {
                         return true;
                     }
                 }
@@ -697,7 +701,8 @@
             intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
             intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
             intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
-            intent.putExtra(UsbManager.USB_DATA_UNLOCKED, isUsbTransferAllowed() && mUsbDataUnlocked);
+            intent.putExtra(UsbManager.USB_DATA_UNLOCKED,
+                    isUsbTransferAllowed() && mUsbDataUnlocked);
             intent.putExtra(UsbManager.USB_CONFIG_CHANGED, configChanged);
 
             if (mCurrentFunctions != null) {
@@ -825,7 +830,7 @@
                     setAdbEnabled(msg.arg1 == 1);
                     break;
                 case MSG_SET_CURRENT_FUNCTIONS:
-                    String functions = (String)msg.obj;
+                    String functions = (String) msg.obj;
                     setEnabledFunctions(functions, false, msg.arg1 == 1);
                     break;
                 case MSG_UPDATE_USER_RESTRICTIONS:
@@ -854,9 +859,9 @@
                     if (mCurrentUser != msg.arg1) {
                         // Restart the USB stack and re-apply user restrictions for MTP or PTP.
                         final boolean active = UsbManager.containsFunction(mCurrentFunctions,
-                                        UsbManager.USB_FUNCTION_MTP)
+                                UsbManager.USB_FUNCTION_MTP)
                                 || UsbManager.containsFunction(mCurrentFunctions,
-                                        UsbManager.USB_FUNCTION_PTP);
+                                UsbManager.USB_FUNCTION_PTP);
                         if (mUsbDataUnlocked && active && mCurrentUser != UserHandle.USER_NULL) {
                             Slog.v(TAG, "Current user switched to " + mCurrentUser
                                     + "; resetting USB host stack for MTP or PTP");
@@ -885,7 +890,9 @@
 
         private void updateUsbNotification() {
             if (mNotificationManager == null || !mUseUsbNotification
-                    || ("0".equals(SystemProperties.get("persist.charging.notify")))) return;
+                    || ("0".equals(SystemProperties.get("persist.charging.notify")))) {
+                return;
+            }
             int id = 0;
             Resources r = mContext.getResources();
             if (mConnected) {
@@ -937,18 +944,19 @@
 
                     Notification notification =
                             new Notification.Builder(mContext, SystemNotificationChannels.USB)
-                            .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                            .setWhen(0)
-                            .setOngoing(true)
-                            .setTicker(title)
-                            .setDefaults(0)  // please be quiet
-                            .setColor(mContext.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color))
-                            .setContentTitle(title)
-                            .setContentText(message)
-                            .setContentIntent(pi)
-                            .setVisibility(Notification.VISIBILITY_PUBLIC)
-                            .build();
+                                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                                    .setWhen(0)
+                                    .setOngoing(true)
+                                    .setTicker(title)
+                                    .setDefaults(0)  // please be quiet
+                                    .setColor(mContext.getColor(
+                                            com.android.internal.R.color
+                                                    .system_notification_accent_color))
+                                    .setContentTitle(title)
+                                    .setContentText(message)
+                                    .setContentIntent(pi)
+                                    .setVisibility(Notification.VISIBILITY_PUBLIC)
+                                    .build();
                     mNotificationManager.notifyAsUser(null, id, notification,
                             UserHandle.ALL);
                     mUsbNotificationId = id;
@@ -976,20 +984,21 @@
 
                     Notification notification =
                             new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER)
-                            .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                            .setWhen(0)
-                            .setOngoing(true)
-                            .setTicker(title)
-                            .setDefaults(0)  // please be quiet
-                            .setColor(mContext.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color))
-                            .setContentTitle(title)
-                            .setContentText(message)
-                            .setContentIntent(pi)
-                            .setVisibility(Notification.VISIBILITY_PUBLIC)
-                            .extend(new Notification.TvExtender()
-                                    .setChannel(ADB_NOTIFICATION_CHANNEL_ID_TV))
-                            .build();
+                                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                                    .setWhen(0)
+                                    .setOngoing(true)
+                                    .setTicker(title)
+                                    .setDefaults(0)  // please be quiet
+                                    .setColor(mContext.getColor(
+                                            com.android.internal.R.color
+                                                    .system_notification_accent_color))
+                                    .setContentTitle(title)
+                                    .setContentText(message)
+                                    .setContentIntent(pi)
+                                    .setVisibility(Notification.VISIBILITY_PUBLIC)
+                                    .extend(new Notification.TvExtender()
+                                            .setChannel(ADB_NOTIFICATION_CHANNEL_ID_TV))
+                                    .build();
                     mAdbNotificationShown = true;
                     mNotificationManager.notifyAsUser(null, id, notification,
                             UserHandle.ALL);
@@ -1038,7 +1047,8 @@
     }
 
     /* opens the currently attached USB accessory */
-    public ParcelFileDescriptor openAccessory(UsbAccessory accessory, UsbUserSettingsManager settings) {
+    public ParcelFileDescriptor openAccessory(UsbAccessory accessory,
+            UsbUserSettingsManager settings) {
         UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
         if (currentAccessory == null) {
             throw new IllegalArgumentException("no accessory attached");
@@ -1058,17 +1068,19 @@
     }
 
     public void setCurrentFunctions(String functions, boolean usbDataUnlocked) {
-        if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ", " +
-				usbDataUnlocked + ")");
+        if (DEBUG) {
+            Slog.d(TAG, "setCurrentFunctions(" + functions + ", " +
+                    usbDataUnlocked + ")");
+        }
         mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked);
     }
 
     private void readOemUsbOverrideConfig() {
         String[] configList = mContext.getResources().getStringArray(
-            com.android.internal.R.array.config_oemUsbModeOverride);
+                com.android.internal.R.array.config_oemUsbModeOverride);
 
         if (configList != null) {
-            for (String config: configList) {
+            for (String config : configList) {
                 String[] items = config.split(":");
                 if (items.length == 3) {
                     if (mOemModeMap == null) {
@@ -1092,7 +1104,7 @@
 
         List<Pair<String, String>> overrides = mOemModeMap.get(bootMode);
         if (overrides != null) {
-            for (Pair<String, String> pair: overrides) {
+            for (Pair<String, String> pair : overrides) {
                 if (pair.first.equals(usbFunctions)) {
                     Slog.d(TAG, "OEM USB override: " + pair.first + " ==> " + pair.second);
                     return pair.second;
@@ -1120,7 +1132,7 @@
             mDebuggingManager.clearUsbDebuggingKeys();
         } else {
             throw new RuntimeException("Cannot clear Usb Debugging keys, "
-                        + "UsbDebuggingManager not enabled");
+                    + "UsbDebuggingManager not enabled");
         }
     }
 
@@ -1134,7 +1146,10 @@
     }
 
     private native String[] nativeGetAccessoryStrings();
+
     private native ParcelFileDescriptor nativeOpenAccessory();
+
     private native boolean nativeIsStartRequested();
+
     private native int nativeGetAudioMode();
 }
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 54ab19d..2516d51 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -25,75 +25,480 @@
 public class PreciseDisconnectCause {
 
     /** The disconnect cause is not valid (Not received a disconnect cause)*/
-    public static final int NOT_VALID                      = -1;
+    public static final int NOT_VALID                                        = -1;
     /** No disconnect cause provided. Generally a local disconnect or an incoming missed call */
-    public static final int NO_DISCONNECT_CAUSE_AVAILABLE  = 0;
+    public static final int NO_DISCONNECT_CAUSE_AVAILABLE                    = 0;
     /**
      * The destination cannot be reached because the number, although valid,
      * is not currently assigned
      */
-    public static final int UNOBTAINABLE_NUMBER            = 1;
+    public static final int UNOBTAINABLE_NUMBER                              = 1;
+    /** The user cannot be reached because the network through which the call has been
+     *  routed does not serve the destination desired
+     */
+    public static final int NO_ROUTE_TO_DESTINATION                          = 3;
+    /** The channel most recently identified is not acceptable to the sending entity for
+     *  use in this call
+     */
+    public static final int CHANNEL_UNACCEPTABLE                             = 6;
+    /** The MS has tried to access a service that the MS's network operator or service
+     *  provider is not prepared to allow
+     */
+    public static final int OPERATOR_DETERMINED_BARRING                      = 8;
     /** One of the users involved in the call has requested that the call is cleared */
-    public static final int NORMAL                         = 16;
+    public static final int NORMAL                                           = 16;
     /** The called user is unable to accept another call */
-    public static final int BUSY                           = 17;
+    public static final int BUSY                                             = 17;
+    /** The user does not respond to a call establishment message with either an alerting
+     *  or connect indication within the prescribed period of time allocated
+     */
+    public static final int NO_USER_RESPONDING                               = 18;
+    /** The user has provided an alerting indication but has not provided a connect
+     *  indication within a prescribed period of time
+     */
+    public static final int NO_ANSWER_FROM_USER                              = 19;
+    /** The equipment sending this cause does not wish to accept this call */
+    public static final int CALL_REJECTED                                    = 21;
     /** The called number is no longer assigned */
-    public static final int NUMBER_CHANGED                 = 22;
+    public static final int NUMBER_CHANGED                                   = 22;
+    /** This cause is returned to the network when a mobile station clears an active
+     *  call which is being pre-empted by another call with higher precedence
+     */
+    public static final int PREEMPTION                                       = 25;
+    /** The destination indicated by the mobile station cannot be reached because
+     *  the interface to the destination is not functioning correctly
+     */
+    public static final int DESTINATION_OUT_OF_ORDER                         = 27;
+    /** The called party number is not a valid format or is not complete */
+    public static final int INVALID_NUMBER_FORMAT                            = 28;
+    /** The facility requested by user can not be provided by the network */
+    public static final int FACILITY_REJECTED                                = 29;
     /** Provided in response to a STATUS ENQUIRY message */
-    public static final int STATUS_ENQUIRY                 = 30;
+    public static final int STATUS_ENQUIRY                                   = 30;
     /** Reports a normal disconnect only when no other normal cause applies */
-    public static final int NORMAL_UNSPECIFIED             = 31;
+    public static final int NORMAL_UNSPECIFIED                               = 31;
     /** There is no channel presently available to handle the call */
-    public static final int NO_CIRCUIT_AVAIL               = 34;
+    public static final int NO_CIRCUIT_AVAIL                                 = 34;
+    /** The network is not functioning correctly and that the condition is likely
+     *  to last a relatively long period of time
+     */
+    public static final int NETWORK_OUT_OF_ORDER                             = 38;
     /**
      * The network is not functioning correctly and the condition is not likely to last
      * a long period of time
      */
-    public static final int TEMPORARY_FAILURE              = 41;
+    public static final int TEMPORARY_FAILURE                                = 41;
     /** The switching equipment is experiencing a period of high traffic */
-    public static final int SWITCHING_CONGESTION           = 42;
+    public static final int SWITCHING_CONGESTION                             = 42;
+    /** The network could not deliver access information to the remote user as requested */
+    public static final int ACCESS_INFORMATION_DISCARDED                     = 43;
     /** The channel cannot be provided */
-    public static final int CHANNEL_NOT_AVAIL              = 44;
+    public static final int CHANNEL_NOT_AVAIL                                = 44;
+    /** This cause is used to report a resource unavailable event only when no other
+     *  cause in the resource unavailable class applies
+     */
+    public static final int RESOURCES_UNAVAILABLE_OR_UNSPECIFIED             = 44;
     /** The requested quality of service (ITU-T X.213) cannot be provided */
-    public static final int QOS_NOT_AVAIL                  = 49;
+    public static final int QOS_NOT_AVAIL                                    = 49;
+    /** The facility could not be provided by the network because the user has no
+     *  complete subscription
+     */
+    public static final int REQUESTED_FACILITY_NOT_SUBSCRIBED                = 50;
+    /** Incoming calls are not allowed within this CUG */
+    public static final int INCOMING_CALLS_BARRED_WITHIN_CUG                 = 55;
+    /** The mobile station is not authorized to use bearer capability requested */
+    public static final int BEARER_CAPABILITY_NOT_AUTHORIZED                 = 57;
     /** The requested bearer capability is not available at this time */
-    public static final int BEARER_NOT_AVAIL               = 58;
+    public static final int BEARER_NOT_AVAIL                                 = 58;
+    /** The service option is not availble at this time */
+    public static final int SERVICE_OPTION_NOT_AVAILABLE                     = 63;
+    /** The equipment sending this cause does not support the bearer capability requested */
+    public static final int BEARER_SERVICE_NOT_IMPLEMENTED                   = 65;
     /** The call clearing is due to ACM being greater than or equal to ACMmax */
-    public static final int ACM_LIMIT_EXCEEDED             = 68;
+    public static final int ACM_LIMIT_EXCEEDED                               = 68;
+    /** The equipment sending this cause does not support the requested facility */
+    public static final int REQUESTED_FACILITY_NOT_IMPLEMENTED               = 69;
+    /** The equipment sending this cause only supports the restricted version of
+     *  the requested bearer capability
+     */
+    public static final int ONLY_DIGITAL_INFORMATION_BEARER_AVAILABLE        = 70;
+    /** The service requested is not implemented at network */
+    public static final int SERVICE_OR_OPTION_NOT_IMPLEMENTED                = 79;
+    /** The equipment sending this cause has received a message with a transaction identifier
+     *  which is not currently in use on the MS-network interface
+     */
+    public static final int INVALID_TRANSACTION_IDENTIFIER                   = 81;
+    /** The called user for the incoming CUG call is not a member of the specified CUG */
+    public static final int USER_NOT_MEMBER_OF_CUG                           = 87;
+    /** The equipment sending this cause has received a request which can't be accomodated */
+    public static final int INCOMPATIBLE_DESTINATION                         = 88;
+    /** This cause is used to report receipt of a message with semantically incorrect contents */
+    public static final int SEMANTICALLY_INCORRECT_MESSAGE                   = 95;
+    /** The equipment sending this cause has received a message with a non-semantical
+     *  mandatory IE error
+     */
+    public static final int INVALID_MANDATORY_INFORMATION                    = 96;
+    /** This is sent in response to a message which is not defined, or defined but not
+     *  implemented by the equipment sending this cause
+     */
+    public static final int MESSAGE_TYPE_NON_IMPLEMENTED                     = 97;
+    /** The equipment sending this cause has received a message not compatible with the
+     *  protocol state
+     */
+    public static final int MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE  = 98;
+    /** The equipment sending this cause has received a message which includes information
+     *  elements not recognized because its identifier is not defined or it is defined but not
+     *  implemented by the equipment sending the cause
+     */
+    public static final int INFORMATION_ELEMENT_NON_EXISTENT                 = 99;
+    /** The equipment sending this cause has received a message with conditional IE errors */
+    public static final int CONDITIONAL_IE_ERROR                             = 100;
+    /** The message has been received which is incompatible with the protocol state */
+    public static final int MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE       = 101;
+    /** The  procedure has been initiated by the expiry of a timer in association with
+     *  3GPP TS 24.008 error handling procedures
+     */
+    public static final int RECOVERY_ON_TIMER_EXPIRED                        = 102;
+    /** This protocol error event is reported only when no other cause in the protocol
+     *  error class applies
+     */
+    public static final int PROTOCOL_ERROR_UNSPECIFIED                       = 111;
+    /** interworking with a network which does not provide causes for actions it takes
+     *  thus, the precise cause for a message which is being sent cannot be ascertained
+     */
+    public static final int INTERWORKING_UNSPECIFIED                         = 127;
     /** The call is restricted */
-    public static final int CALL_BARRED                    = 240;
+    public static final int CALL_BARRED                                      = 240;
     /** The call is blocked by the Fixed Dialing Number list */
-    public static final int FDN_BLOCKED                    = 241;
+    public static final int FDN_BLOCKED                                      = 241;
     /** The given IMSI is not known at the VLR */
     /** TS 24.008 cause 4 */
-    public static final int IMSI_UNKNOWN_IN_VLR            = 242;
+    public static final int IMSI_UNKNOWN_IN_VLR                              = 242;
     /**
      * The network does not accept emergency call establishment using an IMEI or not accept attach
      * procedure for emergency services using an IMEI
      */
-    public static final int IMEI_NOT_ACCEPTED              = 243;
+    public static final int IMEI_NOT_ACCEPTED                                = 243;
+    /** The call cannot be established because RADIO is OFF */
+    public static final int RADIO_OFF                                        = 247;
+    /** The call cannot be established because of no cell coverage */
+    public static final int OUT_OF_SRV                                       = 248;
+    /** The call cannot be established because of no valid SIM */
+    public static final int NO_VALID_SIM                                     = 249;
+    /** The call is dropped or failed internally by modem */
+    public static final int RADIO_INTERNAL_ERROR                             = 250;
+    /** Call failed because of UE timer expired while waiting for a response from network */
+    public static final int NETWORK_RESP_TIMEOUT                             = 251;
+    /** Call failed because of a network reject */
+    public static final int NETWORK_REJECT                                   = 252;
+    /** Call failed because of radio access failure. ex. RACH failure */
+    public static final int RADIO_ACCESS_FAILURE                             = 253;
+    /** Call failed/dropped because of a RLF */
+    public static final int RADIO_LINK_FAILURE                               = 254;
+    /** Call failed/dropped because of radio link lost */
+    public static final int RADIO_LINK_LOST                                  = 255;
+    /** Call failed because of a radio uplink issue */
+    public static final int RADIO_UPLINK_FAILURE                             = 256;
+    /** Call failed because of a RRC connection setup failure */
+    public static final int RADIO_SETUP_FAILURE                              = 257;
+    /** Call failed/dropped because of RRC connection release from NW */
+    public static final int RADIO_RELEASE_NORMAL                             = 258;
+    /** Call failed/dropped because of RRC abnormally released by modem/network */
+    public static final int RADIO_RELEASE_ABNORMAL                           = 259;
+    /** Call setup failed because of access class barring */
+    public static final int ACCESS_CLASS_BLOCKED                             = 260;
+    /** Call failed/dropped because of a network detach */
+    public static final int NETWORK_DETACH                                   = 261;
+
     /** MS is locked until next power cycle */
-    public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE  = 1000;
+    public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE                    = 1000;
     /** Drop call*/
-    public static final int CDMA_DROP                      = 1001;
+    public static final int CDMA_DROP                                        = 1001;
     /** INTERCEPT order received, MS state idle entered */
-    public static final int CDMA_INTERCEPT                 = 1002;
+    public static final int CDMA_INTERCEPT                                   = 1002;
     /** MS has been redirected, call is cancelled */
-    public static final int CDMA_REORDER                   = 1003;
+    public static final int CDMA_REORDER                                     = 1003;
     /** Service option rejection */
-    public static final int CDMA_SO_REJECT                 = 1004;
+    public static final int CDMA_SO_REJECT                                   = 1004;
     /** Requested service is rejected, retry delay is set */
-    public static final int CDMA_RETRY_ORDER               = 1005;
+    public static final int CDMA_RETRY_ORDER                                 = 1005;
     /** Unable to obtain access to the CDMA system */
-    public static final int CDMA_ACCESS_FAILURE            = 1006;
+    public static final int CDMA_ACCESS_FAILURE                              = 1006;
     /** Not a preempted call */
-    public static final int CDMA_PREEMPTED                 = 1007;
+    public static final int CDMA_PREEMPTED                                   = 1007;
     /** Not an emergency call */
-    public static final int CDMA_NOT_EMERGENCY             = 1008;
+    public static final int CDMA_NOT_EMERGENCY                               = 1008;
     /** Access Blocked by CDMA network */
-    public static final int CDMA_ACCESS_BLOCKED            = 1009;
+    public static final int CDMA_ACCESS_BLOCKED                              = 1009;
+
+    /** Mapped from ImsReasonInfo */
+    /* The passed argument is an invalid */
+    public static final int LOCAL_ILLEGAL_ARGUMENT                           = 1200;
+    // The operation is invoked in invalid call state
+    public static final int LOCAL_ILLEGAL_STATE                              = 1201;
+    // IMS service internal error
+    public static final int LOCAL_INTERNAL_ERROR                             = 1202;
+    // IMS service goes down (service connection is lost)
+    public static final int LOCAL_IMS_SERVICE_DOWN                           = 1203;
+    // No pending incoming call exists
+    public static final int LOCAL_NO_PENDING_CALL                            = 1204;
+    // Service unavailable; by power off
+    public static final int LOCAL_POWER_OFF                                  = 1205;
+    // Service unavailable; by low battery
+    public static final int LOCAL_LOW_BATTERY                                = 1206;
+    // Service unavailable; by out of service (data service state)
+    public static final int LOCAL_NETWORK_NO_SERVICE                         = 1207;
+    /* Service unavailable; by no LTE coverage
+     * (VoLTE is not supported even though IMS is registered)
+     */
+    public static final int LOCAL_NETWORK_NO_LTE_COVERAGE                    = 1208;
+    /** Service unavailable; by located in roaming area */
+    public static final int LOCAL_NETWORK_ROAMING                            = 1209;
+    /** Service unavailable; by IP changed */
+    public static final int LOCAL_NETWORK_IP_CHANGED                         = 1210;
+    /** Service unavailable; other */
+    public static final int LOCAL_SERVICE_UNAVAILABLE                        = 1211;
+    /* Service unavailable; IMS connection is lost (IMS is not registered) */
+    public static final int LOCAL_NOT_REGISTERED                             = 1212;
+    /** Max call exceeded */
+    public static final int LOCAL_MAX_CALL_EXCEEDED                          = 1213;
+    /** Call decline */
+    public static final int LOCAL_CALL_DECLINE                               = 1214;
+    /** SRVCC is in progress */
+    public static final int LOCAL_CALL_VCC_ON_PROGRESSING                    = 1215;
+    /** Resource reservation is failed (QoS precondition) */
+    public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED           = 1216;
+    /** Retry CS call; VoLTE service can't be provided by the network or remote end
+     *  Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
+     */
+    public static final int LOCAL_CALL_CS_RETRY_REQUIRED                     = 1217;
+    /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */
+    public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED                  = 1218;
+    /** IMS call is already terminated (in TERMINATED state) */
+    public static final int LOCAL_CALL_TERMINATED                            = 1219;
+    /** Handover not feasible */
+    public static final int LOCAL_HO_NOT_FEASIBLE                            = 1220;
+
+    /** 1xx waiting timer is expired after sending INVITE request (MO only) */
+    public static final int TIMEOUT_1XX_WAITING                              = 1221;
+    /** User no answer during call setup operation (MO/MT)
+     *  MO : 200 OK to INVITE request is not received,
+     *  MT : No action from user after alerting the call
+     */
+    public static final int TIMEOUT_NO_ANSWER                                = 1222;
+    /** User no answer during call update operation (MO/MT)
+     *  MO : 200 OK to re-INVITE request is not received,
+     *  MT : No action from user after alerting the call
+     */
+    public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE                    = 1223;
+
+    /**
+     * STATUSCODE (SIP response code) (IMS -> Telephony)
+     */
+    /** SIP request is redirected */
+    public static final int SIP_REDIRECTED                                   = 1300;
+    /** 4xx responses */
+    /** 400 : Bad Request */
+    public static final int SIP_BAD_REQUEST                                  = 1310;
+    /** 403 : Forbidden */
+    public static final int SIP_FORBIDDEN                                    = 1311;
+    /** 404 : Not Found */
+    public static final int SIP_NOT_FOUND                                    = 1312;
+    /** 415 : Unsupported Media Type
+     *  416 : Unsupported URI Scheme
+     *  420 : Bad Extension
+     */
+    public static final int SIP_NOT_SUPPORTED                                = 1313;
+    /** 408 : Request Timeout */
+    public static final int SIP_REQUEST_TIMEOUT                              = 1314;
+    /** 480 : Temporarily Unavailable */
+    public static final int SIP_TEMPRARILY_UNAVAILABLE                       = 1315;
+    /** 484 : Address Incomplete */
+    public static final int SIP_BAD_ADDRESS                                  = 1316;
+    /** 486 : Busy Here
+     *  600 : Busy Everywhere
+     */
+    public static final int SIP_BUSY                                         = 1317;
+    /** 487 : Request Terminated */
+    public static final int SIP_REQUEST_CANCELLED                            = 1318;
+    /** 406 : Not Acceptable
+     *  488 : Not Acceptable Here
+     *  606 : Not Acceptable
+     */
+    public static final int SIP_NOT_ACCEPTABLE                               = 1319;
+    /** 410 : Gone
+     *  604 : Does Not Exist Anywhere
+     */
+    public static final int SIP_NOT_REACHABLE                                = 1320;
+    /** Others */
+    public static final int SIP_CLIENT_ERROR                                 = 1321;
+    /** 5xx responses
+     *  501 : Server Internal Error
+     */
+    public static final int SIP_SERVER_INTERNAL_ERROR                        = 1330;
+    /** 503 : Service Unavailable */
+    public static final int SIP_SERVICE_UNAVAILABLE                          = 1331;
+    /** 504 : Server Time-out */
+    public static final int SIP_SERVER_TIMEOUT                               = 1332;
+    /** Others */
+    public static final int SIP_SERVER_ERROR                                 = 1333;
+    /** 6xx responses
+     *  603 : Decline
+     */
+    public static final int SIP_USER_REJECTED                                = 1340;
+    /** Others */
+    public static final int SIP_GLOBAL_ERROR                                 = 1341;
+    /** Emergency failure */
+    public static final int EMERGENCY_TEMP_FAILURE                           = 1342;
+    public static final int EMERGENCY_PERM_FAILURE                           = 1343;
+    /** Media resource initialization failed */
+    public static final int MEDIA_INIT_FAILED                                = 1400;
+    /** RTP timeout (no audio / video traffic in the session) */
+    public static final int MEDIA_NO_DATA                                    = 1401;
+    /** Media is not supported; so dropped the call */
+    public static final int MEDIA_NOT_ACCEPTABLE                             = 1402;
+    /** Unknown media related errors */
+    public static final int MEDIA_UNSPECIFIED                                = 1403;
+    /** User triggers the call end */
+    public static final int USER_TERMINATED                                  = 1500;
+    /** No action while an incoming call is ringing */
+    public static final int USER_NOANSWER                                    = 1501;
+    /** User ignores an incoming call */
+    public static final int USER_IGNORE                                      = 1502;
+    /** User declines an incoming call */
+    public static final int USER_DECLINE                                     = 1503;
+    /** Device declines/ends a call due to low battery */
+    public static final int LOW_BATTERY                                      = 1504;
+    /** Device declines call due to blacklisted call ID */
+    public static final int BLACKLISTED_CALL_ID                              = 1505;
+    /** The call is terminated by the network or remote user */
+    public static final int USER_TERMINATED_BY_REMOTE                        = 1510;
+
+    /**
+     * UT
+     */
+    public static final int UT_NOT_SUPPORTED                                 = 1800;
+    public static final int UT_SERVICE_UNAVAILABLE                           = 1801;
+    public static final int UT_OPERATION_NOT_ALLOWED                         = 1802;
+    public static final int UT_NETWORK_ERROR                                 = 1803;
+    public static final int UT_CB_PASSWORD_MISMATCH                          = 1804;
+
+    /**
+     * ECBM
+     */
+    public static final int ECBM_NOT_SUPPORTED                               = 1900;
+
+    /**
+     * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
+     */
+    public static final int MULTIENDPOINT_NOT_SUPPORTED                      = 1901;
+
+    /**
+     * CALL DROP error codes (Call could drop because of many reasons like Network not available,
+     *  handover, failed, etc)
+     */
+
+    /**
+     * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
+     * active wifi call and at the edge of coverage and there is no qualified LTE network available
+     * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
+     * code is received as part of the handover message.
+     */
+    public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE               = 2000;
+
+    /**
+     * MT call has ended due to a release from the network
+     * because the call was answered elsewhere
+     */
+    public static final int ANSWERED_ELSEWHERE                               = 2100;
+
+    /**
+     * For MultiEndpoint - Call Pull request has failed
+     */
+    public static final int CALL_PULL_OUT_OF_SYNC                            = 2101;
+
+    /**
+     * For MultiEndpoint - Call has been pulled from primary to secondary
+     */
+    public static final int CALL_PULLED                                      = 2102;
+
+    /**
+     * Supplementary services (HOLD/RESUME) failure error codes.
+     * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
+     */
+    public static final int SUPP_SVC_FAILED                                  = 2300;
+    public static final int SUPP_SVC_CANCELLED                               = 2301;
+    public static final int SUPP_SVC_REINVITE_COLLISION                      = 2302;
+
+    /**
+     * DPD Procedure received no response or send failed
+     */
+    public static final int IWLAN_DPD_FAILURE                                = 2400;
+
+    /**
+     * Establishment of the ePDG Tunnel Failed
+     */
+    public static final int EPDG_TUNNEL_ESTABLISH_FAILURE                    = 2500;
+
+    /**
+     * Re-keying of the ePDG Tunnel Failed; may not always result in teardown
+     */
+    public static final int EPDG_TUNNEL_REKEY_FAILURE                        = 2501;
+
+    /**
+     * Connection to the packet gateway is lost
+     */
+    public static final int EPDG_TUNNEL_LOST_CONNECTION                      = 2502;
+
+    /**
+     * The maximum number of calls allowed has been reached.  Used in a multi-endpoint scenario
+     * where the number of calls across all connected devices has reached the maximum.
+     */
+    public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED                  = 2503;
+
+    /**
+     * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
+     * declined the call.  Used in a multi-endpoint scenario where a remote device declined an
+     * incoming call.
+     */
+    public static final int REMOTE_CALL_DECLINE                              = 2504;
+
+    /**
+     * Indicates the call was disconnected due to the user reaching their data limit.
+     */
+    public static final int DATA_LIMIT_REACHED                               = 2505;
+
+    /**
+     * Indicates the call was disconnected due to the user disabling cellular data.
+     */
+    public static final int DATA_DISABLED                                    = 2506;
+
+    /**
+     * Indicates a call was disconnected due to loss of wifi signal.
+     */
+    public static final int WIFI_LOST                                        = 2507;
+
+
+    /* OEM specific error codes. To be used by OEMs when they don't want to
+       reveal error code which would be replaced by ERROR_UNSPECIFIED */
+    public static final int OEM_CAUSE_1                                      = 0xf001;
+    public static final int OEM_CAUSE_2                                      = 0xf002;
+    public static final int OEM_CAUSE_3                                      = 0xf003;
+    public static final int OEM_CAUSE_4                                      = 0xf004;
+    public static final int OEM_CAUSE_5                                      = 0xf005;
+    public static final int OEM_CAUSE_6                                      = 0xf006;
+    public static final int OEM_CAUSE_7                                      = 0xf007;
+    public static final int OEM_CAUSE_8                                      = 0xf008;
+    public static final int OEM_CAUSE_9                                      = 0xf009;
+    public static final int OEM_CAUSE_10                                     = 0xf00a;
+    public static final int OEM_CAUSE_11                                     = 0xf00b;
+    public static final int OEM_CAUSE_12                                     = 0xf00c;
+    public static final int OEM_CAUSE_13                                     = 0xf00d;
+    public static final int OEM_CAUSE_14                                     = 0xf00e;
+    public static final int OEM_CAUSE_15                                     = 0xf00f;
+
     /** Disconnected due to unspecified reasons */
-    public static final int ERROR_UNSPECIFIED              = 0xffff;
+    public static final int ERROR_UNSPECIFIED                                = 0xffff;
 
     /** Private constructor to avoid class instantiation. */
     private PreciseDisconnectCause() {
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index e4f380f..bd8492b3 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -318,6 +318,66 @@
      */
     public static final int CODE_IKEV2_AUTH_FAILURE = 1408;
 
+    /** The call cannot be established because RADIO is OFF */
+    public static final int CODE_RADIO_OFF = 1500;
+
+    /** The call cannot be established because of no valid SIM */
+    public static final int CODE_NO_VALID_SIM = 1501;
+
+    /** The failure is due internal error at modem */
+    public static final int CODE_RADIO_INTERNAL_ERROR = 1502;
+
+    /** The failure is due to UE timer expired while waiting for a response from network */
+    public static final int CODE_NETWORK_RESP_TIMEOUT = 1503;
+
+    /** The failure is due to explicit reject from network */
+    public static final int CODE_NETWORK_REJECT = 1504;
+
+    /** The failure is due to radio access failure. ex. RACH failure */
+    public static final int CODE_RADIO_ACCESS_FAILURE = 1505;
+
+    /** Call/IMS registration failed/dropped because of a RLF */
+    public static final int CODE_RADIO_LINK_FAILURE = 1506;
+
+    /** Call/IMS registration failed/dropped because of radio link lost */
+    public static final int CODE_RADIO_LINK_LOST = 1507;
+
+    /** The call Call/IMS registration failed because of a radio uplink issue */
+    public static final int CODE_RADIO_UPLINK_FAILURE = 1508;
+
+    /** Call failed because of a RRC connection setup failure */
+    public static final int CODE_RADIO_SETUP_FAILURE = 1509;
+
+    /** Call failed/dropped because of RRC connection release from NW */
+    public static final int CODE_RADIO_RELEASE_NORMAL = 1510;
+
+    /** Call failed/dropped because of RRC abnormally released by modem/network */
+    public static final int CODE_RADIO_RELEASE_ABNORMAL = 1511;
+
+    /** Call failed because of access class barring */
+    public static final int CODE_ACCESS_CLASS_BLOCKED = 1512;
+
+    /** Call/IMS registration is failed/dropped because of a network detach */
+    public static final int CODE_NETWORK_DETACH = 1513;
+
+    /* OEM specific error codes. To be used by OEMs when they don't want to
+   reveal error code which would be replaced by ERROR_UNSPECIFIED */
+    public static final int CODE_OEM_CAUSE_1 = 0xf001;
+    public static final int CODE_OEM_CAUSE_2 = 0xf002;
+    public static final int CODE_OEM_CAUSE_3 = 0xf003;
+    public static final int CODE_OEM_CAUSE_4 = 0xf004;
+    public static final int CODE_OEM_CAUSE_5 = 0xf005;
+    public static final int CODE_OEM_CAUSE_6 = 0xf006;
+    public static final int CODE_OEM_CAUSE_7 = 0xf007;
+    public static final int CODE_OEM_CAUSE_8 = 0xf008;
+    public static final int CODE_OEM_CAUSE_9 = 0xf009;
+    public static final int CODE_OEM_CAUSE_10 = 0xf00a;
+    public static final int CODE_OEM_CAUSE_11 = 0xf00b;
+    public static final int CODE_OEM_CAUSE_12 = 0xf00c;
+    public static final int CODE_OEM_CAUSE_13 = 0xf00d;
+    public static final int CODE_OEM_CAUSE_14 = 0xf00e;
+    public static final int CODE_OEM_CAUSE_15 = 0xf00f;
+
     /**
      * Network string error messages.
      * mExtraMessage may have these values.
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index 1e488f7..9db21aa 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -23,30 +23,22 @@
 
 namespace aapt {
 
-/**
- * Holds basic information about the app being built. Most of this information
- * will come from the app's AndroidManifest.
- */
+// Information relevant to building an app, parsed from the app's AndroidManifest.xml.
 struct AppInfo {
-  /**
-   * App's package name.
-   */
+  // The app's package name.
   std::string package;
 
-  /**
-   * The App's minimum SDK version.
-   */
+  // The app's minimum SDK version, if it is defined.
   Maybe<std::string> min_sdk_version;
 
-  /**
-   * The Version code of the app.
-   */
+  // The app's version code, if it is defined.
   Maybe<uint32_t> version_code;
 
-  /**
-   * The revision code of the app.
-   */
+  // The app's revision code, if it is defined.
   Maybe<uint32_t> revision_code;
+
+  // The app's split name, if it is a split.
+  Maybe<std::string> split_name;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 1042111..1b4d5bb 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -751,70 +751,67 @@
     return true;
   }
 
-  Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res,
-                                            IDiagnostics* diag) {
+  Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
     // Make sure the first element is <manifest> with package attribute.
-    if (xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get())) {
-      AppInfo app_info;
-
-      if (!manifest_el->namespace_uri.empty() ||
-          manifest_el->name != "manifest") {
-        diag->Error(DiagMessage(xml_res->file.source)
-                    << "root tag must be <manifest>");
-        return {};
-      }
-
-      xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
-      if (!package_attr) {
-        diag->Error(DiagMessage(xml_res->file.source)
-                    << "<manifest> must have a 'package' attribute");
-        return {};
-      }
-
-      app_info.package = package_attr->value;
-
-      if (xml::Attribute* version_code_attr =
-              manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
-        Maybe<uint32_t> maybe_code =
-            ResourceUtils::ParseInt(version_code_attr->value);
-        if (!maybe_code) {
-          diag->Error(DiagMessage(xml_res->file.source.WithLine(
-                          manifest_el->line_number))
-                      << "invalid android:versionCode '"
-                      << version_code_attr->value << "'");
-          return {};
-        }
-        app_info.version_code = maybe_code.value();
-      }
-
-      if (xml::Attribute* revision_code_attr =
-              manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
-        Maybe<uint32_t> maybe_code =
-            ResourceUtils::ParseInt(revision_code_attr->value);
-        if (!maybe_code) {
-          diag->Error(DiagMessage(xml_res->file.source.WithLine(
-                          manifest_el->line_number))
-                      << "invalid android:revisionCode '"
-                      << revision_code_attr->value << "'");
-          return {};
-        }
-        app_info.revision_code = maybe_code.value();
-      }
-
-      if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
-        if (xml::Attribute* min_sdk = uses_sdk_el->FindAttribute(
-                xml::kSchemaAndroid, "minSdkVersion")) {
-          app_info.min_sdk_version = min_sdk->value;
-        }
-      }
-      return app_info;
+    xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get());
+    if (manifest_el == nullptr) {
+      return {};
     }
-    return {};
+
+    AppInfo app_info;
+
+    if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+      diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
+      return {};
+    }
+
+    xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+    if (!package_attr) {
+      diag->Error(DiagMessage(xml_res->file.source)
+                  << "<manifest> must have a 'package' attribute");
+      return {};
+    }
+    app_info.package = package_attr->value;
+
+    if (xml::Attribute* version_code_attr =
+            manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
+      Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_attr->value);
+      if (!maybe_code) {
+        diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+                    << "invalid android:versionCode '" << version_code_attr->value << "'");
+        return {};
+      }
+      app_info.version_code = maybe_code.value();
+    }
+
+    if (xml::Attribute* revision_code_attr =
+            manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
+      Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(revision_code_attr->value);
+      if (!maybe_code) {
+        diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+                    << "invalid android:revisionCode '" << revision_code_attr->value << "'");
+        return {};
+      }
+      app_info.revision_code = maybe_code.value();
+    }
+
+    if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
+      if (!split_name_attr->value.empty()) {
+        app_info.split_name = split_name_attr->value;
+      }
+    }
+
+    if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+      if (xml::Attribute* min_sdk =
+              uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
+        app_info.min_sdk_version = min_sdk->value;
+      }
+    }
+    return app_info;
   }
 
   /**
-   * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it
-   * linked.
+   * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
    * Postcondition: ResourceTable has only one package left. All others are
    * stripped, or there is an error and false is returned.
    */
@@ -1367,45 +1364,44 @@
     return true;
   }
 
-  std::unique_ptr<xml::XmlResource> GenerateSplitManifest(
-      const AppInfo& app_info, const SplitConstraints& constraints) {
-    std::unique_ptr<xml::XmlResource> doc =
-        util::make_unique<xml::XmlResource>();
+  std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
+                                                          const SplitConstraints& constraints) {
+    std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
 
-    std::unique_ptr<xml::Namespace> namespace_android =
-        util::make_unique<xml::Namespace>();
+    std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>();
     namespace_android->namespace_uri = xml::kSchemaAndroid;
     namespace_android->namespace_prefix = "android";
 
-    std::unique_ptr<xml::Element> manifest_el =
-        util::make_unique<xml::Element>();
+    std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>();
     manifest_el->name = "manifest";
-    manifest_el->attributes.push_back(
-        xml::Attribute{"", "package", app_info.package});
+    manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package});
 
     if (app_info.version_code) {
-      manifest_el->attributes.push_back(
-          xml::Attribute{xml::kSchemaAndroid, "versionCode",
-                         std::to_string(app_info.version_code.value())});
+      manifest_el->attributes.push_back(xml::Attribute{
+          xml::kSchemaAndroid, "versionCode", std::to_string(app_info.version_code.value())});
     }
 
     if (app_info.revision_code) {
-      manifest_el->attributes.push_back(
-          xml::Attribute{xml::kSchemaAndroid, "revisionCode",
-                         std::to_string(app_info.revision_code.value())});
+      manifest_el->attributes.push_back(xml::Attribute{
+          xml::kSchemaAndroid, "revisionCode", std::to_string(app_info.revision_code.value())});
     }
 
     std::stringstream split_name;
+    if (app_info.split_name) {
+      split_name << app_info.split_name.value() << ".";
+    }
     split_name << "config." << util::Joiner(constraints.configs, "_");
 
-    manifest_el->attributes.push_back(
-        xml::Attribute{"", "split", split_name.str()});
+    manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()});
 
-    std::unique_ptr<xml::Element> application_el =
-        util::make_unique<xml::Element>();
+    if (app_info.split_name) {
+      manifest_el->attributes.push_back(
+          xml::Attribute{"", "configForSplit", app_info.split_name.value()});
+    }
+
+    std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>();
     application_el->name = "application";
-    application_el->attributes.push_back(
-        xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
+    application_el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
 
     manifest_el->AppendChild(std::move(application_el));
     namespace_android->AppendChild(std::move(manifest_el));
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 313fe45..0c19c7a 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -29,10 +29,7 @@
 
 namespace aapt {
 
-/**
- * This is how PackageManager builds class names from AndroidManifest.xml
- * entries.
- */
+// This is how PackageManager builds class names from AndroidManifest.xml entries.
 static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr,
                                 SourcePathDiagnostics* diag) {
   // We allow unqualified class names (ie: .HelloActivity)
@@ -90,6 +87,36 @@
   };
 }
 
+static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* diag) {
+  constexpr const char* kFeatureSplit = "featureSplit";
+  constexpr const char* kIsFeatureSplit = "isFeatureSplit";
+
+  xml::Attribute* attr = el->FindAttribute({}, kFeatureSplit);
+  if (attr != nullptr) {
+    // Rewrite the featureSplit attribute to be "split". This is what the
+    // platform recognizes.
+    attr->name = "split";
+
+    // Now inject the android:isFeatureSplit="true" attribute.
+    xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, kIsFeatureSplit);
+    if (attr != nullptr) {
+      if (!ResourceUtils::ParseBool(attr->value).value_or_default(false)) {
+        // The isFeatureSplit attribute is false, which conflicts with the use
+        // of "featureSplit".
+        diag->Error(DiagMessage(el->line_number)
+                    << "attribute 'featureSplit' used in <manifest> but 'android:isFeatureSplit' "
+                       "is not 'true'");
+        return false;
+      }
+
+      // The attribute is already there and set to true, nothing to do.
+    } else {
+      el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, kIsFeatureSplit, "true"});
+    }
+  }
+  return true;
+}
+
 static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
   xml::Attribute* attr = el->FindAttribute({}, "package");
   if (!attr) {
@@ -97,31 +124,34 @@
                 << "<manifest> tag is missing 'package' attribute");
     return false;
   } else if (ResourceUtils::IsReference(attr->value)) {
-    diag->Error(
-        DiagMessage(el->line_number)
-        << "attribute 'package' in <manifest> tag must not be a reference");
+    diag->Error(DiagMessage(el->line_number)
+                << "attribute 'package' in <manifest> tag must not be a reference");
     return false;
   } else if (!util::IsJavaPackageName(attr->value)) {
     diag->Error(DiagMessage(el->line_number)
-                << "attribute 'package' in <manifest> tag is not a valid Java "
-                   "package name: '"
+                << "attribute 'package' in <manifest> tag is not a valid Java package name: '"
                 << attr->value << "'");
     return false;
   }
+
+  attr = el->FindAttribute({}, "split");
+  if (attr) {
+    if (!util::IsJavaPackageName(attr->value)) {
+      diag->Error(DiagMessage(el->line_number) << "attribute 'split' in <manifest> tag is not a "
+                                                  "valid split name");
+      return false;
+    }
+  }
   return true;
 }
 
-/**
- * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
- * checking on it is manual.
- */
+// The coreApp attribute in <manifest> is not a regular AAPT attribute, so type
+// checking on it is manual.
 static bool FixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) {
   if (xml::Attribute* attr = el->FindAttribute("", "coreApp")) {
-    std::unique_ptr<BinaryPrimitive> result =
-        ResourceUtils::TryParseBool(attr->value);
+    std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseBool(attr->value);
     if (!result) {
-      diag->Error(DiagMessage(el->line_number)
-                  << "attribute coreApp must be a boolean");
+      diag->Error(DiagMessage(el->line_number) << "attribute coreApp must be a boolean");
       return false;
     }
     attr->compiled_value = std::move(result);
@@ -172,8 +202,7 @@
   }
 
   if (options_.rename_instrumentation_target_package) {
-    if (!util::IsJavaPackageName(
-            options_.rename_instrumentation_target_package.value())) {
+    if (!util::IsJavaPackageName(options_.rename_instrumentation_target_package.value())) {
       diag->Error(DiagMessage()
                   << "invalid instrumentation target package override '"
                   << options_.rename_instrumentation_target_package.value()
@@ -203,6 +232,7 @@
 
   // Manifest actions.
   xml::XmlNodeAction& manifest_action = (*executor)["manifest"];
+  manifest_action.Action(AutoGenerateIsFeatureSplit);
   manifest_action.Action(VerifyManifest);
   manifest_action.Action(FixCoreAppAttribute);
   manifest_action.Action([&](xml::Element* el) -> bool {
@@ -276,6 +306,7 @@
   manifest_action["compatible-screens"]["screen"];
   manifest_action["supports-gl-texture"];
   manifest_action["meta-data"] = meta_data_action;
+  manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
 
   // Application actions.
   xml::XmlNodeAction& application_action = manifest_action["application"];
@@ -311,15 +342,13 @@
  public:
   using xml::Visitor::Visit;
 
-  explicit FullyQualifiedClassNameVisitor(const StringPiece& package)
-      : package_(package) {}
+  explicit FullyQualifiedClassNameVisitor(const StringPiece& package) : package_(package) {}
 
   void Visit(xml::Element* el) override {
     for (xml::Attribute& attr : el->attributes) {
       if (attr.namespace_uri == xml::kSchemaAndroid &&
           class_attributes_.find(attr.name) != class_attributes_.end()) {
-        if (Maybe<std::string> new_value =
-                util::GetFullyQualifiedClassName(package_, attr.value)) {
+        if (Maybe<std::string> new_value = util::GetFullyQualifiedClassName(package_, attr.value)) {
           attr.value = std::move(new_value.value());
         }
       }
@@ -334,8 +363,7 @@
   std::unordered_set<StringPiece> class_attributes_ = {"name"};
 };
 
-static bool RenameManifestPackage(const StringPiece& package_override,
-                                  xml::Element* manifest_el) {
+static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) {
   xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
 
   // We've already verified that the manifest element is present, with a package
@@ -358,8 +386,7 @@
     return false;
   }
 
-  if ((options_.min_sdk_version_default ||
-       options_.target_sdk_version_default) &&
+  if ((options_.min_sdk_version_default || options_.target_sdk_version_default) &&
       root->FindChild({}, "uses-sdk") == nullptr) {
     // Auto insert a <uses-sdk> element. This must be inserted before the
     // <application> tag. The device runtime PackageParser will make SDK version
@@ -374,8 +401,7 @@
     return false;
   }
 
-  if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist,
-                        context->GetDiagnostics(), doc)) {
+  if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, context->GetDiagnostics(), doc)) {
     return false;
   }
 
@@ -383,8 +409,7 @@
     // Rename manifest package outside of the XmlActionExecutor.
     // We need to extract the old package name and FullyQualify all class
     // names.
-    if (!RenameManifestPackage(options_.rename_manifest_package.value(),
-                               root)) {
+    if (!RenameManifestPackage(options_.rename_manifest_package.value(), root)) {
       return false;
     }
   }
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index eb8a1cc..008344c 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -521,36 +521,21 @@
     # add zwj sequences not in the current emoji-zwj-sequences.txt
     adjusted_emoji_zwj_sequences = dict(_emoji_zwj_sequences)
     adjusted_emoji_zwj_sequences.update(_emoji_zwj_sequences)
-    # single parent families
+    # Wrestlers with modifiers
     additional_emoji_zwj = (
-        (0x1F468, 0x200D, 0x1F466),
-        (0x1F468, 0x200D, 0x1F467),
-        (0x1F468, 0x200D, 0x1F466, 0x200D, 0x1F466),
-        (0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F466),
-        (0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F467),
-        (0x1F469, 0x200D, 0x1F466),
-        (0x1F469, 0x200D, 0x1F467),
-        (0x1F469, 0x200D, 0x1F466, 0x200D, 0x1F466),
-        (0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F466),
-        (0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F467),
-    )
-    # sequences formed from man and woman and optional fitzpatrick modifier
-    modified_extensions = (
-        0x2696,
-        0x2708,
-        0x1F3A8,
-        0x1F680,
-        0x1F692,
+        (0x1F93C, 0x1F3FB, 0x200D, 0x2640),
+        (0x1F93C, 0x1F3FB, 0x200D, 0x2642),
+        (0x1F93C, 0x1F3FC, 0x200D, 0x2640),
+        (0x1F93C, 0x1F3FC, 0x200D, 0x2642),
+        (0x1F93C, 0x1F3FD, 0x200D, 0x2640),
+        (0x1F93C, 0x1F3FD, 0x200D, 0x2642),
+        (0x1F93C, 0x1F3FE, 0x200D, 0x2640),
+        (0x1F93C, 0x1F3FE, 0x200D, 0x2642),
+        (0x1F93C, 0x1F3FF, 0x200D, 0x2640),
+        (0x1F93C, 0x1F3FF, 0x200D, 0x2642),
     )
     for seq in additional_emoji_zwj:
         adjusted_emoji_zwj_sequences[seq] = 'Emoji_ZWJ_Sequence'
-    for ext in modified_extensions:
-        for base in (0x1F468, 0x1F469):
-            seq = (base, 0x200D, ext)
-            adjusted_emoji_zwj_sequences[seq] = 'Emoji_ZWJ_Sequence'
-            for modifier in range(0x1F3FB, 0x1F400):
-                seq = (base, modifier, 0x200D, ext)
-                adjusted_emoji_zwj_sequences[seq] = 'Emoji_ZWJ_Sequence'
 
     for sequence in _emoji_sequences.keys():
         sequence = tuple(ch for ch in sequence if ch != EMOJI_VS)
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 7de55aa..333a4f7d 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -28,6 +28,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -329,6 +330,50 @@
                 mUsageLimitStartTimeInMs, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes);
     }
 
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("UpdateIdentifier: ").append(mUpdateIdentifier).append("\n");
+        builder.append("CredentialPriority: ").append(mCredentialPriority).append("\n");
+        builder.append("SubscriptionCreationTime: ").append(
+                mSubscriptionCreationTimeInMs != Long.MIN_VALUE
+                ? new Date(mSubscriptionCreationTimeInMs) : "Not specified").append("\n");
+        builder.append("SubscriptionExpirationTime: ").append(
+                mSubscriptionExpirationTimeInMs != Long.MIN_VALUE
+                ? new Date(mSubscriptionExpirationTimeInMs) : "Not specified").append("\n");
+        builder.append("UsageLimitStartTime: ").append(mUsageLimitStartTimeInMs != Long.MIN_VALUE
+                ? new Date(mUsageLimitStartTimeInMs) : "Not specified").append("\n");
+        builder.append("UsageTimePeriod: ").append(mUsageLimitUsageTimePeriodInMinutes)
+                .append("\n");
+        builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
+        builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
+        if (mHomeSp != null) {
+            builder.append("HomeSP Begin ---\n");
+            builder.append(mHomeSp);
+            builder.append("HomeSP End ---\n");
+        }
+        if (mCredential != null) {
+            builder.append("Credential Begin ---\n");
+            builder.append(mCredential);
+            builder.append("Credential End ---\n");
+        }
+        if (mPolicy != null) {
+            builder.append("Policy Begin ---\n");
+            builder.append(mPolicy);
+            builder.append("Policy End ---\n");
+        }
+        if (mSubscriptionUpdate != null) {
+            builder.append("SubscriptionUpdate Begin ---\n");
+            builder.append(mSubscriptionUpdate);
+            builder.append("SubscriptionUpdate End ---\n");
+        }
+        if (mTrustRootCertList != null) {
+            builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet())
+                    .append("\n");
+        }
+        return builder.toString();
+    }
+
     /**
      * Validate the configuration data.
      *
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index d8da84f..67fa1bb 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -30,6 +30,7 @@
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
@@ -282,6 +283,18 @@
                     mAbleToShare, mEapType, mNonEapInnerMethod);
         }
 
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("Username: ").append(mUsername).append("\n");
+            builder.append("MachineManaged: ").append(mMachineManaged).append("\n");
+            builder.append("SoftTokenApp: ").append(mSoftTokenApp).append("\n");
+            builder.append("AbleToShare: ").append(mAbleToShare).append("\n");
+            builder.append("EAPType: ").append(mEapType).append("\n");
+            builder.append("AuthMethod: ").append(mNonEapInnerMethod).append("\n");
+            return builder.toString();
+        }
+
         /**
          * Validate the configuration data.
          *
@@ -440,6 +453,11 @@
             return Objects.hash(mCertType, mCertSha256Fingerprint);
         }
 
+        @Override
+        public String toString() {
+            return "CertificateType: " + mCertType + "\n";
+        }
+
         /**
          * Validate the configuration data.
          *
@@ -562,6 +580,14 @@
         }
 
         @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("IMSI: ").append(mImsi).append("\n");
+            builder.append("EAPType: ").append(mEapType).append("\n");
+            return builder.toString();
+        }
+
+        @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeString(mImsi);
             dest.writeInt(mEapType);
@@ -767,6 +793,33 @@
                 mCaCertificate, mClientCertificateChain, mClientPrivateKey);
     }
 
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Realm: ").append(mRealm).append("\n");
+        builder.append("CreationTime: ").append(mCreationTimeInMs != Long.MIN_VALUE
+                ? new Date(mCreationTimeInMs) : "Not specified").append("\n");
+        builder.append("ExpirationTime: ").append(mExpirationTimeInMs != Long.MIN_VALUE
+                ? new Date(mExpirationTimeInMs) : "Not specified").append("\n");
+        builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n");
+        if (mUserCredential != null) {
+            builder.append("UserCredential Begin ---\n");
+            builder.append(mUserCredential);
+            builder.append("UserCredential End ---\n");
+        }
+        if (mCertCredential != null) {
+            builder.append("CertificateCredential Begin ---\n");
+            builder.append(mCertCredential);
+            builder.append("CertificateCredential End ---\n");
+        }
+        if (mSimCredential != null) {
+            builder.append("SIMCredential Begin ---\n");
+            builder.append(mSimCredential);
+            builder.append("SIMCredential End ---\n");
+        }
+        return builder.toString();
+    }
+
     /**
      * Validate the configuration data.
      *
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
index 68bdf37..9192ab0 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -241,6 +241,20 @@
                 mMatchAnyOis, mOtherHomePartners, mRoamingConsortiumOis);
     }
 
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("FQDN: ").append(mFqdn).append("\n");
+        builder.append("FriendlyName: ").append(mFriendlyName).append("\n");
+        builder.append("IconURL: ").append(mIconUrl).append("\n");
+        builder.append("HomeNetworkIDs: ").append(mHomeNetworkIds).append("\n");
+        builder.append("MatchAllOIs: ").append(mMatchAllOis).append("\n");
+        builder.append("MatchAnyOIs: ").append(mMatchAnyOis).append("\n");
+        builder.append("OtherHomePartners: ").append(mOtherHomePartners).append("\n");
+        builder.append("RoamingConsortiumOIs: ").append(mRoamingConsortiumOis).append("\n");
+        return builder.toString();
+    }
+
     /**
      * Validate HomeSp data.
      *
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
index da36a11..1df70f8 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
@@ -249,6 +249,16 @@
             return Objects.hash(mFqdn, mFqdnExactMatch, mPriority, mCountries);
         }
 
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("FQDN: ").append(mFqdn).append("\n");
+            builder.append("ExactMatch: ").append("mFqdnExactMatch").append("\n");
+            builder.append("Priority: ").append(mPriority).append("\n");
+            builder.append("Countries: ").append(mCountries).append("\n");
+            return builder.toString();
+        }
+
         /**
          * Validate RoamingParnter data.
          *
@@ -390,6 +400,29 @@
                 mPolicyUpdate);
     }
 
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("MinHomeDownlinkBandwidth: ").append(mMinHomeDownlinkBandwidth)
+                .append("\n");
+        builder.append("MinHomeUplinkBandwidth: ").append(mMinHomeUplinkBandwidth).append("\n");
+        builder.append("MinRoamingDownlinkBandwidth: ").append(mMinRoamingDownlinkBandwidth)
+                .append("\n");
+        builder.append("MinRoamingUplinkBandwidth: ").append(mMinRoamingUplinkBandwidth)
+                .append("\n");
+        builder.append("ExcludedSSIDList: ").append(mExcludedSsidList).append("\n");
+        builder.append("RequiredProtoPortMap: ").append(mRequiredProtoPortMap).append("\n");
+        builder.append("MaximumBSSLoadValue: ").append(mMaximumBssLoadValue).append("\n");
+        builder.append("PreferredRoamingPartnerList: ").append(mPreferredRoamingPartnerList)
+                .append("\n");
+        if (mPolicyUpdate != null) {
+            builder.append("PolicyUpdate Begin ---\n");
+            builder.append(mPolicyUpdate);
+            builder.append("PolicyUpdate End ---\n");
+        }
+        return builder.toString();
+    }
+
     /**
      * Validate Policy data.
      *
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
index ae051b0..a7adfeb 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -247,6 +247,18 @@
                 mTrustRootCertSha256Fingerprint);
     }
 
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("UpdateInterval: ").append(mUpdateIntervalInMinutes).append("\n");
+        builder.append("UpdateMethod: ").append(mUpdateMethod).append("\n");
+        builder.append("Restriction: ").append(mRestriction).append("\n");
+        builder.append("ServerURI: ").append(mServerUri).append("\n");
+        builder.append("Username: ").append(mUsername).append("\n");
+        builder.append("TrustRootCertURL: ").append(mTrustRootCertUrl).append("\n");
+        return builder.toString();
+    }
+
     /**
      * Validate UpdateParameter data.
      *