Merge "Enable simplified version of RootRenderNode for host"
diff --git a/api/removed.txt b/api/removed.txt
index 8ebded13..7b1d241 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -23,15 +23,6 @@
 
 }
 
-package android.app.admin {
-
-  public class DevicePolicyManager {
-    method @Deprecated @Nullable public android.os.UserHandle createAndInitializeUser(@NonNull android.content.ComponentName, String, String, @NonNull android.content.ComponentName, android.os.Bundle);
-    method @Deprecated @Nullable public android.os.UserHandle createUser(@NonNull android.content.ComponentName, String);
-  }
-
-}
-
 package android.app.slice {
 
   public final class Slice implements android.os.Parcelable {
@@ -104,10 +95,6 @@
 
 package android.content.pm {
 
-  public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
-    field @Deprecated public String volumeUuid;
-  }
-
   public class PackageInfo implements android.os.Parcelable {
     field public static final int REQUESTED_PERMISSION_REQUIRED = 1; // 0x1
   }
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 3e26a9b..07b8969 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -23,15 +23,6 @@
 
 }
 
-package android.app.admin {
-
-  public class DevicePolicyManager {
-    method @Deprecated @Nullable public String getDeviceInitializerApp();
-    method @Deprecated @Nullable public android.content.ComponentName getDeviceInitializerComponent();
-  }
-
-}
-
 package android.app.backup {
 
   public class RestoreSession {
diff --git a/api/test-current.txt b/api/test-current.txt
index 14c70e3..050ec52 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -700,6 +700,7 @@
   }
 
   public abstract class PackageManager {
+    method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract boolean arePermissionsIndividuallyControlled();
     method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int);
     method @Nullable public String getIncidentReportApproverPackageName();
@@ -713,6 +714,7 @@
     method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
     method @Nullable public String getWellbeingPackageName();
     method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+    method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS"}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, int, int, @NonNull android.os.UserHandle);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
@@ -735,6 +737,10 @@
     field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
   }
 
+  public static interface PackageManager.OnPermissionsChangedListener {
+    method public void onPermissionsChanged(int);
+  }
+
   public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     field public static final int FLAG_REMOVED = 2; // 0x2
     field public static final int PROTECTION_FLAG_APP_PREDICTOR = 2097152; // 0x200000
@@ -2320,6 +2326,7 @@
   }
 
   public static interface DeviceConfig.WindowManager {
+    field public static final String KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE = "system_gestures_excluded_by_pre_q_sticky_immersive";
     field public static final String KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP = "system_gesture_exclusion_limit_dp";
   }
 
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index ee500d7..ceabd39 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -6360,6 +6360,9 @@
 
     // CPU Vulkan implementation is in use.
     optional bool cpu_vulkan_in_use = 6;
+
+    // App is not doing pre-rotation correctly.
+    optional bool false_prerotation = 7;
 }
 
 /*
diff --git a/cmds/statsd/src/external/GpuStatsPuller.cpp b/cmds/statsd/src/external/GpuStatsPuller.cpp
index 0d3aca0..bbdb540 100644
--- a/cmds/statsd/src/external/GpuStatsPuller.cpp
+++ b/cmds/statsd/src/external/GpuStatsPuller.cpp
@@ -96,6 +96,7 @@
         if (!event->write(int64VectorToProtoByteString(info.vkDriverLoadingTime))) return false;
         if (!event->write(int64VectorToProtoByteString(info.angleDriverLoadingTime))) return false;
         if (!event->write(info.cpuVulkanInUse)) return false;
+        if (!event->write(info.falsePrerotation)) return false;
         event->init();
         data->emplace_back(event);
     }
diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
index bdc52b0..e91fb0d 100644
--- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
@@ -56,8 +56,9 @@
 static const int32_t CPU_VULKAN_VERSION           = 2;
 static const int32_t GLES_VERSION                 = 3;
 static const bool CPU_VULKAN_IN_USE               = true;
+static const bool FALSE_PREROTATION               = true;
 static const size_t NUMBER_OF_VALUES_GLOBAL       = 13;
-static const size_t NUMBER_OF_VALUES_APP          = 6;
+static const size_t NUMBER_OF_VALUES_APP          = 7;
 // clang-format on
 
 class MockGpuStatsPuller : public GpuStatsPuller {
@@ -150,6 +151,7 @@
     EXPECT_TRUE(event->write(int64VectorToProtoByteString(vkDriverLoadingTime)));
     EXPECT_TRUE(event->write(int64VectorToProtoByteString(angleDriverLoadingTime)));
     EXPECT_TRUE(event->write(CPU_VULKAN_IN_USE));
+    EXPECT_TRUE(event->write(FALSE_PREROTATION));
     event->init();
     inData.emplace_back(event);
     MockGpuStatsPuller mockPuller(android::util::GPU_STATS_APP_INFO, &inData);
@@ -168,6 +170,7 @@
     EXPECT_EQ(int64VectorToProtoByteString(angleDriverLoadingTime),
               outData[0]->getValues()[4].mValue.str_value);
     EXPECT_EQ(CPU_VULKAN_IN_USE, outData[0]->getValues()[5].mValue.int_value);
+    EXPECT_EQ(FALSE_PREROTATION, outData[0]->getValues()[6].mValue.int_value);
 }
 
 }  // namespace statsd
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 3bf659b..755f047 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -120,6 +120,7 @@
 
         mActivityTaskManager = ActivityTaskManager.getService();
         mSurfaceView = new SurfaceView(context);
+        mSurfaceView.setUseAlpha();
         mSurfaceView.setAlpha(0f);
         mSurfaceCallback = new SurfaceCallback();
         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 60f1424..fb72e65 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -240,7 +240,8 @@
     public @interface UidState {}
 
     /**
-     * Uid state: The UID is a foreground persistent app.
+     * Uid state: The UID is a foreground persistent app. The lower the UID
+     * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
@@ -248,7 +249,8 @@
     public static final int UID_STATE_PERSISTENT = 100;
 
     /**
-     * Uid state: The UID is top foreground app.
+     * Uid state: The UID is top foreground app. The lower the UID
+     * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
@@ -257,6 +259,7 @@
 
     /**
      * Uid state: The UID is running a foreground service of location type.
+     * The lower the UID state the more important the UID is for the user.
      * @hide
      */
     @TestApi
@@ -264,7 +267,8 @@
     public static final int UID_STATE_FOREGROUND_SERVICE_LOCATION = 300;
 
     /**
-     * Uid state: The UID is running a foreground service.
+     * Uid state: The UID is running a foreground service. The lower the UID
+     * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
@@ -279,7 +283,8 @@
     public static final int UID_STATE_MAX_LAST_NON_RESTRICTED = UID_STATE_FOREGROUND_SERVICE;
 
     /**
-     * Uid state: The UID is a foreground app.
+     * Uid state: The UID is a foreground app. The lower the UID
+     * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
@@ -287,7 +292,8 @@
     public static final int UID_STATE_FOREGROUND = 500;
 
     /**
-     * Uid state: The UID is a background app.
+     * Uid state: The UID is a background app. The lower the UID
+     * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
@@ -295,7 +301,8 @@
     public static final int UID_STATE_BACKGROUND = 600;
 
     /**
-     * Uid state: The UID is a cached app.
+     * Uid state: The UID is a cached app. The lower the UID
+     * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
@@ -2507,7 +2514,7 @@
         }
 
         /**
-         * @return The duration of the operation in milliseconds.
+         * @return The duration of the operation in milliseconds. The duration is in wall time.
          */
         public long getDuration() {
             return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
@@ -2515,7 +2522,7 @@
 
         /**
          * Return the duration in milliseconds the app accessed this op while
-         * in the foreground.
+         * in the foreground. The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -2534,7 +2541,7 @@
 
         /**
          * Return the duration in milliseconds the app accessed this op while
-         * in the background.
+         * in the background. The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -2553,7 +2560,7 @@
 
         /**
          * Return the duration in milliseconds the app accessed this op for
-         * a given range of UID states.
+         * a given range of UID states. The duration is in wall time.
          *
          * @param fromUidState The UID state for which to query. Could be one of
          * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
@@ -3968,6 +3975,7 @@
 
         /**
          * Gets the total duration the app op was accessed (performed) in the foreground.
+         * The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -3986,6 +3994,7 @@
 
         /**
          * Gets the total duration the app op was accessed (performed) in the background.
+         * The duration is in wall time.
          *
          * @param flags The flags which are any combination of
          * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
@@ -4004,7 +4013,7 @@
 
         /**
          * Gets the total duration the app op was accessed (performed) for a given
-         * range of UID states.
+         * range of UID states. The duration is in wall time.
          *
          * @param fromUidState The UID state from which to query. Could be one of
          * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 26720fc..2957309 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -386,15 +386,6 @@
      */
     void resizePinnedStack(in Rect pinnedBounds, in Rect tempPinnedTaskBounds);
 
-    /**
-     * Updates override configuration applied to specific display.
-     * @param values Update values for display configuration. If null is passed it will request the
-     *               Window Manager to compute new config for the specified display.
-     * @param displayId Id of the display to apply the config to.
-     * @throws RemoteException
-     * @return Returns true if the configuration was updated.
-     */
-    boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
     void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback,
             in CharSequence message);
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 441ff6b..5624ba5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6066,30 +6066,6 @@
 
     /**
      * @hide
-     * @deprecated Do not use
-     * @removed
-     */
-    @Deprecated
-    @SystemApi
-    @SuppressLint("Doclava125")
-    public @Nullable String getDeviceInitializerApp() {
-        return null;
-    }
-
-    /**
-     * @hide
-     * @deprecated Do not use
-     * @removed
-     */
-    @Deprecated
-    @SystemApi
-    @SuppressLint("Doclava125")
-    public @Nullable ComponentName getDeviceInitializerComponent() {
-        return null;
-    }
-
-    /**
-     * @hide
      * @deprecated Use #ACTION_SET_PROFILE_OWNER
      * Sets the given component as an active admin and registers the package as the profile
      * owner for this user. The package must already be installed and there shouldn't be
@@ -7394,60 +7370,6 @@
     }
 
     /**
-     * Called by a device owner to create a user with the specified name. The UserHandle returned
-     * by this method should not be persisted as user handles are recycled as users are removed and
-     * created. If you need to persist an identifier for this user, use
-     * {@link UserManager#getSerialNumberForUser}.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param name the user's name
-     * @see UserHandle
-     * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
-     *         user could not be created.
-     *
-     * @deprecated From {@link android.os.Build.VERSION_CODES#M}
-     * @removed From {@link android.os.Build.VERSION_CODES#N}
-     */
-    @Deprecated
-    public @Nullable UserHandle createUser(@NonNull ComponentName admin, String name) {
-        return null;
-    }
-
-    /**
-     * Called by a device owner to create a user with the specified name. The UserHandle returned
-     * by this method should not be persisted as user handles are recycled as users are removed and
-     * created. If you need to persist an identifier for this user, use
-     * {@link UserManager#getSerialNumberForUser}.  The new user will be started in the background
-     * immediately.
-     *
-     * <p> profileOwnerComponent is the {@link DeviceAdminReceiver} to be the profile owner as well
-     * as registered as an active admin on the new user.  The profile owner package will be
-     * installed on the new user if it already is installed on the device.
-     *
-     * <p>If the optionalInitializeData is not null, then the extras will be passed to the
-     * profileOwnerComponent when onEnable is called.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param name the user's name
-     * @param ownerName the human readable name of the organisation associated with this DPM.
-     * @param profileOwnerComponent The {@link DeviceAdminReceiver} that will be an active admin on
-     *      the user.
-     * @param adminExtras Extras that will be passed to onEnable of the admin receiver
-     *      on the new user.
-     * @see UserHandle
-     * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
-     *         user could not be created.
-     *
-     * @deprecated From {@link android.os.Build.VERSION_CODES#M}
-     * @removed From {@link android.os.Build.VERSION_CODES#N}
-     */
-    @Deprecated
-    public @Nullable UserHandle createAndInitializeUser(@NonNull ComponentName admin, String name,
-            String ownerName, @NonNull ComponentName profileOwnerComponent, Bundle adminExtras) {
-        return null;
-    }
-
-    /**
       * Flag used by {@link #createAndManageUser} to skip setup wizard after creating a new user.
       */
     public static final int SKIP_SETUP_WIZARD = 0x0001;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c3dd827..341fc26 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3067,7 +3067,7 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(trackingBug = 136728678)
     public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
             Handler handler, UserHandle user) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 9bc5f80..f75cd23 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -783,8 +783,7 @@
      */
     public float minAspectRatio;
 
-    /** @removed */
-    @Deprecated
+    /** @hide */
     public String volumeUuid;
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e08f4a2..b845a37 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -105,6 +105,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public interface OnPermissionsChangedListener {
 
         /**
@@ -6413,6 +6414,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
     public abstract void addOnPermissionsChangeListener(
             @NonNull OnPermissionsChangedListener listener);
@@ -6425,6 +6427,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
     public abstract void removeOnPermissionsChangeListener(
             @NonNull OnPermissionsChangedListener listener);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index e5ef67b..2420a61 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1157,8 +1157,11 @@
             }
         }
 
-        if (mObject != 0) {
-            nativeDestroy(mObject);
+        synchronized (this) {
+            if (mObject != 0) {
+                nativeDestroy(mObject);
+                mObject = 0;
+            }
         }
     }
 
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index ecd16dd..caa6a43 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1554,7 +1554,10 @@
         }
     }
 
-    public final static class HistoryItem implements Parcelable {
+    /**
+     * Battery history record.
+     */
+    public static final class HistoryItem {
         public HistoryItem next;
 
         // The time of this event in milliseconds, as per SystemClock.elapsedRealtime().
@@ -1789,16 +1792,10 @@
         public HistoryItem() {
         }
 
-        public HistoryItem(long time, Parcel src) {
-            this.time = time;
-            numReadInts = 2;
+        public HistoryItem(Parcel src) {
             readFromParcel(src);
         }
 
-        public int describeContents() {
-            return 0;
-        }
-
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeLong(time);
             int bat = (((int)cmd)&0xff)
@@ -1835,6 +1832,7 @@
 
         public void readFromParcel(Parcel src) {
             int start = src.dataPosition();
+            time = src.readLong();
             int bat = src.readInt();
             cmd = (byte)(bat&0xff);
             batteryLevel = (byte)((bat>>8)&0xff);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index a32b7c6..cdd0d45 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -671,16 +671,6 @@
     private boolean fetchUsapPoolEnabledPropWithMinInterval() {
         final long currentTimestamp = SystemClock.elapsedRealtime();
 
-        if (SystemProperties.get("dalvik.vm.boot-image", "").endsWith("apex.art")) {
-            // TODO(b/119800099): In jitzygote mode, we want to start using USAP processes
-            // only once the boot classpath has been compiled. There is currently no callback
-            // from the runtime to notify the zygote about end of compilation, so for now just
-            // arbitrarily start USAP processes 15 seconds after boot.
-            if (currentTimestamp <= 15000) {
-                return false;
-            }
-        }
-
         if (mIsFirstPropCheck
                 || (currentTimestamp - mLastPropCheckTimestamp >= Zygote.PROPERTY_CHECK_INTERVAL)) {
             mIsFirstPropCheck = false;
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 48faf11..d0401e3 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -333,6 +333,26 @@
          */
         @TestApi
         String KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP = "system_gesture_exclusion_limit_dp";
+
+        /**
+         * Key for controlling whether system gestures are implicitly excluded by windows requesting
+         * sticky immersive mode from apps that are targeting an SDK prior to Q.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
+         * @hide
+         */
+        @TestApi
+        String KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE =
+                "system_gestures_excluded_by_pre_q_sticky_immersive";
+
+        /**
+         * Key for controlling which packages are explicitly blocked from running at refresh rates
+         * higher than 90hz.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
+         * @hide
+         */
+        String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
     }
 
     private static final Object sLock = new Object();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2ee6b7d..5308cd3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -90,7 +90,6 @@
 import android.util.Log;
 import android.util.MemoryIntArray;
 import android.view.Display;
-import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.widget.ILockSettings;
@@ -9298,13 +9297,6 @@
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
             CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
-            if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
-                CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
-                CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
-                CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
-                CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER);
-                CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE);
-            }
         }
 
         /** @hide */
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index fa9949c..9cc6b28 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -99,6 +99,7 @@
 public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
     private static final String TAG = "SurfaceView";
     private static final boolean DEBUG = false;
+    private static final boolean DEBUG_POSITION = false;
 
     @UnsupportedAppUsage
     final ArrayList<SurfaceHolder.Callback> mCallbacks = new ArrayList<>();
@@ -124,6 +125,7 @@
     // we need to preserve the old one until the new one has drawn.
     SurfaceControl mDeferredDestroySurfaceControl;
     SurfaceControl mBackgroundControl;
+    final Object mSurfaceControlLock = new Object();
     final Rect mTmpRect = new Rect();
 
     Paint mRoundedViewportPaint;
@@ -161,6 +163,9 @@
     @UnsupportedAppUsage
     int mRequestedFormat = PixelFormat.RGB_565;
 
+    boolean mUseAlpha = false;
+    float mSurfaceAlpha = 1f;
+
     @UnsupportedAppUsage
     boolean mHaveFrame = false;
     boolean mSurfaceCreated = false;
@@ -276,6 +281,152 @@
         updateSurface();
     }
 
+    /**
+     * Make alpha value of this view reflect onto the surface. This can only be called from at most
+     * one SurfaceView within a view tree.
+     *
+     * <p class="note"><strong>Note:</strong> Alpha value of the view is ignored and the underlying
+     * surface is rendered opaque by default.</p>
+     *
+     * @hide
+     */
+    public void setUseAlpha() {
+        if (!mUseAlpha) {
+            mUseAlpha = true;
+            updateSurfaceAlpha();
+        }
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        // Sets the opacity of the view to a value, where 0 means the view is completely transparent
+        // and 1 means the view is completely opaque.
+        //
+        // Note: Alpha value of this view is ignored by default. To enable alpha blending, you need
+        // to call setUseAlpha() as well.
+        // This view doesn't support translucent opacity if the view is located z-below, since the
+        // logic to punch a hole in the view hierarchy cannot handle such case. See also
+        // #clearSurfaceViewPort(Canvas)
+        if (DEBUG) {
+            Log.d(TAG, System.identityHashCode(this)
+                    + " setAlpha: mUseAlpha = " + mUseAlpha + " alpha=" + alpha);
+        }
+        super.setAlpha(alpha);
+        updateSurfaceAlpha();
+    }
+
+    private float getFixedAlpha() {
+        // Compute alpha value to be set on the underlying surface.
+        final float alpha = getAlpha();
+        return mUseAlpha && (mSubLayer > 0 || alpha == 0f) ? alpha : 1f;
+    }
+
+    private void updateSurfaceAlpha() {
+        if (!mUseAlpha) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: setUseAlpha() is not called, ignored.");
+            }
+            return;
+        }
+        final float viewAlpha = getAlpha();
+        if (mSubLayer < 0 && 0f < viewAlpha && viewAlpha < 1f) {
+            Log.w(TAG, System.identityHashCode(this)
+                    + " updateSurfaceAlpha:"
+                    + " translucent color is not supported for a surface placed z-below.");
+        }
+        if (!mHaveFrame) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: has no surface.");
+            }
+            return;
+        }
+        final ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: ViewRootImpl not available.");
+            }
+            return;
+        }
+        if (mSurfaceControl == null) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + "updateSurfaceAlpha:"
+                        + " surface is not yet created, or already released.");
+            }
+            return;
+        }
+        final Surface parent = viewRoot.mSurface;
+        if (parent == null || !parent.isValid()) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurfaceAlpha: ViewRootImpl has no valid surface");
+            }
+            return;
+        }
+        final float alpha = getFixedAlpha();
+        if (alpha != mSurfaceAlpha) {
+            if (isHardwareAccelerated()) {
+                /*
+                 * Schedule a callback that reflects an alpha value onto the underlying surfaces.
+                 * This gets called on a RenderThread worker thread, so members accessed here must
+                 * be protected by a lock.
+                 */
+                viewRoot.registerRtFrameCallback(frame -> {
+                    try {
+                        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                        synchronized (mSurfaceControlLock) {
+                            if (!parent.isValid()) {
+                                if (DEBUG) {
+                                    Log.d(TAG, System.identityHashCode(this)
+                                            + " updateSurfaceAlpha RT:"
+                                            + " ViewRootImpl has no valid surface");
+                                }
+                                return;
+                            }
+                            if (mSurfaceControl == null) {
+                                if (DEBUG) {
+                                    Log.d(TAG, System.identityHashCode(this)
+                                            + "updateSurfaceAlpha RT:"
+                                            + " mSurfaceControl has already released");
+                                }
+                                return;
+                            }
+                            if (DEBUG) {
+                                Log.d(TAG, System.identityHashCode(this)
+                                        + " updateSurfaceAlpha RT: set alpha=" + alpha);
+                            }
+                            t.setAlpha(mSurfaceControl, alpha);
+                            t.deferTransactionUntilSurface(mSurfaceControl, parent, frame);
+                        }
+                        // It's possible that mSurfaceControl is released in the UI thread before
+                        // the transaction completes. If that happens, an exception is thrown, which
+                        // must be caught immediately.
+                        t.apply();
+                    } catch (Exception e) {
+                        Log.e(TAG, System.identityHashCode(this)
+                                + "updateSurfaceAlpha RT: Exception during surface transaction", e);
+                    }
+                });
+                damageInParent();
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, System.identityHashCode(this)
+                            + " updateSurfaceAlpha: set alpha=" + alpha);
+                }
+                SurfaceControl.openTransaction();
+                try {
+                    mSurfaceControl.setAlpha(alpha);
+                } finally {
+                    SurfaceControl.closeTransaction();
+                }
+            }
+            mSurfaceAlpha = alpha;
+        }
+    }
+
     private void performDrawFinished() {
         if (mPendingReportDraws > 0) {
             mDrawFinished = true;
@@ -326,11 +477,7 @@
         mRequestedVisible = false;
 
         updateSurface();
-        if (mSurfaceControl != null) {
-            mTmpTransaction.remove(mSurfaceControl).apply();
-        }
-        mSurfaceControl = null;
-
+        releaseSurfaces();
         mHaveFrame = false;
 
         super.onDetachedFromWindow();
@@ -499,15 +646,6 @@
         }
     }
 
-    private Rect getParentSurfaceInsets() {
-        final ViewRootImpl root = getViewRootImpl();
-        if (root == null) {
-            return null;
-        } else {
-            return root.mWindowAttributes.surfaceInsets;
-        }
-    }
-
     private void updateBackgroundVisibilityInTransaction(SurfaceControl viewRoot) {
         if (mBackgroundControl == null) {
             return;
@@ -521,24 +659,34 @@
     }
 
     private void releaseSurfaces() {
-        if (mSurfaceControl != null) {
-            mTmpTransaction.remove(mSurfaceControl);
-            mSurfaceControl = null;
+        synchronized (mSurfaceControlLock) {
+            if (mSurfaceControl != null) {
+                mTmpTransaction.remove(mSurfaceControl);
+                mSurfaceControl = null;
+            }
+            if (mBackgroundControl != null) {
+                mTmpTransaction.remove(mBackgroundControl);
+                mBackgroundControl = null;
+            }
+            mTmpTransaction.apply();
         }
-        if (mBackgroundControl != null) {
-            mTmpTransaction.remove(mBackgroundControl);
-            mBackgroundControl = null;
-        }
-        mTmpTransaction.apply();
+        mSurfaceAlpha = 1f;
     }
 
     /** @hide */
     protected void updateSurface() {
         if (!mHaveFrame) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this) + " updateSurface: has no frame");
+            }
             return;
         }
         ViewRootImpl viewRoot = getViewRootImpl();
         if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+            if (DEBUG) {
+                Log.d(TAG, System.identityHashCode(this)
+                        + " updateSurface: no valid surface");
+            }
             return;
         }
 
@@ -552,20 +700,25 @@
         int myHeight = mRequestedHeight;
         if (myHeight <= 0) myHeight = getHeight();
 
+        final float alpha = getFixedAlpha();
         final boolean formatChanged = mFormat != mRequestedFormat;
         final boolean visibleChanged = mVisible != mRequestedVisible;
+        final boolean alphaChanged = mSurfaceAlpha != alpha;
         final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
                 && mRequestedVisible;
         final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
         final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
         boolean redrawNeeded = false;
 
-        if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+        if (creating || formatChanged || sizeChanged || visibleChanged || (mUseAlpha
+                && alphaChanged) || windowVisibleChanged) {
             getLocationInWindow(mLocation);
 
             if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                     + "Changes: creating=" + creating
                     + " format=" + formatChanged + " size=" + sizeChanged
+                    + " visible=" + visibleChanged + " alpha=" + alphaChanged
+                    + " mUseAlpha=" + mUseAlpha
                     + " visible=" + visibleChanged
                     + " left=" + (mWindowSpaceLeft != mLocation[0])
                     + " top=" + (mWindowSpaceTop != mLocation[1]));
@@ -587,7 +740,7 @@
                     translator.translateRectInAppWindowToScreen(mScreenRect);
                 }
 
-                final Rect surfaceInsets = getParentSurfaceInsets();
+                final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
                 mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
 
                 if (creating) {
@@ -636,6 +789,10 @@
                             mSurfaceControl.hide();
                         }
                         updateBackgroundVisibilityInTransaction(viewRoot.getSurfaceControl());
+                        if (mUseAlpha) {
+                            mSurfaceControl.setAlpha(alpha);
+                            mSurfaceAlpha = alpha;
+                        }
 
                         // While creating the surface, we will set it's initial
                         // geometry. Outside of that though, we should generally
@@ -778,7 +935,6 @@
                     mIsCreating = false;
                     if (mSurfaceControl != null && !mSurfaceCreated) {
                         mSurface.release();
-
                         releaseSurfaces();
                     }
                 }
@@ -818,10 +974,13 @@
 
                 if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
                     try {
-                        if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
-                                "position = [%d, %d, %d, %d]", System.identityHashCode(this),
-                                mScreenRect.left, mScreenRect.top,
-                                mScreenRect.right, mScreenRect.bottom));
+                        if (DEBUG_POSITION) {
+                            Log.d(TAG, String.format("%d updateSurfacePosition UI, "
+                                            + "position = [%d, %d, %d, %d]",
+                                    System.identityHashCode(this),
+                                    mScreenRect.left, mScreenRect.top,
+                                    mScreenRect.right, mScreenRect.bottom));
+                        }
                         setParentSpaceRectangle(mScreenRect, -1);
                     } catch (Exception ex) {
                         Log.e(TAG, "Exception configuring surface", ex);
@@ -872,7 +1031,6 @@
         if (mViewVisibility) {
             mRtTransaction.show(surface);
         }
-
     }
 
     private void setParentSpaceRectangle(Rect position, long frameNumber) {
@@ -913,7 +1071,7 @@
                 return;
             }
             try {
-                if (DEBUG) {
+                if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
                             "%d updateSurfacePosition RenderWorker, frameNr = %d, "
                                     + "position = [%d, %d, %d, %d]",
diff --git a/core/java/android/view/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
index 05143a1..0689806 100644
--- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java
+++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
@@ -43,17 +43,6 @@
      */
     private static final String PROP_DEBUG_MULTI_CLIENT_IME = "persist.debug.multi_client_ime";
 
-    /**
-     * System property key for debugging purpose. The value must be empty, "1", or "0".
-     *
-     * <p>Values 'y', 'yes', '1', 'true' or 'on' are considered true.</p>
-     *
-     * <p>To set, run "adb root && adb shell setprop persist.debug.per_profile_ime 1".</p>
-     *
-     * <p>This value will be ignored when {@link Build#IS_DEBUGGABLE} returns {@code false}.</p>
-     */
-    private static final String PROP_DEBUG_PER_PROFILE_IME = "persist.debug.per_profile_ime";
-
     @Nullable
     private static ComponentName getMultiClientImeComponentName() {
         if (Build.IS_DEBUGGABLE) {
@@ -91,19 +80,4 @@
      */
     @TestApi
     public static final boolean MULTI_CLIENT_IME_ENABLED = (sMultiClientImeComponentName != null);
-
-    /**
-     * {@code true} when per-profile IME is enabled.
-     * @hide
-     */
-    public static final boolean PER_PROFILE_IME_ENABLED;
-    static {
-        if (MULTI_CLIENT_IME_ENABLED) {
-            PER_PROFILE_IME_ENABLED = true;
-        } else if (Build.IS_DEBUGGABLE) {
-            PER_PROFILE_IME_ENABLED = SystemProperties.getBoolean(PROP_DEBUG_PER_PROFILE_IME, true);
-        } else {
-            PER_PROFILE_IME_ENABLED = true;
-        }
-    }
 }
diff --git a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java
index 6b90588..3164567 100644
--- a/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java
+++ b/core/java/android/view/textclassifier/ActionsModelParamsSupplier.java
@@ -60,7 +60,9 @@
     private boolean mParsed = true;
 
     public ActionsModelParamsSupplier(Context context, @Nullable Runnable onChangedListener) {
-        mAppContext = Preconditions.checkNotNull(context).getApplicationContext();
+        final Context appContext = Preconditions.checkNotNull(context).getApplicationContext();
+        // Some contexts don't have an app context.
+        mAppContext = appContext != null ? appContext : context;
         mOnChangedListener = onChangedListener == null ? () -> {} : onChangedListener;
         mSettingsObserver = new SettingsObserver(mAppContext, () -> {
             synchronized (mLock) {
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index aeb99b8..f7f503a 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -21,10 +21,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.annotation.UserIdInt;
 import android.app.Person;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.SpannedString;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -316,6 +318,8 @@
         private final List<String> mHints;
         @Nullable
         private String mCallingPackageName;
+        @UserIdInt
+        private int mUserId = UserHandle.USER_NULL;
         @NonNull
         private Bundle mExtras;
 
@@ -340,6 +344,7 @@
             List<String> hints = new ArrayList<>();
             in.readStringList(hints);
             String callingPackageName = in.readString();
+            int userId = in.readInt();
             Bundle extras = in.readBundle();
             Request request = new Request(
                     conversation,
@@ -348,6 +353,7 @@
                     hints,
                     extras);
             request.setCallingPackageName(callingPackageName);
+            request.setUserId(userId);
             return request;
         }
 
@@ -358,6 +364,7 @@
             parcel.writeInt(mMaxSuggestions);
             parcel.writeStringList(mHints);
             parcel.writeString(mCallingPackageName);
+            parcel.writeInt(mUserId);
             parcel.writeBundle(mExtras);
         }
 
@@ -428,6 +435,25 @@
         }
 
         /**
+         * Sets the id of the user that sent this request.
+         * <p>
+         * Package-private for SystemTextClassifier's use.
+         * @hide
+         */
+        void setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Returns the id of the user that sent this request.
+         * @hide
+         */
+        @UserIdInt
+        public int getUserId() {
+            return mUserId;
+        }
+
+        /**
          * Returns the extended data related to this request.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 374e667..80c728f 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -19,8 +19,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.view.textclassifier.TextClassifier.EntityType;
 import android.view.textclassifier.TextClassifier.WidgetType;
 
@@ -127,6 +129,7 @@
     private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN;
     private @InvocationMethod int mInvocationMethod;
     @Nullable private String mWidgetVersion;
+    private @UserIdInt int mUserId = UserHandle.USER_NULL;
     @Nullable private String mResultId;
     private long mEventTime;
     private long mDurationSinceSessionStart;
@@ -158,6 +161,7 @@
         mEntityType = in.readString();
         mWidgetVersion = in.readInt() > 0 ? in.readString() : null;
         mPackageName = in.readString();
+        mUserId = in.readInt();
         mWidgetType = in.readString();
         mInvocationMethod = in.readInt();
         mResultId = in.readString();
@@ -184,6 +188,7 @@
             dest.writeString(mWidgetVersion);
         }
         dest.writeString(mPackageName);
+        dest.writeInt(mUserId);
         dest.writeString(mWidgetType);
         dest.writeInt(mInvocationMethod);
         dest.writeString(mResultId);
@@ -405,6 +410,15 @@
     }
 
     /**
+     * Returns the id of this event's user.
+     * @hide
+     */
+    @UserIdInt
+    public int getUserId() {
+        return mUserId;
+    }
+
+    /**
      * Returns the type of widget that was involved in triggering this event.
      */
     @WidgetType
@@ -430,6 +444,7 @@
         mPackageName = context.getPackageName();
         mWidgetType = context.getWidgetType();
         mWidgetVersion = context.getWidgetVersion();
+        mUserId = context.getUserId();
     }
 
     /**
@@ -616,7 +631,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
-                mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mResultId,
+                mWidgetVersion, mPackageName, mUserId, mWidgetType, mInvocationMethod, mResultId,
                 mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
                 mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
     }
@@ -637,6 +652,7 @@
                 && Objects.equals(mEntityType, other.mEntityType)
                 && Objects.equals(mWidgetVersion, other.mWidgetVersion)
                 && Objects.equals(mPackageName, other.mPackageName)
+                && mUserId == other.mUserId
                 && Objects.equals(mWidgetType, other.mWidgetType)
                 && mInvocationMethod == other.mInvocationMethod
                 && Objects.equals(mResultId, other.mResultId)
@@ -656,12 +672,12 @@
         return String.format(Locale.US,
                 "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, "
                         + "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
-                        + "resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
+                        + "userId=%d, resultId=%s, eventTime=%d, durationSinceSessionStart=%d, "
                         + "durationSincePreviousEvent=%d, eventIndex=%d,"
                         + "sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}",
                 mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
                 mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod,
-                mResultId, mEventTime, mDurationSinceSessionStart,
+                mUserId, mResultId, mEventTime, mDurationSinceSessionStart,
                 mDurationSincePreviousEvent, mEventIndex,
                 mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
     }
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 8f8766e..b5f972a 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.content.Context;
 import android.os.Bundle;
@@ -50,6 +51,10 @@
     private final TextClassificationConstants mSettings;
     private final TextClassifier mFallback;
     private final String mPackageName;
+    // NOTE: Always set this before sending a request to the manager service otherwise the manager
+    // service will throw a remote exception.
+    @UserIdInt
+    private final int mUserId;
     private TextClassificationSessionId mSessionId;
 
     public SystemTextClassifier(Context context, TextClassificationConstants settings)
@@ -60,6 +65,7 @@
         mFallback = context.getSystemService(TextClassificationManager.class)
                 .getTextClassifier(TextClassifier.LOCAL);
         mPackageName = Preconditions.checkNotNull(context.getOpPackageName());
+        mUserId = context.getUserId();
     }
 
     /**
@@ -72,6 +78,7 @@
         Utils.checkMainThread();
         try {
             request.setCallingPackageName(mPackageName);
+            request.setUserId(mUserId);
             final BlockingCallback<TextSelection> callback =
                     new BlockingCallback<>("textselection");
             mManagerService.onSuggestSelection(mSessionId, request, callback);
@@ -95,6 +102,7 @@
         Utils.checkMainThread();
         try {
             request.setCallingPackageName(mPackageName);
+            request.setUserId(mUserId);
             final BlockingCallback<TextClassification> callback =
                     new BlockingCallback<>("textclassification");
             mManagerService.onClassifyText(mSessionId, request, callback);
@@ -123,6 +131,7 @@
 
         try {
             request.setCallingPackageName(mPackageName);
+            request.setUserId(mUserId);
             final BlockingCallback<TextLinks> callback =
                     new BlockingCallback<>("textlinks");
             mManagerService.onGenerateLinks(mSessionId, request, callback);
@@ -167,6 +176,7 @@
 
         try {
             request.setCallingPackageName(mPackageName);
+            request.setUserId(mUserId);
             final BlockingCallback<TextLanguage> callback =
                     new BlockingCallback<>("textlanguage");
             mManagerService.onDetectLanguage(mSessionId, request, callback);
@@ -187,6 +197,7 @@
 
         try {
             request.setCallingPackageName(mPackageName);
+            request.setUserId(mUserId);
             final BlockingCallback<ConversationActions> callback =
                     new BlockingCallback<>("conversation-actions");
             mManagerService.onSuggestConversationActions(mSessionId, request, callback);
@@ -228,6 +239,7 @@
         printWriter.printPair("mFallback", mFallback);
         printWriter.printPair("mPackageName", mPackageName);
         printWriter.printPair("mSessionId", mSessionId);
+        printWriter.printPair("mUserId", mUserId);
         printWriter.decreaseIndent();
         printWriter.println();
     }
@@ -243,6 +255,7 @@
             @NonNull TextClassificationSessionId sessionId) {
         mSessionId = Preconditions.checkNotNull(sessionId);
         try {
+            classificationContext.setUserId(mUserId);
             mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Error starting a new classification session.", e);
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 6321051..93f7103 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -21,6 +21,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
@@ -35,6 +36,7 @@
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.SpannedString;
 import android.util.ArrayMap;
 import android.view.View.OnClickListener;
@@ -551,6 +553,8 @@
         @Nullable private final ZonedDateTime mReferenceTime;
         @NonNull private final Bundle mExtras;
         @Nullable private String mCallingPackageName;
+        @UserIdInt
+        private int mUserId = UserHandle.USER_NULL;
 
         private Request(
                 CharSequence text,
@@ -631,6 +635,25 @@
         }
 
         /**
+         * Sets the id of the user that sent this request.
+         * <p>
+         * Package-private for SystemTextClassifier's use.
+         * @hide
+         */
+        void setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Returns the id of the user that sent this request.
+         * @hide
+         */
+        @UserIdInt
+        public int getUserId() {
+            return mUserId;
+        }
+
+        /**
          * Returns the extended data.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -730,6 +753,7 @@
             dest.writeParcelable(mDefaultLocales, flags);
             dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
             dest.writeString(mCallingPackageName);
+            dest.writeInt(mUserId);
             dest.writeBundle(mExtras);
         }
 
@@ -742,11 +766,13 @@
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
             final String callingPackageName = in.readString();
+            final int userId = in.readInt();
             final Bundle extras = in.readBundle();
 
             final Request request = new Request(text, startIndex, endIndex,
                     defaultLocales, referenceTime, extras);
             request.setCallingPackageName(callingPackageName);
+            request.setUserId(userId);
             return request;
         }
 
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index 3bf8e9b..e4baaac 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -18,8 +18,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.view.textclassifier.TextClassifier.WidgetType;
 
 import com.android.internal.util.Preconditions;
@@ -35,6 +37,8 @@
     private final String mPackageName;
     private final String mWidgetType;
     @Nullable private final String mWidgetVersion;
+    @UserIdInt
+    private int mUserId = UserHandle.USER_NULL;
 
     private TextClassificationContext(
             String packageName,
@@ -54,6 +58,25 @@
     }
 
     /**
+     * Sets the id of this context's user.
+     * <p>
+     * Package-private for SystemTextClassifier's use.
+     * @hide
+     */
+    void setUserId(@UserIdInt int userId) {
+        mUserId = userId;
+    }
+
+    /**
+     * Returns the id of this context's user.
+     * @hide
+     */
+    @UserIdInt
+    public int getUserId() {
+        return mUserId;
+    }
+
+    /**
      * Returns the widget type for this classification context.
      */
     @NonNull
@@ -75,8 +98,8 @@
     @Override
     public String toString() {
         return String.format(Locale.US, "TextClassificationContext{"
-                + "packageName=%s, widgetType=%s, widgetVersion=%s}",
-                mPackageName, mWidgetType, mWidgetVersion);
+                + "packageName=%s, widgetType=%s, widgetVersion=%s, userId=%d}",
+                mPackageName, mWidgetType, mWidgetVersion, mUserId);
     }
 
     /**
@@ -133,12 +156,14 @@
         parcel.writeString(mPackageName);
         parcel.writeString(mWidgetType);
         parcel.writeString(mWidgetVersion);
+        parcel.writeInt(mUserId);
     }
 
     private TextClassificationContext(Parcel in) {
         mPackageName = in.readString();
         mWidgetType = in.readString();
         mWidgetVersion = in.readString();
+        mUserId = in.readInt();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR =
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
index 6c75ffb..cc9109e 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -20,10 +20,12 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.icu.util.ULocale;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +228,8 @@
         private final CharSequence mText;
         private final Bundle mExtra;
         @Nullable private String mCallingPackageName;
+        @UserIdInt
+        private int mUserId = UserHandle.USER_NULL;
 
         private Request(CharSequence text, Bundle bundle) {
             mText = text;
@@ -260,6 +264,25 @@
         }
 
         /**
+         * Sets the id of the user that sent this request.
+         * <p>
+         * Package-private for SystemTextClassifier's use.
+         * @hide
+         */
+        void setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Returns the id of the user that sent this request.
+         * @hide
+         */
+        @UserIdInt
+        public int getUserId() {
+            return mUserId;
+        }
+
+        /**
          * Returns a bundle containing non-structured extra information about this request.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -278,16 +301,19 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeCharSequence(mText);
             dest.writeString(mCallingPackageName);
+            dest.writeInt(mUserId);
             dest.writeBundle(mExtra);
         }
 
         private static Request readFromParcel(Parcel in) {
             final CharSequence text = in.readCharSequence();
             final String callingPackageName = in.readString();
+            final int userId = in.readInt();
             final Bundle extra = in.readBundle();
 
             final Request request = new Request(text, extra);
             request.setCallingPackageName(callingPackageName);
+            request.setUserId(userId);
             return request;
         }
 
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index f3e0dc1..bbb7f07 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -20,11 +20,13 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.Spannable;
 import android.text.method.MovementMethod;
 import android.text.style.ClickableSpan;
@@ -339,6 +341,8 @@
         private final boolean mLegacyFallback;
         @Nullable private String mCallingPackageName;
         private final Bundle mExtras;
+        @UserIdInt
+        private int mUserId = UserHandle.USER_NULL;
 
         private Request(
                 CharSequence text,
@@ -410,6 +414,25 @@
         }
 
         /**
+         * Sets the id of the user that sent this request.
+         * <p>
+         * Package-private for SystemTextClassifier's use.
+         * @hide
+         */
+        void setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Returns the id of the user that sent this request.
+         * @hide
+         */
+        @UserIdInt
+        public int getUserId() {
+            return mUserId;
+        }
+
+        /**
          * Returns the extended data.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -509,6 +532,7 @@
             dest.writeParcelable(mDefaultLocales, flags);
             dest.writeParcelable(mEntityConfig, flags);
             dest.writeString(mCallingPackageName);
+            dest.writeInt(mUserId);
             dest.writeBundle(mExtras);
         }
 
@@ -517,11 +541,13 @@
             final LocaleList defaultLocales = in.readParcelable(null);
             final EntityConfig entityConfig = in.readParcelable(null);
             final String callingPackageName = in.readString();
+            final int userId = in.readInt();
             final Bundle extras = in.readBundle();
 
             final Request request = new Request(text, defaultLocales, entityConfig,
                     /* legacyFallback= */ true, extras);
             request.setCallingPackageName(callingPackageName);
+            request.setUserId(userId);
             return request;
         }
 
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 75c27bd..0c89749 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -20,10 +20,12 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.SpannedString;
 import android.util.ArrayMap;
 import android.view.textclassifier.TextClassifier.EntityType;
@@ -211,6 +213,8 @@
         private final boolean mDarkLaunchAllowed;
         private final Bundle mExtras;
         @Nullable private String mCallingPackageName;
+        @UserIdInt
+        private int mUserId = UserHandle.USER_NULL;
 
         private Request(
                 CharSequence text,
@@ -292,6 +296,25 @@
         }
 
         /**
+         * Sets the id of the user that sent this request.
+         * <p>
+         * Package-private for SystemTextClassifier's use.
+         * @hide
+         */
+        void setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+        }
+
+        /**
+         * Returns the id of the user that sent this request.
+         * @hide
+         */
+        @UserIdInt
+        public int getUserId() {
+            return mUserId;
+        }
+
+        /**
          * Returns the extended data.
          *
          * <p><b>NOTE: </b>Do not modify this bundle.
@@ -394,6 +417,7 @@
             dest.writeInt(mEndIndex);
             dest.writeParcelable(mDefaultLocales, flags);
             dest.writeString(mCallingPackageName);
+            dest.writeInt(mUserId);
             dest.writeBundle(mExtras);
         }
 
@@ -403,11 +427,13 @@
             final int endIndex = in.readInt();
             final LocaleList defaultLocales = in.readParcelable(null);
             final String callingPackageName = in.readString();
+            final int userId = in.readInt();
             final Bundle extras = in.readBundle();
 
             final Request request = new Request(text, startIndex, endIndex, defaultLocales,
                     /* darkLaunchAllowed= */ false, extras);
             request.setCallingPackageName(callingPackageName);
+            request.setUserId(userId);
             return request;
         }
 
diff --git a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index b530ddf..157c435 100644
--- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -27,6 +27,7 @@
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextSelection;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.Preconditions;
@@ -100,7 +101,7 @@
     private boolean mSmartSelectionTriggered;
     private String mModelName;
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(trackingBug = 136637107)
     public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
         mWidgetType = widgetType;
         mWidgetVersion = null;
@@ -119,7 +120,7 @@
      *
      * @param event the selection event
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(trackingBug = 136637107)
     public void logEvent(@NonNull SelectionEvent event) {
         Preconditions.checkNotNull(event);
 
@@ -443,7 +444,7 @@
          *
          * @param start  the word index of the selected word
          */
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(trackingBug = 136637107)
         public static SelectionEvent selectionStarted(int start) {
             return new SelectionEvent(
                     start, start + 1, EventType.SELECTION_STARTED,
@@ -457,7 +458,7 @@
          * @param start  the start word (inclusive) index of the selection
          * @param end  the end word (exclusive) index of the selection
          */
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(trackingBug = 136637107)
         public static SelectionEvent selectionModified(int start, int end) {
             return new SelectionEvent(
                     start, end, EventType.SELECTION_MODIFIED,
@@ -473,7 +474,7 @@
          * @param classification  the TextClassification object returned by the TextClassifier that
          *      classified the selected text
          */
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(trackingBug = 136637107)
         public static SelectionEvent selectionModified(
                 int start, int end, @NonNull TextClassification classification) {
             final String entityType = classification.getEntityCount() > 0
@@ -493,7 +494,7 @@
          * @param selection  the TextSelection object returned by the TextClassifier for the
          *      specified selection
          */
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(trackingBug = 136637107)
         public static SelectionEvent selectionModified(
                 int start, int end, @NonNull TextSelection selection) {
             final boolean smartSelection = getSourceClassifier(selection.getId())
@@ -522,7 +523,7 @@
          * @param end  the end word (exclusive) index of the selection
          * @param actionType  the action that was performed on the selection
          */
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(trackingBug = 136637107)
         public static SelectionEvent selectionAction(
                 int start, int end, @ActionType int actionType) {
             return new SelectionEvent(
@@ -540,7 +541,7 @@
          * @param classification  the TextClassification object returned by the TextClassifier that
          *      classified the selected text
          */
-        @UnsupportedAppUsage
+        @UnsupportedAppUsage(trackingBug = 136637107)
         public static SelectionEvent selectionAction(
                 int start, int end, @ActionType int actionType,
                 @NonNull TextClassification classification) {
@@ -551,10 +552,11 @@
             return new SelectionEvent(start, end, actionType, entityType, versionTag);
         }
 
-        private static String getVersionInfo(String signature) {
-            final int start = signature.indexOf("|");
+        @VisibleForTesting
+        public static String getVersionInfo(String signature) {
+            final int start = signature.indexOf("|") + 1;
             final int end = signature.indexOf("|", start);
-            if (start >= 0 && end >= start) {
+            if (start >= 1 && end >= start) {
                 return signature.substring(start, end);
             }
             return "";
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 339947a..67a70b4 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -26,12 +26,14 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.text.format.DateUtils;
-import android.text.format.Time;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.RemoteViews.RemoteView;
 
-import java.util.TimeZone;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 
 /**
  * This widget display an analogic clock with two hands for hours and
@@ -45,7 +47,7 @@
 @RemoteView
 @Deprecated
 public class AnalogClock extends View {
-    private Time mCalendar;
+    private Clock mClock;
 
     @UnsupportedAppUsage
     private Drawable mHourHand;
@@ -99,7 +101,7 @@
             mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
         }
 
-        mCalendar = new Time();
+        mClock = Clock.systemDefaultZone();
 
         mDialWidth = mDial.getIntrinsicWidth();
         mDialHeight = mDial.getIntrinsicHeight();
@@ -132,7 +134,7 @@
         // in the main thread, therefore the receiver can't run before this method returns.
 
         // The time zone may have changed while the receiver wasn't registered, so update the Time
-        mCalendar = new Time();
+        mClock = Clock.systemDefaultZone();
 
         // Make sure we update to the current time
         onTimeChanged();
@@ -241,17 +243,18 @@
     }
 
     private void onTimeChanged() {
-        mCalendar.setToNow();
+        long nowMillis = mClock.millis();
+        LocalDateTime localDateTime = toLocalDateTime(nowMillis, mClock.getZone());
 
-        int hour = mCalendar.hour;
-        int minute = mCalendar.minute;
-        int second = mCalendar.second;
+        int hour = localDateTime.getHour();
+        int minute = localDateTime.getMinute();
+        int second = localDateTime.getSecond();
 
         mMinutes = minute + second / 60.0f;
         mHour = hour + mMinutes / 60.0f;
         mChanged = true;
 
-        updateContentDescription(mCalendar);
+        updateContentDescription(nowMillis);
     }
 
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -259,7 +262,7 @@
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
                 String tz = intent.getStringExtra("time-zone");
-                mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
+                mClock = Clock.system(ZoneId.of(tz));
             }
 
             onTimeChanged();
@@ -268,10 +271,17 @@
         }
     };
 
-    private void updateContentDescription(Time time) {
+    private void updateContentDescription(long timeMillis) {
         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
-        String contentDescription = DateUtils.formatDateTime(mContext,
-                time.toMillis(false), flags);
+        String contentDescription = DateUtils.formatDateTime(mContext, timeMillis, flags);
         setContentDescription(contentDescription);
     }
+
+    private static LocalDateTime toLocalDateTime(long timeMillis, ZoneId zoneId) {
+        // java.time types like LocalDateTime / Instant can support the full range of "long millis"
+        // with room to spare so we do not need to worry about overflow / underflow and the
+        // resulting exceptions while the input to this class is a long.
+        Instant instant = Instant.ofEpochMilli(timeMillis);
+        return LocalDateTime.ofInstant(instant, zoneId);
+    }
 }
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index 0469dbd..2864ad0 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -20,7 +20,6 @@
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.YEAR_IN_MILLIS;
-import static android.text.format.Time.getJulianDay;
 
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
@@ -32,7 +31,6 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.os.Handler;
-import android.text.format.Time;
 import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.inspector.InspectableProperty;
@@ -41,10 +39,14 @@
 import com.android.internal.R;
 
 import java.text.DateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.temporal.JulianFields;
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.Date;
-import java.util.TimeZone;
 
 //
 // TODO
@@ -63,8 +65,9 @@
     private static final int SHOW_TIME = 0;
     private static final int SHOW_MONTH_DAY_YEAR = 1;
 
-    Date mTime;
-    long mTimeMillis;
+    private long mTimeMillis;
+    // The LocalDateTime equivalent of mTimeMillis but truncated to minute, i.e. no seconds / nanos.
+    private LocalDateTime mLocalTime;
 
     int mLastDisplay = -1;
     DateFormat mLastFormat;
@@ -128,11 +131,10 @@
 
     @android.view.RemotableViewMethod
     @UnsupportedAppUsage
-    public void setTime(long time) {
-        Time t = new Time();
-        t.set(time);
-        mTimeMillis = t.toMillis(false);
-        mTime = new Date(t.year-1900, t.month, t.monthDay, t.hour, t.minute, 0);
+    public void setTime(long timeMillis) {
+        mTimeMillis = timeMillis;
+        LocalDateTime dateTime = toLocalDateTime(timeMillis, ZoneId.systemDefault());
+        mLocalTime = dateTime.withSecond(0);
         update();
     }
 
@@ -165,7 +167,7 @@
 
     @UnsupportedAppUsage
     void update() {
-        if (mTime == null || getVisibility() == GONE) {
+        if (mLocalTime == null || getVisibility() == GONE) {
             return;
         }
         if (mShowRelativeTime) {
@@ -174,31 +176,27 @@
         }
 
         int display;
-        Date time = mTime;
+        ZoneId zoneId = ZoneId.systemDefault();
 
-        Time t = new Time();
-        t.set(mTimeMillis);
-        t.second = 0;
+        // localTime is the local time for mTimeMillis but at zero seconds past the minute.
+        LocalDateTime localTime = mLocalTime;
+        LocalDateTime localStartOfDay =
+                LocalDateTime.of(localTime.toLocalDate(), LocalTime.MIDNIGHT);
+        LocalDateTime localTomorrowStartOfDay = localStartOfDay.plusDays(1);
+        // now is current local time but at zero seconds past the minute.
+        LocalDateTime localNow = LocalDateTime.now(zoneId).withSecond(0);
 
-        t.hour -= 12;
-        long twelveHoursBefore = t.toMillis(false);
-        t.hour += 12;
-        long twelveHoursAfter = t.toMillis(false);
-        t.hour = 0;
-        t.minute = 0;
-        long midnightBefore = t.toMillis(false);
-        t.monthDay++;
-        long midnightAfter = t.toMillis(false);
-
-        long nowMillis = System.currentTimeMillis();
-        t.set(nowMillis);
-        t.second = 0;
-        nowMillis = t.normalize(false);
+        long twelveHoursBefore = toEpochMillis(localTime.minusHours(12), zoneId);
+        long twelveHoursAfter = toEpochMillis(localTime.plusHours(12), zoneId);
+        long midnightBefore = toEpochMillis(localStartOfDay, zoneId);
+        long midnightAfter = toEpochMillis(localTomorrowStartOfDay, zoneId);
+        long time = toEpochMillis(localTime, zoneId);
+        long now = toEpochMillis(localNow, zoneId);
 
         // Choose the display mode
         choose_display: {
-            if ((nowMillis >= midnightBefore && nowMillis < midnightAfter)
-                    || (nowMillis >= twelveHoursBefore && nowMillis < twelveHoursAfter)) {
+            if ((now >= midnightBefore && now < midnightAfter)
+                    || (now >= twelveHoursBefore && now < twelveHoursAfter)) {
                 display = SHOW_TIME;
                 break choose_display;
             }
@@ -227,7 +225,7 @@
         }
 
         // Set the text
-        String text = format.format(mTime);
+        String text = format.format(new Date(time));
         setText(text);
 
         // Schedule the next update
@@ -236,7 +234,7 @@
             mUpdateTimeMillis = twelveHoursAfter > midnightAfter ? twelveHoursAfter : midnightAfter;
         } else {
             // Currently showing the date
-            if (mTimeMillis < nowMillis) {
+            if (mTimeMillis < now) {
                 // If the time is in the past, don't schedule an update
                 mUpdateTimeMillis = 0;
             } else {
@@ -277,15 +275,18 @@
             millisIncrease = HOUR_IN_MILLIS;
         } else if (duration < YEAR_IN_MILLIS) {
             // In weird cases it can become 0 because of daylight savings
-            TimeZone timeZone = TimeZone.getDefault();
-            count = Math.max(Math.abs(dayDistance(timeZone, mTimeMillis, now)), 1);
+            LocalDateTime localDateTime = mLocalTime;
+            ZoneId zoneId = ZoneId.systemDefault();
+            LocalDateTime localNow = toLocalDateTime(now, zoneId);
+
+            count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
             result = String.format(getContext().getResources().getQuantityString(past
                             ? com.android.internal.R.plurals.duration_days_shortest
                             : com.android.internal.R.plurals.duration_days_shortest_future,
                             count),
                     count);
             if (past || count != 1) {
-                mUpdateTimeMillis = computeNextMidnight(timeZone);
+                mUpdateTimeMillis = computeNextMidnight(localNow, zoneId);
                 millisIncrease = -1;
             } else {
                 millisIncrease = DAY_IN_MILLIS;
@@ -311,18 +312,13 @@
     }
 
     /**
-     * @param timeZone the timezone we are in
-     * @return the timepoint in millis at UTC at midnight in the current timezone
+     * Returns the epoch millis for the next midnight in the specified timezone.
      */
-    private long computeNextMidnight(TimeZone timeZone) {
-        Calendar c = Calendar.getInstance();
-        c.setTimeZone(timeZone);
-        c.add(Calendar.DAY_OF_MONTH, 1);
-        c.set(Calendar.HOUR_OF_DAY, 0);
-        c.set(Calendar.MINUTE, 0);
-        c.set(Calendar.SECOND, 0);
-        c.set(Calendar.MILLISECOND, 0);
-        return c.getTimeInMillis();
+    private static long computeNextMidnight(LocalDateTime time, ZoneId zoneId) {
+        // This ignores the chance of overflow: it should never happen.
+        LocalDate tomorrow = time.toLocalDate().plusDays(1);
+        LocalDateTime nextMidnight = LocalDateTime.of(tomorrow, LocalTime.MIDNIGHT);
+        return toEpochMillis(nextMidnight, zoneId);
     }
 
     @Override
@@ -340,11 +336,10 @@
                 com.android.internal.R.string.now_string_shortest);
     }
 
-    // Return the date difference for the two times in a given timezone.
-    private static int dayDistance(TimeZone timeZone, long startTime,
-            long endTime) {
-        return getJulianDay(endTime, timeZone.getOffset(endTime) / 1000)
-                - getJulianDay(startTime, timeZone.getOffset(startTime) / 1000);
+    // Return the number of days between the two dates.
+    private static int dayDistance(LocalDateTime start, LocalDateTime end) {
+        return (int) (end.getLong(JulianFields.JULIAN_DAY)
+                - start.getLong(JulianFields.JULIAN_DAY));
     }
 
     private DateFormat getTimeFormat() {
@@ -389,8 +384,11 @@
                         count);
             } else if (duration < YEAR_IN_MILLIS) {
                 // In weird cases it can become 0 because of daylight savings
-                TimeZone timeZone = TimeZone.getDefault();
-                count = Math.max(Math.abs(dayDistance(timeZone, mTimeMillis, now)), 1);
+                LocalDateTime localDateTime = mLocalTime;
+                ZoneId zoneId = ZoneId.systemDefault();
+                LocalDateTime localNow = toLocalDateTime(now, zoneId);
+
+                count = Math.max(Math.abs(dayDistance(localDateTime, localNow)), 1);
                 result = String.format(getContext().getResources().getQuantityString(past
                                 ? com.android.internal.
                                         R.plurals.duration_days_relative
@@ -526,4 +524,17 @@
             }
         }
     }
+
+    private static LocalDateTime toLocalDateTime(long timeMillis, ZoneId zoneId) {
+        // java.time types like LocalDateTime / Instant can support the full range of "long millis"
+        // with room to spare so we do not need to worry about overflow / underflow and the rsulting
+        // exceptions while the input to this class is a long.
+        Instant instant = Instant.ofEpochMilli(timeMillis);
+        return LocalDateTime.ofInstant(instant, zoneId);
+    }
+
+    private static long toEpochMillis(LocalDateTime time, ZoneId zoneId) {
+        Instant instant = time.toInstant(zoneId.getRules().getOffset(time));
+        return instant.toEpochMilli();
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 95cf9a9..a4844ea 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -11283,6 +11283,12 @@
     }
 
     @Nullable
+    final TextClassificationManager getTextClassificationManagerForUser() {
+        return getServiceManagerForUser(
+                getContext().getPackageName(), TextClassificationManager.class);
+    }
+
+    @Nullable
     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
         if (mTextOperationUser == null) {
             return getContext().getSystemService(managerClazz);
@@ -12397,8 +12403,7 @@
     @NonNull
     public TextClassifier getTextClassifier() {
         if (mTextClassifier == null) {
-            final TextClassificationManager tcm =
-                    mContext.getSystemService(TextClassificationManager.class);
+            final TextClassificationManager tcm = getTextClassificationManagerForUser();
             if (tcm != null) {
                 return tcm.getTextClassifier();
             }
@@ -12414,8 +12419,7 @@
     @NonNull
     TextClassifier getTextClassificationSession() {
         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
-            final TextClassificationManager tcm =
-                    mContext.getSystemService(TextClassificationManager.class);
+            final TextClassificationManager tcm = getTextClassificationManagerForUser();
             if (tcm != null) {
                 final String widgetType;
                 if (isTextEditable()) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 4573084..88d0e85 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -3478,10 +3478,8 @@
         if (deltaTimeToken < DELTA_TIME_ABS) {
             cur.time += deltaTimeToken;
         } else if (deltaTimeToken == DELTA_TIME_ABS) {
-            cur.time = src.readLong();
-            cur.numReadInts += 2;
-            if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time);
             cur.readFromParcel(src);
+            if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time);
             return;
         } else if (deltaTimeToken == DELTA_TIME_INT) {
             int delta = src.readInt();
@@ -13459,9 +13457,8 @@
             return;
         }
         mHistory = mHistoryEnd = mHistoryCache = null;
-        long time;
-        while (in.dataAvail() > 0 && (time=in.readLong()) >= 0) {
-            HistoryItem rec = new HistoryItem(time, in);
+        while (in.dataAvail() > 0) {
+            HistoryItem rec = new HistoryItem(in);
             addHistoryRecordLocked(rec);
         }
     }
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index 5b6b619..1afc67b 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -24,8 +24,6 @@
 import android.graphics.Rect;
 import android.util.Size;
 import android.view.Gravity;
-import android.view.ViewConfiguration;
-import android.widget.Scroller;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -180,14 +178,6 @@
         // If we're not flinging along the current edge, find the closest point instead.
         final double distanceVert = Math.hypot(vertPoint.x - x, vertPoint.y - y);
         final double distanceHoriz = Math.hypot(horizPoint.x - x, horizPoint.y - y);
-        // Ensure that we're actually going somewhere
-        if (distanceVert == 0) {
-            return horizPoint;
-        }
-        if (distanceHoriz == 0) {
-            return vertPoint;
-        }
-        // Otherwise use the closest point
         return Math.abs(distanceVert) > Math.abs(distanceHoriz) ? horizPoint : vertPoint;
     }
 
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 14dbabb..c3e7a36 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -575,7 +575,7 @@
     if (outArray != NULL) {
         outLen = MEMINFO_COUNT;
         for (int i = 0; i < outLen; i++) {
-            if (i == MEMINFO_VMALLOC_USED) {
+            if (i == MEMINFO_VMALLOC_USED && mem[i] == 0) {
                 outArray[i] = smi.ReadVmallocInfo() / 1024;
                 continue;
             }
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 2542286..f90d1cf 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -141,6 +141,7 @@
                 event->getClassification(),
                 event->getXOffset(), event->getYOffset(),
                 event->getXPrecision(), event->getYPrecision(),
+                event->getRawXCursorPosition(), event->getRawYCursorPosition(),
                 event->getDownTime(), event->getHistoricalEventTime(i),
                 event->getPointerCount(), event->getPointerProperties(),
                 event->getHistoricalRawPointerCoords(0, i));
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 50cff5c..8ddbe72 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -375,6 +375,7 @@
     event->initialize(deviceId, source, displayId, action, 0, flags, edgeFlags, metaState,
             buttonState, static_cast<MotionClassification>(classification),
             xOffset, yOffset, xPrecision, yPrecision,
+            AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
             downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords);
 
     return reinterpret_cast<jlong>(event);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dea8092..f25f93f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -724,6 +724,10 @@
 
     <!-- Allows an application to send SMS messages.
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.SEND_SMS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -734,6 +738,10 @@
 
     <!-- Allows an application to receive SMS messages.
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.RECEIVE_SMS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -744,6 +752,10 @@
 
     <!-- Allows an application to read SMS messages.
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.READ_SMS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -754,6 +766,10 @@
 
     <!-- Allows an application to receive WAP push messages.
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.RECEIVE_WAP_PUSH"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -763,7 +779,11 @@
         android:protectionLevel="dangerous" />
 
     <!-- Allows an application to monitor incoming MMS messages.
-        <p>Protection level: dangerous
+         <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.RECEIVE_MMS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -783,6 +803,11 @@
          when the alert is first received, and to delay presenting the info
          to the user until after the initial alert dialog is dismissed.
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+
          @hide Pending API council approval -->
     <permission android:name="android.permission.READ_CELL_BROADCASTS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -805,30 +830,35 @@
         android:priority="900" />
 
     <!-- Allows an application to read from external storage.
-     <p>Any app that declares the {@link #WRITE_EXTERNAL_STORAGE} permission is implicitly
-     granted this permission.</p>
-     <p>This permission is enforced starting in API level 19.  Before API level 19, this
-     permission is not enforced and all apps still have access to read from external storage.
-     You can test your app with the permission enforced by enabling <em>Protect USB
-     storage</em> under Developer options in the Settings app on a device running Android 4.1 or
-     higher.</p>
-     <p>Also starting in API level 19, this permission is <em>not</em> required to
-     read/write files in your application-specific directories returned by
-     {@link android.content.Context#getExternalFilesDir} and
-     {@link android.content.Context#getExternalCacheDir}.
-     <p class="note"><strong>Note:</strong> If <em>both</em> your <a
-     href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
-     minSdkVersion}</a> and <a
-     href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-     targetSdkVersion}</a> values are set to 3 or lower, the system implicitly
-     grants your app this permission. If you don't need this permission, be sure your <a
-     href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
-     targetSdkVersion}</a> is 4 or higher.
-     <p>Is this permission is not whitelisted for an app that targets an API level before
-     {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
-     <p>Is this permission is not whitelisted for an app that targets an API level
-     {@link android.os.Build.VERSION_CODES#Q} or later the app will be forced into isolated storage.
-     </p>
+      <p>Any app that declares the {@link #WRITE_EXTERNAL_STORAGE} permission is implicitly
+      granted this permission.</p>
+      <p>This permission is enforced starting in API level 19.  Before API level 19, this
+      permission is not enforced and all apps still have access to read from external storage.
+      You can test your app with the permission enforced by enabling <em>Protect USB
+      storage</em> under Developer options in the Settings app on a device running Android 4.1 or
+      higher.</p>
+      <p>Also starting in API level 19, this permission is <em>not</em> required to
+      read/write files in your application-specific directories returned by
+      {@link android.content.Context#getExternalFilesDir} and
+      {@link android.content.Context#getExternalCacheDir}.
+      <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+      href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
+      minSdkVersion}</a> and <a
+      href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+      targetSdkVersion}</a> values are set to 3 or lower, the system implicitly
+      grants your app this permission. If you don't need this permission, be sure your <a
+      href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+      targetSdkVersion}</a> is 4 or higher.
+
+      <p> This is a soft restricted permission which cannot be held by an app it its
+      full form until the installer on record did not whitelist the permission.
+      Specifically, if the permission is whitelisted the holder app can access
+      external storage and the visual and aural media collections while if the
+      permission is not whitelisted the holder app can only access to the visual
+      and aural medial collections. Also the permission is immutably restricted
+      meaning that the whitelist state can be specified only at install time and
+      cannot change until the app is installed. For more details see
+      {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
      -->
     <permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -916,6 +946,10 @@
          {@link #ACCESS_FINE_LOCATION}. Requesting this permission by itself doesn't give you
          location access.
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -958,6 +992,10 @@
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
          targetSdkVersion}</a> is 16 or higher.</p>
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.READ_CALL_LOG"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -978,6 +1016,10 @@
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
          targetSdkVersion}</a> is 16 or higher.</p>
          <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
     -->
     <permission android:name="android.permission.WRITE_CALL_LOG"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -991,6 +1033,10 @@
          abort the call altogether.
          <p>Protection level: dangerous
 
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record did not whitelist the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+
          @deprecated Applications should use {@link android.telecom.CallRedirectionService} instead
          of the {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL} broadcast.
     -->
diff --git a/core/res/res/anim/lock_screen_behind_enter_subtle.xml b/core/res/res/anim/lock_screen_behind_enter_subtle.xml
index 23b26b7..f9f69b1 100644
--- a/core/res/res/anim/lock_screen_behind_enter_subtle.xml
+++ b/core/res/res/anim/lock_screen_behind_enter_subtle.xml
@@ -23,9 +23,11 @@
         android:fromAlpha="0.0" android:toAlpha="1.0"
         android:fillEnabled="true" android:fillBefore="true"
         android:interpolator="@interpolator/linear"
+        android:startOffset="80"
         android:duration="233"/>
     <translate android:fromYDelta="5%p" android:toYDelta="0"
             android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
             android:interpolator="@interpolator/fast_out_slow_in"
+            android:startOffset="80"
             android:duration="233" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f515b2d..bc7e3b7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4210,6 +4210,14 @@
          one bar higher than they actually are -->
     <bool name="config_inflateSignalStrength">false</bool>
 
+    <!-- Trigger a warning for notifications with RemoteView objects that are larger in bytes than
+    this value (default 1MB)-->
+    <integer name="config_notificationWarnRemoteViewSizeBytes">1000000</integer>
+
+    <!-- Strip notification RemoteView objects that are larger in bytes than this value (also log)
+    (default 2MB) -->
+    <integer name="config_notificationStripRemoteViewSizeBytes">2000000</integer>
+
     <!-- Sharesheet: define a max number of targets per application for new shortcuts-based direct share introduced in Q -->
     <integer name="config_maxShortcutTargetsPerApp">3</integer>
 
@@ -4217,4 +4225,8 @@
     the default implementation of ACTION_FACTORY_RESET does not work, so it is needed to re-route
     this intent to this package. This is being used in MasterClearReceiver.java. -->
     <string name="config_factoryResetPackage" translatable="false"></string>
+
+    <!-- The list of packages to automatically opt out of refresh rates higher than 60hz because
+         of known compatibility issues. -->
+    <string-array name="config_highRefreshRateBlacklist"></string-array>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6c908aa..622e043 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3823,5 +3823,10 @@
 
   <java-symbol type="string" name="config_defaultSupervisionProfileOwnerComponent" />
   <java-symbol type="bool" name="config_inflateSignalStrength" />
+
+  <java-symbol type="integer" name="config_notificationWarnRemoteViewSizeBytes" />
+  <java-symbol type="integer" name="config_notificationStripRemoteViewSizeBytes" />
+
   <java-symbol type="string" name="config_factoryResetPackage" />
+  <java-symbol type="array" name="config_highRefreshRateBlacklist" />
 </resources>
diff --git a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
index 1ca879c..49849ee 100644
--- a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
+++ b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
@@ -36,12 +36,12 @@
 
 import com.android.frameworks.coretests.R;
 
-import libcore.testing.io.TestIoUtils;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 
 import java.io.File;
@@ -60,21 +60,14 @@
     private static final String APK_FILE_EXTENSION = ".apk";
     private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
 
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
     private File mTmpDir = null;
 
     @Before
-    public void setUp() {
-        mTmpDir = TestIoUtils.createTemporaryDirectory("DexMetadataHelperTest");
-    }
-
-    @After
-    public void tearDown() {
-        if (mTmpDir != null) {
-            File[] files = mTmpDir.listFiles();
-            for (File f : files) {
-                f.delete();
-            }
-        }
+    public void setUp() throws IOException {
+        mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
     }
 
     private File createDexMetadataFile(String apkFileName) throws IOException {
diff --git a/core/tests/coretests/src/android/view/textclassifier/logging/SmartSelectionEventTrackerTest.java b/core/tests/coretests/src/android/view/textclassifier/logging/SmartSelectionEventTrackerTest.java
new file mode 100644
index 0000000..321a7f2
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/logging/SmartSelectionEventTrackerTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.view.textclassifier.logging;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.truth.Truth;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SmartSelectionEventTrackerTest {
+
+    @Test
+    public void getVersionInfo_valid() {
+        String signature = "a|702|b";
+        String versionInfo = SmartSelectionEventTracker.SelectionEvent.getVersionInfo(signature);
+        Truth.assertThat(versionInfo).isEqualTo("702");
+    }
+
+    @Test
+    public void getVersionInfo_invalid() {
+        String signature = "|702";
+        String versionInfo = SmartSelectionEventTracker.SelectionEvent.getVersionInfo(signature);
+        Truth.assertThat(versionInfo).isEmpty();
+    }
+}
diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
index e24f26c1..054f68b 100644
--- a/data/etc/hiddenapi-package-whitelist.xml
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -17,8 +17,12 @@
 
 <!--
 This XML file declares which system apps should be exempted from the hidden API blacklisting, i.e.
-which apps should be allowed to access the entire private API. Only apps NOT signed with the
-platform cert need to be included, as apps signed with the platform cert are exempted by default.
+which apps should be allowed to access the entire private API.
+
+Only apps NOT signed with the platform cert need to be included, as apps signed with the platform
+cert are exempted by default.
+
+Do NOT include any apps that are updatable via Play Store!
 -->
 
 <config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 54c548a..89e26da 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -269,6 +269,8 @@
         <permission name="android.permission.INSTALL_PACKAGES"/>
         <!-- Needed for test only -->
         <permission name="android.permission.INTERACT_ACROSS_PROFILES"/>
+        <!-- Permission required to test onPermissionsChangedListener -->
+        <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 646aa13..ee8cc40 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -584,7 +584,7 @@
         }
         KeyCharacteristics characteristics = result.getKeyCharacteristics();
         if (characteristics == null) {
-            Log.e(TAG, "generateKeyInternal got empty key cheractariestics " + error);
+            Log.e(TAG, "generateKeyInternal got empty key characteristics " + error);
             return SYSTEM_ERROR;
         }
         outCharacteristics.shallowCopyFrom(characteristics);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index ff29a5b..84c0d13 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -19,14 +19,17 @@
 #include <SkImageEncoder.h>
 #include <SkImageInfo.h>
 #include <SkImagePriv.h>
+#include <SkMultiPictureDocument.h>
 #include <SkOverdrawCanvas.h>
 #include <SkOverdrawColorFilter.h>
 #include <SkPicture.h>
 #include <SkPictureRecorder.h>
+#include <SkSerialProcs.h>
 #include "LightingInfo.h"
 #include "TreeInfo.h"
 #include "VectorDrawable.h"
 #include "thread/CommonPool.h"
+#include "tools/SkSharingProc.h"
 #include "utils/TraceUtils.h"
 
 #include <unistd.h>
@@ -99,7 +102,7 @@
             SkASSERT(layerNode->getLayerSurface());
             SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList();
             if (!displayList || displayList->isEmpty()) {
-                SkDEBUGF(("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()));
+                ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
                 return;
             }
 
@@ -233,58 +236,138 @@
         if (stream.isValid()) {
             stream.write(data->data(), data->size());
             stream.flush();
-            SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(),
+            ALOGD("SKP Captured Drawing Output (%zu bytes) for frame. %s", stream.bytesWritten(),
                      filename.c_str());
         }
     });
 }
 
-SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
-    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+// Note multiple SkiaPipeline instances may be loaded if more than one app is visible.
+// Each instance may observe the filename changing and try to record to a file of the same name.
+// Only the first one will succeed. There is no scope available here where we could coordinate
+// to cause this function to return true for only one of the instances.
+bool SkiaPipeline::shouldStartNewFileCapture() {
+    // Don't start a new file based capture if one is currently ongoing.
+    if (mCaptureMode != CaptureMode::None) { return false; }
+
+    // A new capture is started when the filename property changes.
+    // Read the filename property.
+    std::string prop = base::GetProperty(PROPERTY_CAPTURE_SKP_FILENAME, "0");
+    // if the filename property changed to a valid value
+    if (prop[0] != '0' && mCapturedFile != prop) {
+        // remember this new filename
+        mCapturedFile = prop;
+        // and get a property indicating how many frames to capture.
+        mCaptureSequence = base::GetIntProperty(PROPERTY_CAPTURE_SKP_FRAMES, 1);
         if (mCaptureSequence <= 0) {
-            std::string prop = base::GetProperty(PROPERTY_CAPTURE_SKP_FILENAME, "0");
-            if (prop[0] != '0' && mCapturedFile != prop) {
-                mCapturedFile = prop;
-                mCaptureSequence = base::GetIntProperty(PROPERTY_CAPTURE_SKP_FRAMES, 1);
-            }
+            return false;
+        } else if (mCaptureSequence == 1) {
+            mCaptureMode = CaptureMode::SingleFrameSKP;
+        } else {
+            mCaptureMode = CaptureMode::MultiFrameSKP;
         }
-        if (mCaptureSequence > 0 || mPictureCapturedCallback) {
-            mRecorder.reset(new SkPictureRecorder());
-            SkCanvas* pictureCanvas =
-                    mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
-                                              SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
-            mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
-            mNwayCanvas->addCanvas(surface->getCanvas());
-            mNwayCanvas->addCanvas(pictureCanvas);
-            return mNwayCanvas.get();
+        return true;
+    }
+    return false;
+}
+
+// performs the first-frame work of a multi frame SKP capture. Returns true if successful.
+bool SkiaPipeline::setupMultiFrameCapture() {
+    ALOGD("Set up multi-frame capture, frames = %d", mCaptureSequence);
+    // We own this stream and need to hold it until close() finishes.
+    auto stream = std::make_unique<SkFILEWStream>(mCapturedFile.c_str());
+    if (stream->isValid()) {
+        mOpenMultiPicStream = std::move(stream);
+        mSerialContext.reset(new SkSharingSerialContext());
+        SkSerialProcs procs;
+        procs.fImageProc = SkSharingSerialContext::serializeImage;
+        procs.fImageCtx = mSerialContext.get();
+        // SkDocuments don't take owership of the streams they write.
+        // we need to keep it until after mMultiPic.close()
+        // procs is passed as a pointer, but just as a method of having an optional default.
+        // procs doesn't need to outlive this Make call.
+        mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs);
+        return true;
+    } else {
+        ALOGE("Could not open \"%s\" for writing.", mCapturedFile.c_str());
+        mCaptureSequence = 0;
+        mCaptureMode = CaptureMode::None;
+        return false;
+    }
+}
+
+SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
+    if (CC_LIKELY(!Properties::skpCaptureEnabled)) {
+        return surface->getCanvas(); // Bail out early when capture is not turned on.
+    }
+    // Note that shouldStartNewFileCapture tells us if this is the *first* frame of a capture.
+    if (shouldStartNewFileCapture() && mCaptureMode == CaptureMode::MultiFrameSKP) {
+        if (!setupMultiFrameCapture()) {
+            return surface->getCanvas();
         }
     }
-    return surface->getCanvas();
+
+    // Create a canvas pointer, fill it depending on what kind of capture is requested (if any)
+    SkCanvas* pictureCanvas = nullptr;
+    switch (mCaptureMode) {
+        case CaptureMode::CallbackAPI:
+        case CaptureMode::SingleFrameSKP:
+            mRecorder.reset(new SkPictureRecorder());
+            pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(),
+                nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+            break;
+        case CaptureMode::MultiFrameSKP:
+            // If a multi frame recording is active, initialize recording for a single frame of a
+            // multi frame file.
+            pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height());
+            break;
+        case CaptureMode::None:
+            // Returning here in the non-capture case means we can count on pictureCanvas being
+            // non-null below.
+            return surface->getCanvas();
+    }
+
+    // Setting up an nway canvas is common to any kind of capture.
+    mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
+    mNwayCanvas->addCanvas(surface->getCanvas());
+    mNwayCanvas->addCanvas(pictureCanvas);
+    return mNwayCanvas.get();
 }
 
 void SkiaPipeline::endCapture(SkSurface* surface) {
+    if (CC_LIKELY(mCaptureMode == CaptureMode::None)) { return; }
     mNwayCanvas.reset();
-    if (CC_UNLIKELY(mRecorder.get())) {
-        ATRACE_CALL();
+    ATRACE_CALL();
+    if (mCaptureSequence > 0 && mCaptureMode == CaptureMode::MultiFrameSKP) {
+        mMultiPic->endPage();
+        mCaptureSequence--;
+        if (mCaptureSequence == 0) {
+            mCaptureMode = CaptureMode::None;
+            // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will handle
+            // the heavyweight serialization work and destroy them. mOpenMultiPicStream is released
+            // to a bare pointer because keeping it in a smart pointer makes the lambda
+            // non-copyable. The lambda is only called once, so this is safe.
+            SkFILEWStream* stream = mOpenMultiPicStream.release();
+            CommonPool::post([doc = std::move(mMultiPic), stream]{
+                ALOGD("Finalizing multi frame SKP");
+                doc->close();
+                delete stream;
+                ALOGD("Multi frame SKP complete.");
+            });
+        }
+    } else {
         sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
         if (picture->approximateOpCount() > 0) {
-            if (mCaptureSequence > 0) {
-                ATRACE_BEGIN("picture->serialize");
-                auto data = picture->serialize();
-                ATRACE_END();
-
-                // offload saving to file in a different thread
-                if (1 == mCaptureSequence) {
-                    savePictureAsync(data, mCapturedFile);
-                } else {
-                    savePictureAsync(data, mCapturedFile + "_" + std::to_string(mCaptureSequence));
-                }
-                mCaptureSequence--;
-            }
             if (mPictureCapturedCallback) {
                 std::invoke(mPictureCapturedCallback, std::move(picture));
+            } else {
+                // single frame skp to file
+                auto data = picture->serialize();
+                savePictureAsync(data, mCapturedFile);
+                mCaptureSequence = 0;
             }
         }
+        mCaptureMode = CaptureMode::None;
         mRecorder.reset();
     }
 }
@@ -305,7 +388,6 @@
 
     // initialize the canvas for the current frame, that might be a recording canvas if SKP
     // capture is enabled.
-    std::unique_ptr<SkPictureRecorder> recorder;
     SkCanvas* canvas = tryCapture(surface.get());
 
     renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 5fc1d61..37b559f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -17,12 +17,15 @@
 #pragma once
 
 #include <SkSurface.h>
+#include <SkDocument.h>
+#include <SkMultiPictureDocument.h>
 #include "Lighting.h"
 #include "hwui/AnimatedImageDrawable.h"
 #include "renderthread/CanvasContext.h"
 #include "renderthread/IRenderPipeline.h"
 
 class SkPictureRecorder;
+struct SkSharingSerialContext;
 
 namespace android {
 namespace uirenderer {
@@ -60,9 +63,12 @@
 
     void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
 
+    // Sets the recording callback to the provided function and the recording mode
+    // to CallbackAPI
     void setPictureCapturedCallback(
             const std::function<void(sk_sp<SkPicture>&&)>& callback) override {
         mPictureCapturedCallback = callback;
+        mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
     }
 
 protected:
@@ -92,8 +98,18 @@
      */
     void renderVectorDrawableCache();
 
+    // Called every frame. Normally returns early with screen canvas.
+    // But when capture is enabled, returns an nwaycanvas where commands are also recorded.
     SkCanvas* tryCapture(SkSurface* surface);
+    // Called at the end of every frame, closes the recording if necessary.
     void endCapture(SkSurface* surface);
+    // Determine if a new file-based capture should be started.
+    // If so, sets mCapturedFile and mCaptureSequence and returns true.
+    // Should be called every frame when capture is enabled.
+    // sets mCaptureMode.
+    bool shouldStartNewFileCapture();
+    // Set up a multi frame capture.
+    bool setupMultiFrameCapture();
 
     std::vector<sk_sp<SkImage>> mPinnedImages;
 
@@ -103,22 +119,46 @@
     std::vector<VectorDrawableRoot*> mVectorDrawables;
 
     // Block of properties used only for debugging to record a SkPicture and save it in a file.
+    // There are three possible ways of recording drawing commands.
+    enum class CaptureMode {
+        // return to this mode when capture stops.
+        None,
+        // A mode where every frame is recorded into an SkPicture and sent to a provided callback,
+        // until that callback is cleared
+        CallbackAPI,
+        // A mode where a finite number of frames are recorded to a file with
+        // SkMultiPictureDocument
+        MultiFrameSKP,
+        // A mode which records a single frame to a normal SKP file.
+        SingleFrameSKP,
+    };
+  CaptureMode mCaptureMode = CaptureMode::None;
+
     /**
-     * mCapturedFile is used to enforce we don't capture more than once for a given name (cause
-     * permissions don't allow to reset a property from render thread).
+     * mCapturedFile - the filename to write a recorded SKP to in either MultiFrameSKP or
+     * SingleFrameSKP mode.
      */
     std::string mCapturedFile;
     /**
-     *  mCaptureSequence counts how many frames are left to take in the sequence.
+     * mCaptureSequence counts down how many frames are left to take in the sequence. Applicable
+     * only to MultiFrameSKP or SingleFrameSKP mode.
      */
     int mCaptureSequence = 0;
 
+    // Multi frame serialization stream and writer used when serializing more than one frame.
+    std::unique_ptr<SkFILEWStream> mOpenMultiPicStream;
+    sk_sp<SkDocument> mMultiPic;
+    std::unique_ptr<SkSharingSerialContext> mSerialContext;
+
     /**
-     *  mRecorder holds the current picture recorder. We could store it on the stack to support
-     *  parallel tryCapture calls (not really needed).
+     * mRecorder holds the current picture recorder when serializing in either SingleFrameSKP or
+     * CallbackAPI modes.
      */
     std::unique_ptr<SkPictureRecorder> mRecorder;
     std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+
+    // Set by setPictureCapturedCallback and when set, CallbackAPI mode recording is ongoing.
+    // Not used in other recording modes.
     std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback;
 };
 
diff --git a/libs/hwui/tests/scripts/skp-capture.sh b/libs/hwui/tests/scripts/skp-capture.sh
index 54fa229..aad31fc 100755
--- a/libs/hwui/tests/scripts/skp-capture.sh
+++ b/libs/hwui/tests/scripts/skp-capture.sh
@@ -4,6 +4,12 @@
 #
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
+#
+# Before this can be used, the device must be rooted and the filesystem must be writable by Skia
+# - These steps are necessary once after flashing to enable capture -
+# adb root
+# adb remount
+# adb reboot
 
 if [ -z "$1" ]; then
     printf 'Usage:\n    skp-capture.sh PACKAGE_NAME OPTIONAL_FRAME_COUNT\n\n'
@@ -20,8 +26,8 @@
         exit 2
     fi
 fi
-phase1_timeout_seconds=15
-phase2_timeout_seconds=60
+phase1_timeout_seconds=60
+phase2_timeout_seconds=300
 package="$1"
 filename="$(date '+%H%M%S').skp"
 remote_path="/data/data/${package}/cache/${filename}"
@@ -29,11 +35,14 @@
 local_path="${local_path_prefix}.skp"
 enable_capture_key='debug.hwui.capture_skp_enabled'
 enable_capture_value=$(adb shell "getprop '${enable_capture_key}'")
-#printf 'captureflag=' "$enable_capture_value" '\n'
+
+# TODO(nifong): check if filesystem is writable here with "avbctl get-verity"
+# result will either start with "verity is disabled" or "verity is enabled"
+
 if [ -z "$enable_capture_value" ]; then
-    printf 'Capture SKP property need to be enabled first. Please use\n'
-    printf "\"adb shell setprop debug.hwui.capture_skp_enabled true\" and then restart\n"
-    printf "the process.\n\n"
+    printf 'debug.hwui.capture_skp_enabled was found to be disabled, enabling it now.\n'
+    printf " restart the process you want to capture on the device, then retry this script.\n\n"
+    adb shell "setprop '${enable_capture_key}' true"
     exit 1
 fi
 if [ ! -z "$2" ]; then
@@ -57,33 +66,17 @@
     printf '   %s' "$*"
     printf '\n=====================\n'
 }
-banner '...WAITING...'
-adb_test_exist() {
-    test '0' = "$(adb shell "test -e \"$1\"; echo \$?")";
-}
-timeout=$(( $(date +%s) + $phase1_timeout_seconds))
-while ! adb_test_exist "$remote_path"; do
-    spin 0.05
-    if [ $(date +%s) -gt $timeout ] ; then
-        printf '\bTimed out.\n'
-        adb shell "setprop '${filename_key}' ''"
-        exit 3
-    fi
-done
-printf '\b'
-
-#read -n1 -r -p "Press any key to continue..." key
-
-banner '...SAVING...'
+banner '...WAITING FOR APP INTERACTION...'
+# Waiting for nonzero file is an indication that the pipeline has both opened the file and written
+# the header. With multiple frames this does not occur until the last frame has been recorded,
+# so we continue to show the "waiting for app interaction" message as long as the app still requires
+# interaction to draw more frames.
 adb_test_file_nonzero() {
     # grab first byte of `du` output
     X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")"
     test "$X" && test "$X" -ne 0
 }
-#adb_filesize() {
-#    adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}'
-#}
-timeout=$(( $(date +%s) + $phase2_timeout_seconds))
+timeout=$(( $(date +%s) + $phase1_timeout_seconds))
 while ! adb_test_file_nonzero "$remote_path"; do
     spin 0.05
     if [ $(date +%s) -gt $timeout ] ; then
@@ -94,8 +87,37 @@
 done
 printf '\b'
 
+# Disable further capturing
 adb shell "setprop '${filename_key}' ''"
 
+banner '...SAVING...'
+# return the size of a file in bytes
+adb_filesize() {
+    adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}'
+}
+timeout=$(( $(date +%s) + $phase2_timeout_seconds))
+last_size='0' # output of last size check command
+unstable=true # false once the file size stops changing
+counter=0 # used to perform size check only 1/sec though we update spinner 20/sec
+# loop until the file size is unchanged for 1 second.
+while [ $unstable != 0 ] ; do
+    spin 0.05
+    counter=$(( $counter+1 ))
+    if ! (( $counter % 20)) ; then
+        new_size=$(adb_filesize "$remote_path")
+        unstable=$(($(adb_filesize "$remote_path") != last_size))
+        last_size=$new_size
+    fi
+    if [ $(date +%s) -gt $timeout ] ; then
+        printf '\bTimed out.\n'
+        adb shell "setprop '${filename_key}' ''"
+        exit 3
+    fi
+done
+printf '\b'
+
+printf "SKP file serialized: %s\n" $(echo $last_size | numfmt --to=iec)
+
 i=0; while [ $i -lt 10 ]; do spin 0.10; i=$(($i + 1)); done; echo
 
 adb pull "$remote_path" "$local_path"
@@ -105,12 +127,4 @@
 fi
 adb shell rm "$remote_path"
 printf '\nSKP saved to file:\n    %s\n\n'  "$local_path"
-if [ ! -z "$2" ]; then
-    bridge="_"
-    adb shell "setprop 'debug.hwui.capture_skp_frames' ''"
-    for i in $(seq 2 $2); do
-        adb pull "${remote_path}_${i}" "${local_path_prefix}_${i}.skp"
-        adb shell rm "${remote_path}_${i}"
-    done
-fi
 
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 813e474..0346010 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -800,7 +800,7 @@
      */
     public static final int METADATA_KEY_YEAR            = 8;
     /**
-     * The metadata key to retrieve the playback duration of the data source.
+     * The metadata key to retrieve the playback duration (in ms) of the data source.
      */
     public static final int METADATA_KEY_DURATION        = 9;
     /**
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index 534d63b..fb581b5 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -356,19 +356,21 @@
                 return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer);
             }
 
-            // Fall back to middle of video
             final int width = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH));
             final int height = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT));
-            final long duration = Long.parseLong(mmr.extractMetadata(METADATA_KEY_DURATION));
+            // Fall back to middle of video
+            // Note: METADATA_KEY_DURATION unit is in ms, not us.
+            final long thumbnailTimeUs =
+                    Long.parseLong(mmr.extractMetadata(METADATA_KEY_DURATION)) * 1000 / 2;
 
             // If we're okay with something larger than native format, just
             // return a frame without up-scaling it
             if (size.getWidth() > width && size.getHeight() > height) {
                 return Objects.requireNonNull(
-                        mmr.getFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC));
+                        mmr.getFrameAtTime(thumbnailTimeUs, OPTION_CLOSEST_SYNC));
             } else {
                 return Objects.requireNonNull(
-                        mmr.getScaledFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC,
+                        mmr.getScaledFrameAtTime(thumbnailTimeUs, OPTION_CLOSEST_SYNC,
                         size.getWidth(), size.getHeight()));
             }
         } catch (RuntimeException e) {
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index 589623b..61cab73 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -26,8 +26,8 @@
     ],
 
     static_libs: [
-        "CarNotificationLib",
         "SystemUI-core",
+        "CarNotificationLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SettingsLib",
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 358e2cd..812adf9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -61,9 +61,9 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.classifier.FalsingLog;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.car.CarQSFragment;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -877,7 +877,7 @@
             KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
         }
 
-        FalsingManagerFactory.getInstance(mContext).dump(pw);
+        Dependency.get(FalsingManager.class).dump(pw);
         FalsingLog.dump(pw);
 
         pw.println("SharedPreferences:");
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index dbc51958..8c208e3 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -61,6 +61,7 @@
             android:id="@android:id/summary"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:textDirection="locale"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"/>
 
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index d879087..a28ba85 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -1,21 +1,13 @@
 # People who can approve changes for submission
-asapperstein@google.com
-asargent@google.com
-dehboxturtle@google.com
-dhnishi@google.com
-dling@google.com
 dsandler@android.com
+edgarwang@google.com
+emilychuang@google.com
 evanlaird@google.com
-jackqdyulei@google.com
-jmonk@google.com
 leifhendrik@google.com
-mfritze@google.com
-rogerxue@google.com
+rafftsai@google.com
+tmfang@google.com
 virgild@google.com
 zhfan@google.com
 
-# Emergency approvers in case the above are not available
-miket@google.com
-
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
 per-file *.xml=*
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 57f5d5a..63463b1 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -408,7 +408,7 @@
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Charging"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"charging"</string>
     <string name="battery_info_status_discharging" msgid="310932812698268588">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge right now"</string>
+    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="2824614753861462808">"Full"</string>
     <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlled by admin"</string>
     <string name="disabled" msgid="9206776641295849915">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 57f5d5a..63463b1 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -408,7 +408,7 @@
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Charging"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"charging"</string>
     <string name="battery_info_status_discharging" msgid="310932812698268588">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge right now"</string>
+    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="2824614753861462808">"Full"</string>
     <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlled by admin"</string>
     <string name="disabled" msgid="9206776641295849915">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 57f5d5a..63463b1 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -408,7 +408,7 @@
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Charging"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"charging"</string>
     <string name="battery_info_status_discharging" msgid="310932812698268588">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge right now"</string>
+    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="2824614753861462808">"Full"</string>
     <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlled by admin"</string>
     <string name="disabled" msgid="9206776641295849915">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 57f5d5a..63463b1 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -408,7 +408,7 @@
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Charging"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"charging"</string>
     <string name="battery_info_status_discharging" msgid="310932812698268588">"Not charging"</string>
-    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge right now"</string>
+    <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Plugged in, can\'t charge at the moment"</string>
     <string name="battery_info_status_full" msgid="2824614753861462808">"Full"</string>
     <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlled by admin"</string>
     <string name="disabled" msgid="9206776641295849915">"Disabled"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 798ea3c..7fe1ef9 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -293,8 +293,8 @@
     <string name="strict_mode_summary" msgid="142834318897332338">"Kedipkan layar saat apl beroperasi lama pada utas utama"</string>
     <string name="pointer_location" msgid="6084434787496938001">"Lokasi penunjuk"</string>
     <string name="pointer_location_summary" msgid="840819275172753713">"Hamparan layar menampilkan data sentuhan saat ini"</string>
-    <string name="show_touches" msgid="2642976305235070316">"Tampilkan tap"</string>
-    <string name="show_touches_summary" msgid="6101183132903926324">"Tampilkan masukan visual untuk tap"</string>
+    <string name="show_touches" msgid="2642976305235070316">"Tampilkan ketukan"</string>
+    <string name="show_touches_summary" msgid="6101183132903926324">"Tampilkan masukan untuk ketukan"</string>
     <string name="show_screen_updates" msgid="5470814345876056420">"Lihat pembaruan permukaan"</string>
     <string name="show_screen_updates_summary" msgid="2569622766672785529">"Sorot seluruh permukaan jendela saat diperbarui"</string>
     <string name="show_hw_screen_updates" msgid="4117270979975470789">"Tampilkan update tampilan"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 4b38383..7a054bb 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -307,7 +307,7 @@
     <string name="simulate_color_space" msgid="6745847141353345872">"Simulo hapësirën e ngjyrës"</string>
     <string name="enable_opengl_traces_title" msgid="6790444011053219871">"Aktivizo gjurmët e OpenGL-së"</string>
     <string name="usb_audio_disable_routing" msgid="8114498436003102671">"Çaktivizo rrugëzuezin e audios përmes USB-së"</string>
-    <string name="usb_audio_disable_routing_summary" msgid="980282760277312264">"Çaktivizo rrugëzuesin automatik për te kufjet ose altoparlantët"</string>
+    <string name="usb_audio_disable_routing_summary" msgid="980282760277312264">"Çaktivizo router-in automatik për te kufjet ose altoparlantët"</string>
     <string name="debug_layout" msgid="5981361776594526155">"Shfaq konturet e kuadrit"</string>
     <string name="debug_layout_summary" msgid="2001775315258637682">"Shfaq konturet e klipit, hapësirat etj."</string>
     <string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Detyro drejtimin e shkrimit nga e djathta në të majtë"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 7f906f6..6d874ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -117,7 +117,7 @@
 
     public boolean isPreferred(BluetoothDevice device) {
         if (mService == null) return false;
-        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+        return mService.getPriority(device) != BluetoothProfile.PRIORITY_OFF;
     }
 
     public int getPreferred(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
index 0b69963..ac7a121 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
@@ -38,7 +38,7 @@
             "/odm/etc/NOTICE.xml.gz",
             "/oem/etc/NOTICE.xml.gz",
             "/product/etc/NOTICE.xml.gz",
-            "/product_services/etc/NOTICE.xml.gz"};
+            "/system_ext/etc/NOTICE.xml.gz"};
     static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
 
     private final Context mContext;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index de38e8a..b15ea98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -18,15 +18,11 @@
 
 import android.content.Context;
 import android.net.NetworkTemplate;
-import android.os.ParcelUuid;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Utils class for data usage
  */
@@ -38,7 +34,7 @@
      */
     public static NetworkTemplate getMobileTemplate(Context context, int subId) {
         final TelephonyManager telephonyManager = context.getSystemService(
-                TelephonyManager.class);
+                TelephonyManager.class).createForSubscriptionId(subId);
         final SubscriptionManager subscriptionManager = context.getSystemService(
                 SubscriptionManager.class);
         final SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfo(subId);
@@ -49,23 +45,8 @@
             Log.i(TAG, "Subscription is not active: " + subId);
             return mobileAll;
         }
-        final ParcelUuid groupUuid = info.getGroupUuid();
-        if (groupUuid == null) {
-            Log.i(TAG, "Subscription doesn't have valid group uuid: " + subId);
-            return mobileAll;
-        }
 
-        // Otherwise merge other subscriberId to create new NetworkTemplate
-        final List<SubscriptionInfo> groupInfos = subscriptionManager.getSubscriptionsInGroup(
-                groupUuid);
-        final List<String> mergedSubscriberIds = new ArrayList<>();
-        for (SubscriptionInfo subInfo : groupInfos) {
-            final String subscriberId = telephonyManager.getSubscriberId(
-                    subInfo.getSubscriptionId());
-            if (subscriberId != null) {
-                mergedSubscriberIds.add(subscriberId);
-            }
-        }
-        return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds.toArray(new String[0]));
+        // Use old API to build networkTemplate
+        return NetworkTemplate.normalize(mobileAll, telephonyManager.getMergedSubscriberIds());
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
index dc33cfe..821c0b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -44,7 +44,6 @@
 public class DataUsageUtilsTest {
 
     private static final int SUB_ID = 1;
-    private static final int SUB_ID_2 = 2;
     private static final String SUBSCRIBER_ID = "Test Subscriber";
     private static final String SUBSCRIBER_ID_2 = "Test Subscriber 2";
 
@@ -67,11 +66,11 @@
 
         mContext = spy(RuntimeEnvironment.application);
         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
         when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
         when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
-        when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
-        when(mInfo1.getSubscriptionId()).thenReturn(SUB_ID);
-        when(mInfo2.getSubscriptionId()).thenReturn(SUB_ID_2);
+        when(mTelephonyManager.getMergedSubscriberIds()).thenReturn(
+                new String[]{SUBSCRIBER_ID, SUBSCRIBER_ID_2});
 
         mInfos = new ArrayList<>();
         mInfos.add(mInfo1);
@@ -89,17 +88,7 @@
     }
 
     @Test
-    public void getMobileTemplate_groupUuidNull_returnMobileAll() {
-        when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
-        when(mInfo1.getGroupUuid()).thenReturn(null);
-
-        final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
-        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
-        assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID_2)).isFalse();
-    }
-
-    @Test
-    public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
+    public void getMobileTemplate_infoExisted_returnMobileMerged() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 98c6702..8689eef 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -101,6 +101,8 @@
     <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" />
+    <!-- Permission required to test onPermissionsChangedListener -->
+    <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" />
     <uses-permission android:name="android.permission.SET_KEYBOARD_LAYOUT" />
     <uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
     <uses-permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 17274f4..4f74605b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -20,6 +20,7 @@
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         package="com.android.systemui"
         android:sharedUserId="android.uid.systemui"
+        xmlns:tools="http://schemas.android.com/tools"
         coreApp="true">
 
     <!-- Using OpenGL ES 2.0 -->
@@ -259,7 +260,8 @@
         android:theme="@style/Theme.SystemUI"
         android:defaultToDeviceProtectedStorage="true"
         android:directBootAware="true"
-        android:appComponentFactory="androidx.core.app.CoreComponentFactory">
+        tools:replace="android:appComponentFactory"
+        android:appComponentFactory=".SystemUIAppComponentFactory">
         <!-- Keep theme in sync with SystemUIApplication.onCreate().
              Setting the theme on the application does not affect views inflated by services.
              The application theme is set again from onCreate to take effect for those views. -->
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md
index c2159df..c440fba 100644
--- a/packages/SystemUI/docs/dagger.md
+++ b/packages/SystemUI/docs/dagger.md
@@ -53,7 +53,7 @@
 ### Adding injection to a new SystemUI object
 
 Anything that depends on any `@Singleton` provider from SystemUIRootComponent
-should be declared as an `@Subcomponent` of the root component, this requires
+should be declared as a `@Subcomponent` of the root component. This requires
 declaring your own interface for generating your own modules or just the
 object you need injected. The subcomponent also needs to be added to
 SystemUIRootComponent in SystemUIFactory so it can be acquired.
@@ -204,6 +204,13 @@
 }
 ```
 
+## Updating Dagger2
+
+Binaries can be downloaded from https://repo1.maven.org/maven2/com/google/dagger/ and then loaded
+into
+[/prebuilts/tools/common/m2/repository/com/google/dagger/](http://cs/android/prebuilts/tools/common/m2/repository/com/google/dagger/)
+
+
 ## TODO List
 
  - Eliminate usages of Dependency#get
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackView.java
index 14fd149..b89218c 100644
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackView.java
@@ -41,8 +41,10 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.recents.LegacyRecentsImpl;
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
@@ -86,15 +88,15 @@
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.utilities.AnimationProps;
 import com.android.systemui.recents.utilities.Utilities;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.views.grid.GridTaskView;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
-
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -256,7 +258,8 @@
         mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
         mStableLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null);
         mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm);
-        mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
+        mTouchHandler = new TaskStackViewTouchHandler(
+                context, this, mStackScroller, Dependency.get(FalsingManager.class));
         mAnimationHelper = new TaskStackAnimationHelper(context, this);
         mTaskCornerRadiusPx = LegacyRecentsImpl.getConfiguration().isGridEnabled ?
                 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
diff --git a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index dd6926c..a7fb4fa 100644
--- a/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/legacy/recents/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -37,6 +37,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.LegacyRecentsImpl;
 import com.android.systemui.recents.events.EventBus;
@@ -107,7 +108,7 @@
     boolean mInterceptedBySwipeHelper;
 
     public TaskStackViewTouchHandler(Context context, TaskStackView sv,
-            TaskStackViewScroller scroller) {
+            TaskStackViewScroller scroller, FalsingManager falsingManager) {
         Resources res = context.getResources();
         ViewConfiguration configuration = ViewConfiguration.get(context);
         mContext = context;
@@ -119,7 +120,7 @@
         mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
         mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
         mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_fling_overscroll_distance);
-        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context) {
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, context, falsingManager) {
             @Override
             protected float getSize(View v) {
                 return getScaledDismissSize();
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index a549870..7c24130 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -480,4 +480,10 @@
     <!-- ThemePicker package name for overlaying icons. -->
     <string name="themepicker_overlayable_package" translatable="false">com.android.wallpaper</string>
 
+    <!-- Default rounded corner curve (a Bezier). Must match (the curved path in) rounded.xml.
+         Note that while rounded.xml includes the entire path (including the horizontal and vertical
+         corner edges), this pulls out just the curve.
+     -->
+    <string name="config_rounded_mask" translatable="false">"M8,0C3.6,0,0,3.6,0,8"</string>
+
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 2e2666e..66f1949 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -98,6 +98,12 @@
     <item type="id" name="keyguard_hun_animator_start_tag"/>
     <item type="id" name="keyguard_hun_animator_end_tag"/>
 
+    <item type="id" name="view_group_fade_helper_modified_views"/>
+    <item type="id" name="view_group_fade_helper_animator"/>
+    <item type="id" name="view_group_fade_helper_previous_value_tag"/>
+    <item type="id" name="view_group_fade_helper_restore_tag"/>
+    <item type="id" name="view_group_fade_helper_hardware_layer"/>
+
     <!-- Accessibility actions for the notification menu -->
     <item type="id" name="action_snooze_undo"/>
     <item type="id" name="action_snooze_shorter"/>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index a2abb4b..506813b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -307,28 +307,22 @@
         }
         final ActivityOptions finalOptions = options;
 
-        // Execute this from another thread such that we can do other things (like caching the
-        // bitmap for the thumbnail) while AM is busy starting our activity.
-        mBackgroundExecutor.submit(new Runnable() {
-            @Override
-            public void run() {
-                boolean result = false;
-                try {
-                    result = startActivityFromRecents(taskKey.id, finalOptions);
-                } catch (Exception e) {
-                    // Fall through
+
+        boolean result = false;
+        try {
+            result = startActivityFromRecents(taskKey.id, finalOptions);
+        } catch (Exception e) {
+            // Fall through
+        }
+        final boolean finalResult = result;
+        if (resultCallback != null) {
+            resultCallbackHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    resultCallback.accept(finalResult);
                 }
-                final boolean finalResult = result;
-                if (resultCallback != null) {
-                    resultCallbackHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            resultCallback.accept(finalResult);
-                        }
-                    });
-                }
-            }
-        });
+            });
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
new file mode 100644
index 0000000..8fabe7a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 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;
+
+/**
+ * Interface necessary to make Dagger happy. See {@link ContextComponentResolver}.
+ */
+public interface ContextComponentHelper {
+    /** Turns a classname into an instance of the class or returns null. */
+    <T> T resolve(String className);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
new file mode 100644
index 0000000..09bccd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+/**
+ * Used during Service and Activity instantiation to make them injectable.
+ */
+public class ContextComponentResolver implements ContextComponentHelper {
+    private final Map<Class<?>, Provider<Object>> mCreators;
+
+    @Inject
+    ContextComponentResolver(Map<Class<?>, Provider<Object>> creators) {
+        mCreators = creators;
+    }
+
+    /**
+     * Looks up the class name to see if Dagger has an instance of it.
+     */
+    @Override
+    public <T> T resolve(String className) {
+        for (Map.Entry<Class<?>, Provider<Object>> p : mCreators.entrySet()) {
+            if (p.getKey().getName().equals(className)) {
+                return (T) p.getValue().get();
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 9a0e9fc..c9d4957 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -46,6 +46,8 @@
     // We delayed destroy render context that subsequent render requests have chance to cancel it.
     // This is to avoid destroying then recreating render context in a very short time.
     private static final int DELAY_FINISH_RENDERING = 1000;
+    private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
+    private static final int PATIENCE_WAIT_FOR_RENDERING = 5;
     private HandlerThread mWorker;
 
     @Override
@@ -80,7 +82,10 @@
         private StatusBarStateController mController;
         private final Runnable mFinishRenderingTask = this::finishRendering;
         private final boolean mNeedTransition;
+        private final Object mMonitor = new Object();
         private boolean mNeedRedraw;
+        // This variable can only be accessed in synchronized block.
+        private boolean mWaitingForRendering;
 
         GLEngine(Context context) {
             mNeedTransition = ActivityManager.isHighEndGfx()
@@ -122,6 +127,27 @@
             long duration = mNeedTransition || animationDuration != 0 ? animationDuration : 0;
             mWorker.getThreadHandler().post(
                     () -> mRenderer.updateAmbientMode(inAmbientMode, duration));
+            if (inAmbientMode && duration == 0) {
+                // This means that we are transiting from home to aod, to avoid
+                // race condition between window visibility and transition,
+                // we don't return until the transition is finished. See b/136643341.
+                waitForBackgroundRendering();
+            }
+        }
+
+        private void waitForBackgroundRendering() {
+            synchronized (mMonitor) {
+                try {
+                    mWaitingForRendering = true;
+                    for (int patience = 1; mWaitingForRendering; patience++) {
+                        mMonitor.wait(INTERVAL_WAIT_FOR_RENDERING);
+                        mWaitingForRendering &= patience < PATIENCE_WAIT_FOR_RENDERING;
+                    }
+                } catch (InterruptedException ex) {
+                } finally {
+                    mWaitingForRendering = false;
+                }
+            }
         }
 
         @Override
@@ -178,7 +204,8 @@
 
         @Override
         public void preRender() {
-            mWorker.getThreadHandler().post(this::preRenderInternal);
+            // This method should only be invoked from worker thread.
+            preRenderInternal();
         }
 
         private void preRenderInternal() {
@@ -212,7 +239,8 @@
 
         @Override
         public void requestRender() {
-            mWorker.getThreadHandler().post(this::requestRenderInternal);
+            // This method should only be invoked from worker thread.
+            requestRenderInternal();
         }
 
         private void requestRenderInternal() {
@@ -234,7 +262,21 @@
 
         @Override
         public void postRender() {
-            mWorker.getThreadHandler().post(this::scheduleFinishRendering);
+            // This method should only be invoked from worker thread.
+            notifyWaitingThread();
+            scheduleFinishRendering();
+        }
+
+        private void notifyWaitingThread() {
+            synchronized (mMonitor) {
+                if (mWaitingForRendering) {
+                    try {
+                        mWaitingForRendering = false;
+                        mMonitor.notify();
+                    } catch (IllegalMonitorStateException ex) {
+                    }
+                }
+            }
         }
 
         private void cancelFinishRenderingTask() {
diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
new file mode 100644
index 0000000..6282c6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 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;
+
+import com.android.systemui.doze.DozeService;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Services and Activities that are injectable should go here.
+ */
+@Module
+public abstract class ServiceBinder {
+
+    @Binds
+    public abstract ContextComponentHelper bindComponentHelper(
+            ContextComponentResolver componentHelper);
+
+    @Binds
+    @IntoMap
+    @ClassKey(DozeService.class)
+    public abstract Object bindDozeService(DozeService service);
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 84e0238..58c52a1 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -34,7 +34,6 @@
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -98,7 +97,8 @@
 
     private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
 
-    public SwipeHelper(int swipeDirection, Callback callback, Context context) {
+    public SwipeHelper(
+            int swipeDirection, Callback callback, Context context, FalsingManager falsingManager) {
         mContext = context;
         mCallback = callback;
         mHandler = new Handler();
@@ -113,7 +113,7 @@
         mDensityScale =  res.getDisplayMetrics().density;
         mFalsingThreshold = res.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
         mFadeDependingOnAmountSwiped = res.getBoolean(R.bool.config_fadeDependingOnAmountSwiped);
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
+        mFalsingManager = falsingManager;
         mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
new file mode 100644
index 0000000..00ae992
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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;
+
+import android.app.Application;
+import android.app.Service;
+import android.content.Intent;
+
+import androidx.core.app.CoreComponentFactory;
+
+import javax.inject.Inject;
+
+/**
+ * Implementation of AppComponentFactory that injects into constructors.
+ */
+public class SystemUIAppComponentFactory extends CoreComponentFactory {
+
+    @Inject
+    public ContextComponentHelper mComponentHelper;
+
+    public SystemUIAppComponentFactory() {
+        super();
+    }
+
+    @Override
+    public Application instantiateApplication(ClassLoader cl, String className)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        Application app = super.instantiateApplication(cl, className);
+        if (app instanceof SystemUIApplication) {
+            ((SystemUIApplication) app).setContextAvailableCallback(
+                    context -> {
+                        SystemUIFactory.createFromConfig(context);
+                        SystemUIFactory.getInstance().getRootComponent().inject(
+                                SystemUIAppComponentFactory.this);
+                    }
+            );
+        }
+
+        return app;
+    }
+
+    @Override
+    public Service instantiateService(ClassLoader cl, String className, Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        Service service = mComponentHelper.resolve(className);
+        if (service != null) {
+            return checkCompatWrapper(service);
+        }
+        return super.instantiateService(cl, className, intent);
+    }
+
+    static <T> T checkCompatWrapper(T obj) {
+        if (obj instanceof CompatWrapped) {
+            T wrapper = (T) ((CompatWrapped) obj).getWrapper();
+            if (wrapper != null) {
+                return wrapper;
+            }
+        }
+
+        return obj;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index e89e6cb..f8449ad 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -60,16 +60,20 @@
     private boolean mServicesStarted;
     private boolean mBootCompleted;
     private final Map<Class<?>, Object> mComponents = new HashMap<>();
+    private ContextAvailableCallback mContextAvailableCallback;
 
     @Override
     public void onCreate() {
         super.onCreate();
+        // This line is used to setup Dagger's dependency injection and should be kept at the
+        // top of this method.
+        mContextAvailableCallback.onContextAvailable(this);
+
         // Set the application theme that is inherited by all services. Note that setting the
         // application theme in the manifest does only work for activities. Keep this in sync with
         // the theme set there.
         setTheme(R.style.Theme_SystemUI);
 
-        SystemUIFactory.createFromConfig(this);
 
         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
             IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
@@ -286,4 +290,12 @@
     public SystemUI[] getServices() {
         return mServices;
     }
+
+    void setContextAvailableCallback(ContextAvailableCallback callback) {
+        mContextAvailableCallback = callback;
+    }
+
+    interface ContextAvailableCallback {
+        void onContextAvailable(Context context);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 4d42e0c..311ed8a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -19,8 +19,8 @@
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
 
-import android.annotation.Nullable;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -29,17 +29,15 @@
 import android.util.Log;
 import android.view.ViewGroup;
 
-
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.util.function.TriConsumer;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.EnhancedEstimatesImpl;
@@ -69,8 +67,6 @@
 import com.android.systemui.statusbar.phone.UnlockMethodCache;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AsyncSensorManager;
-import com.android.systemui.util.InjectionInflationController;
-import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.volume.VolumeDialogComponent;
 
 import java.util.function.Consumer;
@@ -78,7 +74,6 @@
 import javax.inject.Named;
 import javax.inject.Singleton;
 
-import dagger.Component;
 import dagger.Module;
 import dagger.Provides;
 
@@ -116,7 +111,7 @@
     public SystemUIFactory() {}
 
     protected void init(Context context) {
-        initWithRootComponent(DaggerSystemUIFactory_SystemUIRootComponent.builder()
+        initWithRootComponent(DaggerSystemUIRootComponent.builder()
                 .systemUIFactory(this)
                 .dependencyProvider(new com.android.systemui.DependencyProvider())
                 .contextHolder(new ContextHolder(context))
@@ -143,9 +138,10 @@
     public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
             LockPatternUtils lockPatternUtils,  ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry,
-            KeyguardBouncer.BouncerExpansionCallback expansionCallback) {
+            KeyguardBouncer.BouncerExpansionCallback expansionCallback,
+            FalsingManager falsingManager) {
         return new KeyguardBouncer(context, callback, lockPatternUtils, container,
-                dismissCallbackRegistry, FalsingManagerFactory.getInstance(context),
+                dismissCallbackRegistry, falsingManager,
                 expansionCallback, UnlockMethodCache.getInstance(context),
                 KeyguardUpdateMonitor.getInstance(context), new Handler(Looper.getMainLooper()));
     }
@@ -276,29 +272,4 @@
             return mContext;
         }
     }
-
-    @Singleton
-    @Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
-            ContextHolder.class})
-    public interface SystemUIRootComponent {
-        @Singleton
-        Dependency.DependencyInjector createDependency();
-
-        @Singleton
-        StatusBar.StatusBarInjector getStatusBarInjector();
-
-        /**
-         * FragmentCreator generates all Fragments that need injection.
-         */
-        @Singleton
-        FragmentService.FragmentCreator createFragmentCreator();
-
-        /**
-         * ViewCreator generates all Views that need injection.
-         */
-        InjectionInflationController.ViewCreator createViewCreator();
-
-        @Singleton
-        GarbageMonitor createGarbageMonitor();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
new file mode 100644
index 0000000..c732df3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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;
+
+import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.InjectionInflationController;
+import com.android.systemui.util.leak.GarbageMonitor;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+
+/**
+ * Root component for Dagger injection.
+ */
+@Singleton
+@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
+        ServiceBinder.class, SystemUIFactory.ContextHolder.class})
+public interface SystemUIRootComponent {
+    /**
+     * Main dependency providing module.
+     */
+    @Singleton
+    Dependency.DependencyInjector createDependency();
+
+    /**
+     * Injects the StatusBar.
+     */
+    @Singleton
+    StatusBar.StatusBarInjector getStatusBarInjector();
+
+    /**
+     * FragmentCreator generates all Fragments that need injection.
+     */
+    @Singleton
+    FragmentService.FragmentCreator createFragmentCreator();
+
+    /**
+     * ViewCreator generates all Views that need injection.
+     */
+    InjectionInflationController.ViewCreator createViewCreator();
+
+    /**
+     * Creatse a GarbageMonitor.
+     */
+    @Singleton
+    GarbageMonitor createGarbageMonitor();
+
+    /**
+     * Injects into the supplied argument.
+     */
+    void inject(SystemUIAppComponentFactory factory);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java b/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java
index 00346a3..e61e47a 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/CircularCornerPathRenderer.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.assist.ui;
 
+import android.content.Context;
 import android.graphics.Path;
 
 /**
@@ -29,12 +30,11 @@
     private final int mWidth;
     private final Path mPath = new Path();
 
-    public CircularCornerPathRenderer(int cornerRadiusBottom, int cornerRadiusTop,
-            int width, int height) {
-        mCornerRadiusBottom = cornerRadiusBottom;
-        mCornerRadiusTop = cornerRadiusTop;
-        mHeight = height;
-        mWidth = width;
+    public CircularCornerPathRenderer(Context context) {
+        mCornerRadiusBottom = DisplayUtils.getCornerRadiusBottom(context);
+        mCornerRadiusTop = DisplayUtils.getCornerRadiusTop(context);
+        mHeight = DisplayUtils.getHeight(context);
+        mWidth = DisplayUtils.getWidth(context);
     }
 
     @Override // CornerPathRenderer
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
index 43e7045..bc782a7 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java
@@ -247,13 +247,9 @@
      * To render corners that aren't circular, override this method in a subclass.
      */
     protected CornerPathRenderer createCornerPathRenderer(Context context) {
-        int displayWidth = DisplayUtils.getWidth(context);
-        int displayHeight = DisplayUtils.getHeight(context);
-        int cornerRadiusBottom = DisplayUtils.getCornerRadiusBottom(context);
-        int cornerRadiusTop = DisplayUtils.getCornerRadiusTop(context);
-        return new CircularCornerPathRenderer(
-                cornerRadiusBottom, cornerRadiusTop, displayWidth, displayHeight);
+        return new CircularCornerPathRenderer(context);
     }
+
     /**
      * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color
      * appropriately. Intention is to match the home handle color.
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java b/packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java
new file mode 100644
index 0000000..2bad7fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 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.assist.ui;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.Log;
+import android.util.PathParser;
+
+import com.android.systemui.R;
+
+/**
+ * Parses a path describing rounded corners from a string.
+ */
+public final class PathSpecCornerPathRenderer extends CornerPathRenderer {
+    private static final String TAG = "PathSpecCornerPathRenderer";
+
+    private final int mHeight;
+    private final int mWidth;
+    private final float mPathScale;
+    private final int mBottomCornerRadius;
+    private final int mTopCornerRadius;
+
+    private final Path mPath = new Path();
+    private final Path mRoundedPath;
+    private final Matrix mMatrix = new Matrix();
+
+    public PathSpecCornerPathRenderer(Context context) {
+        mWidth = DisplayUtils.getWidth(context);
+        mHeight = DisplayUtils.getHeight(context);
+
+        mBottomCornerRadius = DisplayUtils.getCornerRadiusBottom(context);
+        mTopCornerRadius = DisplayUtils.getCornerRadiusTop(context);
+
+        String pathData = context.getResources().getString(R.string.config_rounded_mask);
+        Path path = PathParser.createPathFromPathData(pathData);
+        if (path == null) {
+            Log.e(TAG, "No rounded corner path found!");
+            mRoundedPath = new Path();
+        } else {
+            mRoundedPath = path;
+
+        }
+
+        RectF bounds = new RectF();
+        mRoundedPath.computeBounds(bounds, true);
+
+        // we use this to scale the path such that the larger of its [width, height] is scaled to
+        // the corner radius (to account for asymmetric paths)
+        mPathScale = Math.min(
+                Math.abs(bounds.right - bounds.left),
+                Math.abs(bounds.top - bounds.bottom));
+    }
+
+    /**
+     * Scales and rotates each corner from the path specification to its correct position.
+     *
+     * Note: the rounded corners are passed in as the full shape (a curved triangle), but we only
+     * want the actual corner curve. Therefore we call getSegment to jump past the horizontal and
+     * vertical lines.
+     */
+    @Override
+    public Path getCornerPath(Corner corner) {
+        if (mRoundedPath.isEmpty()) {
+            return mRoundedPath;
+        }
+        int cornerRadius;
+        int rotateDegrees;
+        int translateX;
+        int translateY;
+        switch (corner) {
+            case TOP_LEFT:
+                cornerRadius = mTopCornerRadius;
+                rotateDegrees = 0;
+                translateX = 0;
+                translateY = 0;
+                break;
+            case TOP_RIGHT:
+                cornerRadius = mTopCornerRadius;
+                rotateDegrees = 90;
+                translateX = mWidth;
+                translateY = 0;
+                break;
+            case BOTTOM_RIGHT:
+                cornerRadius = mBottomCornerRadius;
+                rotateDegrees = 180;
+                translateX = mWidth;
+                translateY = mHeight;
+                break;
+            case BOTTOM_LEFT:
+            default:
+                cornerRadius = mBottomCornerRadius;
+                rotateDegrees = 270;
+                translateX = 0;
+                translateY = mHeight;
+                break;
+        }
+        mPath.reset();
+        mMatrix.reset();
+        mPath.addPath(mRoundedPath);
+
+        mMatrix.preScale(cornerRadius / mPathScale, cornerRadius / mPathScale);
+        mMatrix.postRotate(rotateDegrees);
+        mMatrix.postTranslate(translateX, translateY);
+        mPath.transform(mMatrix);
+        return mPath;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java b/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java
index 65a9fcc..fb41b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/PerimeterPathGuide.java
@@ -102,7 +102,7 @@
      * Sets the rotation.
      *
      * @param rotation one of Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180,
-     *                  Surface.ROTATION_270
+     *                 Surface.ROTATION_270
      */
     public void setRotation(int rotation) {
         if (rotation != mRotation) {
@@ -229,14 +229,14 @@
                     - mDeviceWidthPx) / 2, (mDeviceWidthPx - mDeviceHeightPx) / 2);
         }
 
-        CircularCornerPathRenderer.Corner screenBottomLeft = getRotatedCorner(
-                CircularCornerPathRenderer.Corner.BOTTOM_LEFT);
-        CircularCornerPathRenderer.Corner screenBottomRight = getRotatedCorner(
-                CircularCornerPathRenderer.Corner.BOTTOM_RIGHT);
-        CircularCornerPathRenderer.Corner screenTopLeft = getRotatedCorner(
-                CircularCornerPathRenderer.Corner.TOP_LEFT);
-        CircularCornerPathRenderer.Corner screenTopRight = getRotatedCorner(
-                CircularCornerPathRenderer.Corner.TOP_RIGHT);
+        CornerPathRenderer.Corner screenBottomLeft = getRotatedCorner(
+                CornerPathRenderer.Corner.BOTTOM_LEFT);
+        CornerPathRenderer.Corner screenBottomRight = getRotatedCorner(
+                CornerPathRenderer.Corner.BOTTOM_RIGHT);
+        CornerPathRenderer.Corner screenTopLeft = getRotatedCorner(
+                CornerPathRenderer.Corner.TOP_LEFT);
+        CornerPathRenderer.Corner screenTopRight = getRotatedCorner(
+                CornerPathRenderer.Corner.TOP_RIGHT);
 
         mRegions[Region.BOTTOM_LEFT.ordinal()].path =
                 mCornerPathRenderer.getInsetPath(screenBottomLeft, mEdgeInset);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFactory.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFactory.java
deleted file mode 100644
index 01921f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.classifier;
-
-import android.content.Context;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.FalsingManager;
-
-/**
- * When the phone is locked, listens to touch, sensor and phone events and sends them to
- * DataCollector and HumanInteractionClassifier.
- *
- * It does not collect touch events when the bouncer shows up.
- *
- * TODO: FalsingManager supports dependency injection. Use it.
- */
-public class FalsingManagerFactory {
-    private static FalsingManager sInstance = null;
-
-    private FalsingManagerFactory() {}
-
-    public static FalsingManager getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = Dependency.get(FalsingManager.class);
-        }
-        return sInstance;
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
new file mode 100644
index 0000000..0041b0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2019 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.classifier;
+
+import android.net.Uri;
+import android.view.MotionEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.plugins.FalsingManager;
+
+import java.io.PrintWriter;
+
+/**
+ * Simple Fake for testing where {@link FalsingManager} is required.
+ */
+public class FalsingManagerFake implements FalsingManager {
+    private boolean mIsFalseTouch;
+    private boolean mIsUnlockingDisabled;
+    private boolean mIsClassiferEnabled;
+    private boolean mShouldEnforceBouncer;
+    private boolean mIsReportingEnabled;
+
+    @Override
+    public void onSucccessfulUnlock() {
+
+    }
+
+    @Override
+    public void onNotificationActive() {
+
+    }
+
+    @Override
+    public void setShowingAod(boolean showingAod) {
+
+    }
+
+    @Override
+    public void onNotificatonStartDraggingDown() {
+
+    }
+
+    @VisibleForTesting
+    public void setIsUnlockingDisabled(boolean isUnlockingDisabled) {
+        mIsUnlockingDisabled = isUnlockingDisabled;
+    }
+
+    @Override
+    public boolean isUnlockingDisabled() {
+        return mIsUnlockingDisabled;
+    }
+
+    @VisibleForTesting
+    public void setIsFalseTouch(boolean isFalseTouch) {
+        mIsFalseTouch = isFalseTouch;
+    }
+
+    @Override
+    public boolean isFalseTouch() {
+        return mIsFalseTouch;
+    }
+
+    @Override
+    public void onNotificatonStopDraggingDown() {
+
+    }
+
+    @Override
+    public void setNotificationExpanded() {
+
+    }
+
+    @VisibleForTesting
+    public void setIsClassiferEnabled(boolean isClassiferEnabled) {
+        mIsClassiferEnabled = isClassiferEnabled;
+    }
+
+    @Override
+    public boolean isClassiferEnabled() {
+        return mIsClassiferEnabled;
+    }
+
+    @Override
+    public void onQsDown() {
+
+    }
+
+    @Override
+    public void setQsExpanded(boolean expanded) {
+
+    }
+
+    @VisibleForTesting
+    public void setShouldEnforceBouncer(boolean shouldEnforceBouncer) {
+        mShouldEnforceBouncer = shouldEnforceBouncer;
+    }
+
+    @Override
+    public boolean shouldEnforceBouncer() {
+        return mShouldEnforceBouncer;
+    }
+
+    @Override
+    public void onTrackingStarted(boolean secure) {
+
+    }
+
+    @Override
+    public void onTrackingStopped() {
+
+    }
+
+    @Override
+    public void onLeftAffordanceOn() {
+
+    }
+
+    @Override
+    public void onCameraOn() {
+
+    }
+
+    @Override
+    public void onAffordanceSwipingStarted(boolean rightCorner) {
+
+    }
+
+    @Override
+    public void onAffordanceSwipingAborted() {
+
+    }
+
+    @Override
+    public void onStartExpandingFromPulse() {
+
+    }
+
+    @Override
+    public void onExpansionFromPulseStopped() {
+
+    }
+
+    @Override
+    public Uri reportRejectedTouch() {
+        return null;
+    }
+
+    @Override
+    public void onScreenOnFromTouch() {
+
+    }
+
+
+    @VisibleForTesting
+    public void setIsReportingEnabled(boolean isReportingEnabled) {
+        mIsReportingEnabled = isReportingEnabled;
+    }
+
+    @Override
+    public boolean isReportingEnabled() {
+        return mIsReportingEnabled;
+    }
+
+    @Override
+    public void onUnlockHintStarted() {
+
+    }
+
+    @Override
+    public void onCameraHintStarted() {
+
+    }
+
+    @Override
+    public void onLeftAffordanceHintStarted() {
+
+    }
+
+    @Override
+    public void onScreenTurningOn() {
+
+    }
+
+    @Override
+    public void onScreenOff() {
+
+    }
+
+    @Override
+    public void onNotificatonStopDismissing() {
+
+    }
+
+    @Override
+    public void onNotificationDismissed() {
+
+    }
+
+    @Override
+    public void onNotificatonStartDismissing() {
+
+    }
+
+    @Override
+    public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
+
+    }
+
+    @Override
+    public void onBouncerShown() {
+
+    }
+
+    @Override
+    public void onBouncerHidden() {
+
+    }
+
+    @Override
+    public void onTouchEvent(MotionEvent ev, int width, int height) {
+
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index fc3d1a5..537c09e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -28,8 +28,8 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -41,7 +41,7 @@
     }
 
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
-    public DozeMachine assembleMachine(DozeService dozeService) {
+    public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager) {
         Context context = dozeService;
         SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
@@ -63,7 +63,7 @@
         DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock);
         machine.setParts(new DozeMachine.Part[]{
                 new DozePauser(handler, machine, alarmManager, params.getPolicy()),
-                new DozeFalsingManagerAdapter(FalsingManagerFactory.getInstance(context)),
+                new DozeFalsingManagerAdapter(falsingManager),
                 createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
                         handler, wakeLock, machine, dockManager),
                 createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params),
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 2db7306..e92acfc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -25,23 +25,29 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.DozeServicePlugin;
 import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.plugins.PluginManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+
 public class DozeService extends DreamService
         implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> {
     private static final String TAG = "DozeService";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private final FalsingManager mFalsingManager;
 
     private DozeMachine mDozeMachine;
     private DozeServicePlugin mDozePlugin;
     private PluginManager mPluginManager;
 
-    public DozeService() {
+    @Inject
+    public DozeService(FalsingManager falsingManager) {
         setDebug(DEBUG);
+        mFalsingManager = falsingManager;
     }
 
     @Override
@@ -56,7 +62,7 @@
         }
         mPluginManager = Dependency.get(PluginManager.class);
         mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
-        mDozeMachine = new DozeFactory().assembleMachine(this);
+        mDozeMachine = new DozeFactory().assembleMachine(this, mFalsingManager);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index 8dbaf0f..b4cc571 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -22,7 +22,7 @@
 
 import com.android.systemui.ConfigurationChangedReceiver;
 import com.android.systemui.Dumpable;
-import com.android.systemui.SystemUIFactory;
+import com.android.systemui.SystemUIRootComponent;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.statusbar.phone.NavigationBarFragment;
 
@@ -51,7 +51,7 @@
     private final FragmentCreator mFragmentCreator;
 
     @Inject
-    public FragmentService(SystemUIFactory.SystemUIRootComponent rootComponent) {
+    public FragmentService(SystemUIRootComponent rootComponent) {
         mFragmentCreator = rootComponent.createFragmentCreator();
         initInjectionMap();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
index 6a1f24a..45e97b3 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
@@ -84,7 +84,17 @@
     void updateAwake(boolean awake, long duration) {
         mAwake = awake;
         mAnimator.setDuration(duration);
-        animate();
+        if (!mAwake && duration == 0) {
+            // We are transiting from home to aod,
+            // since main thread is waiting for rendering finished, we only need draw
+            // the last state directly, which is a black screen.
+            mReveal = MIN_REVEAL;
+            mRevealListener.onRevealStart();
+            mRevealListener.onRevealStateChanged();
+            mRevealListener.onRevealEnd();
+        } else {
+            animate();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d7ca4d0..819ce5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -61,6 +61,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManagerPolicyConstants;
 import android.view.animation.Animation;
@@ -83,8 +84,9 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.UiOffloadThread;
-import com.android.systemui.classifier.FalsingManagerFactory;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -1594,7 +1596,7 @@
                     Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
                     handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
-                    FalsingManagerFactory.getInstance(mContext).onSucccessfulUnlock();
+                    Dependency.get(FalsingManager.class).onSucccessfulUnlock();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
@@ -2060,9 +2062,12 @@
 
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
             ViewGroup container, NotificationPanelView panelView,
-            BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer) {
+            BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer,
+            View notificationContainer, KeyguardBypassController bypassController,
+            FalsingManager falsingManager) {
         mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView,
-                biometricUnlockController, mDismissCallbackRegistry, lockIconContainer);
+                biometricUnlockController, mDismissCallbackRegistry, lockIconContainer,
+                notificationContainer, bypassController, falsingManager);
         return mStatusBarKeyguardViewManager;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ed6f599..19edc94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -120,6 +120,8 @@
     private int mCurrentBoundedUserId = -1;
     private float mNavBarButtonAlpha;
     private boolean mInputFocusTransferStarted;
+    private float mInputFocusTransferStartY;
+    private long mInputFocusTransferStartMillis;
     private float mWindowCornerRadius;
     private boolean mSupportsRoundedCornersOnWindows;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
@@ -180,12 +182,16 @@
                         int action = event.getActionMasked();
                         if (action == ACTION_DOWN) {
                             mInputFocusTransferStarted = true;
-
+                            mInputFocusTransferStartY = event.getY();
+                            mInputFocusTransferStartMillis = event.getEventTime();
+                            bar.onInputFocusTransfer(mInputFocusTransferStarted, 0 /* velocity */);
                         }
                         if (action == ACTION_UP || action == ACTION_CANCEL) {
                             mInputFocusTransferStarted = false;
+                            bar.onInputFocusTransfer(mInputFocusTransferStarted,
+                                    (event.getY() - mInputFocusTransferStartY)
+                                    / (event.getEventTime() - mInputFocusTransferStartMillis));
                         }
-                        bar.onInputFocusTransfer(mInputFocusTransferStarted);
                         event.recycle();
                     }
                 });
@@ -596,7 +602,7 @@
                 StatusBar bar = SysUiServiceProvider.getComponent(mContext, StatusBar.class);
                 if (bar != null) {
                     mInputFocusTransferStarted = false;
-                    bar.onInputFocusTransfer(false);
+                    bar.onInputFocusTransfer(false, 0 /* velocity */);
                 }
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 514a2ae..d939828 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -29,7 +29,6 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
@@ -59,14 +58,15 @@
     private FalsingManager mFalsingManager;
 
     public DragDownHelper(Context context, View host, ExpandHelper.Callback callback,
-            DragDownCallback dragDownCallback) {
+            DragDownCallback dragDownCallback,
+            FalsingManager falsingManager) {
         mMinDragDistance = context.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_drag_down_min_distance);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mCallback = callback;
         mDragDownCallback = dragDownCallback;
         mHost = host;
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
+        mFalsingManager = falsingManager;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 986486a..48d6de9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -27,11 +27,11 @@
 import android.view.MotionEvent
 import android.view.VelocityTracker
 import android.view.ViewConfiguration
+import com.android.systemui.Dependency
 
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.Interpolators
 import com.android.systemui.R
-import com.android.systemui.classifier.FalsingManagerFactory
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -115,7 +115,7 @@
         mMinDragDistance = context.resources.getDimensionPixelSize(
                 R.dimen.keyguard_drag_down_min_distance)
         mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
-        mFalsingManager = FalsingManagerFactory.getInstance(context)
+        mFalsingManager = Dependency.get(FalsingManager::class.java)
         mPowerManager = context.getSystemService(PowerManager::class.java)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
new file mode 100644
index 0000000..847d1cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 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.view.View
+import android.view.ViewGroup
+import com.android.systemui.Interpolators
+import com.android.systemui.R
+
+/**
+ * Class to help with fading of view groups without fading one subview
+ */
+class ViewGroupFadeHelper {
+    companion object {
+        private val visibilityIncluder = {
+            view: View -> view.visibility == View.VISIBLE
+        }
+
+        /**
+         * Fade out all views of a root except a single child. This will iterate over all children
+         * of the view and make sure that the animation works smoothly.
+         * @param root the view root to fade the children away
+         * @param excludedView which view should remain
+         * @param duration the duration of the animation
+         */
+        @JvmStatic
+        fun fadeOutAllChildrenExcept(root: ViewGroup, excludedView: View, duration: Long,
+                                     endRunnable: Runnable?) {
+            // starting from the view going up, we are adding the siblings of the child to the set
+            // of views that need to be faded.
+            val viewsToFadeOut = gatherViews(root, excludedView, visibilityIncluder)
+
+            // Applying the right layertypes for the animation
+            for (viewToFade in viewsToFadeOut) {
+                if (viewToFade.hasOverlappingRendering
+                        && viewToFade.layerType == View.LAYER_TYPE_NONE) {
+                    viewToFade.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+                    viewToFade.setTag(R.id.view_group_fade_helper_hardware_layer, true)
+                }
+            }
+
+            val animator = ValueAnimator.ofFloat(1.0f, 0.0f).apply {
+                this.duration = duration
+                interpolator = Interpolators.ALPHA_OUT
+                addUpdateListener { animation ->
+                    val previousSetAlpha = root.getTag(
+                            R.id.view_group_fade_helper_previous_value_tag) as Float?
+                    val newAlpha = animation.animatedValue as Float
+                    for (viewToFade in viewsToFadeOut) {
+                        if (viewToFade.alpha != previousSetAlpha) {
+                            // A value was set that wasn't set from our view, let's store it and restore
+                            // it at the end
+                            viewToFade.setTag(R.id.view_group_fade_helper_restore_tag, viewToFade.alpha)
+                        }
+                        viewToFade.alpha = newAlpha
+                    }
+                    root.setTag(R.id.view_group_fade_helper_previous_value_tag, newAlpha)
+                }
+                addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        endRunnable?.run()
+                    }
+                })
+                start()
+            }
+            root.setTag(R.id.view_group_fade_helper_modified_views, viewsToFadeOut)
+            root.setTag(R.id.view_group_fade_helper_animator, animator)
+        }
+
+        private fun gatherViews(root: ViewGroup, excludedView: View,
+                                shouldInclude: (View) -> Boolean): MutableSet<View> {
+            val viewsToFadeOut = mutableSetOf<View>()
+            var parent = excludedView.parent as ViewGroup?
+            var viewContainingExcludedView = excludedView;
+            while (parent != null) {
+                for (i in 0 until parent.childCount) {
+                    val child = parent.getChildAt(i)
+                    if (shouldInclude.invoke(child) && viewContainingExcludedView != child) {
+                        viewsToFadeOut.add(child)
+                    }
+                }
+                if (parent == root) {
+                    break;
+                }
+                viewContainingExcludedView = parent
+                parent = parent.parent as ViewGroup?
+            }
+            return viewsToFadeOut
+        }
+
+        /**
+         * Reset all view alphas for views previously transformed away.
+         */
+        @JvmStatic
+        fun reset(root: ViewGroup) {
+            @Suppress("UNCHECKED_CAST")
+            val modifiedViews = root.getTag(R.id.view_group_fade_helper_modified_views)
+                    as MutableSet<View>?
+            val animator = root.getTag(R.id.view_group_fade_helper_animator) as Animator?
+            if (modifiedViews == null || animator == null) {
+                // nothing to restore
+                return
+            }
+            animator.cancel()
+            val lastSetValue = root.getTag(
+                    R.id.view_group_fade_helper_previous_value_tag) as Float?
+            for (viewToFade in modifiedViews) {
+                val restoreAlpha = viewToFade.getTag(
+                        R.id.view_group_fade_helper_restore_tag) as Float?
+                if (restoreAlpha == null) {
+                    continue
+                }
+                if (lastSetValue == viewToFade.alpha) {
+                    // it was modified after the transition!
+                    viewToFade.alpha = restoreAlpha
+                }
+                val needsLayerReset = viewToFade.getTag(
+                        R.id.view_group_fade_helper_hardware_layer) as Boolean?
+                if (needsLayerReset == true) {
+                    viewToFade.setLayerType(View.LAYER_TYPE_NONE, null)
+                    viewToFade.setTag(R.id.view_group_fade_helper_hardware_layer, null)
+                }
+                viewToFade.setTag(R.id.view_group_fade_helper_restore_tag, null)
+            }
+            root.setTag(R.id.view_group_fade_helper_modified_views, null)
+            root.setTag(R.id.view_group_fade_helper_previous_value_tag, null)
+            root.setTag(R.id.view_group_fade_helper_animator, null)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index dca152f..8d73251 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -33,9 +33,9 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
@@ -130,7 +130,7 @@
     private boolean mLastInSection;
     private boolean mFirstInSection;
     private boolean mIsBelowSpeedBump;
-    private FalsingManager mFalsingManager;
+    private final FalsingManager mFalsingManager;
 
     private float mNormalBackgroundVisibilityAmount;
     private float mDimmedBackgroundFadeInAmount = -1;
@@ -164,10 +164,10 @@
         super(context, attrs);
         mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
         mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
+        mFalsingManager = Dependency.get(FalsingManager.class);  // TODO: inject into a controller.
         setClipChildren(false);
         setClipToPadding(false);
         updateColors();
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
         mDoubleTapHelper = new DoubleTapHelper(this, (active) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a8327f6..6e84089 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -74,7 +74,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -1635,7 +1634,7 @@
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
+        mFalsingManager = Dependency.get(FalsingManager.class);  // TODO: inject into a controller.
         mNotificationInflater = new NotificationContentInflater(this);
         mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b654cd6..bec9520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -86,7 +86,6 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -512,7 +511,8 @@
             ActivityStarter activityStarter,
             StatusBarStateController statusBarStateController,
             HeadsUpManagerPhone headsUpManager,
-            KeyguardBypassController keyguardBypassController) {
+            KeyguardBypassController keyguardBypassController,
+            FalsingManager falsingManager) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
 
@@ -527,6 +527,7 @@
         mHeadsUpManager.addListener(mRoundnessManager);
         mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
         mKeyguardBypassController = keyguardBypassController;
+        mFalsingManager = falsingManager;
 
         mSectionsManager =
                 new NotificationSectionsManager(
@@ -551,10 +552,9 @@
         mExpandHelper.setEventSource(this);
         mExpandHelper.setScrollAdapter(this);
         mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback,
-                getContext(), mMenuEventListener);
+                getContext(), mMenuEventListener, mFalsingManager);
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
         initView(context);
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
         mShouldDrawNotificationBackground =
                 res.getBoolean(R.bool.config_drawNotificationBackground);
         mFadeNotificationsOnDismiss =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 4dfc343..0968674 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.SwipeHelper;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -50,9 +51,11 @@
     private boolean mIsExpanded;
     private boolean mPulsing;
 
-    public NotificationSwipeHelper(int swipeDirection, NotificationCallback callback,
-            Context context, NotificationMenuRowPlugin.OnMenuEventListener menuListener) {
-        super(swipeDirection, callback, context);
+    NotificationSwipeHelper(
+            int swipeDirection, NotificationCallback callback, Context context,
+            NotificationMenuRowPlugin.OnMenuEventListener menuListener,
+            FalsingManager falsingManager) {
+        super(swipeDirection, callback, context, falsingManager);
         mMenuListener = menuListener;
         mCallback = callback;
         mFalsingCheck = new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 4691a31..66b1dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -27,7 +27,6 @@
 
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -59,7 +58,7 @@
     private KeyguardAffordanceView mLeftIcon;
     private KeyguardAffordanceView mRightIcon;
     private Animator mSwipeAnimator;
-    private FalsingManager mFalsingManager;
+    private final FalsingManager mFalsingManager;
     private int mMinBackgroundRadius;
     private boolean mMotionCancelled;
     private int mTouchTargetSize;
@@ -80,12 +79,13 @@
         }
     };
 
-    KeyguardAffordanceHelper(Callback callback, Context context) {
+    KeyguardAffordanceHelper(Callback callback, Context context, FalsingManager falsingManager) {
         mContext = context;
         mCallback = callback;
         initIcons();
         updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
         updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
+        mFalsingManager = falsingManager;
         initDimens();
     }
 
@@ -102,7 +102,6 @@
         mHintGrowAmount =
                 mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
         mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
-        mFalsingManager = FalsingManagerFactory.getInstance(mContext);
     }
 
     private void initIcons() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 6eeb968..f64fc81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -61,7 +61,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.plugins.FalsingManager;
@@ -290,7 +289,11 @@
     private boolean mIsFullWidth;
     private boolean mBlockingExpansionForCurrentTouch;
 
-    private boolean mExpectingOpenPanelGesture;
+    /**
+     * Following variables maintain state of events when input focus transfer may occur.
+     */
+    private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
+    private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
 
     /**
      * Current dark amount that follows regular interpolation curve of animation.
@@ -377,11 +380,12 @@
             NotificationWakeUpCoordinator coordinator,
             PulseExpansionHandler pulseExpansionHandler,
             DynamicPrivacyController dynamicPrivacyController,
-            KeyguardBypassController bypassController) {
+            KeyguardBypassController bypassController,
+            FalsingManager falsingManager) {
         super(context, attrs);
         setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
+        mFalsingManager = falsingManager;
         mPowerManager = context.getSystemService(PowerManager.class);
         mWakeUpCoordinator = coordinator;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -606,7 +610,7 @@
     }
 
     private void initBottomArea() {
-        mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext());
+        mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext(), mFalsingManager);
         mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
         mKeyguardBottomArea.setStatusBar(mStatusBar);
         mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
@@ -1059,6 +1063,15 @@
             mDownY = event.getY();
             mCollapsedOnDown = isFullyCollapsed();
             mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
+            if (mExpectingSynthesizedDown) {
+                mLastEventSynthesizedDown = true;
+            } else {
+                // down but not synthesized motion event.
+                mLastEventSynthesizedDown = false;
+            }
+        } else {
+            // not down event at all.
+            mLastEventSynthesizedDown = false;
         }
     }
 
@@ -1124,12 +1137,18 @@
             return false;
         }
 
-        initDownStates(event);
         // Make sure the next touch won't the blocked after the current ends.
         if (event.getAction() == MotionEvent.ACTION_UP
                 || event.getAction() == MotionEvent.ACTION_CANCEL) {
             mBlockingExpansionForCurrentTouch = false;
         }
+        // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
+        // without any ACTION_MOVE event.
+        // In such case, simply expand the panel instead of being stuck at the bottom bar.
+        if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
+            expand(true /* animate */);
+        }
+        initDownStates(event);
         if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
                 && mPulseExpansionHandler.onTouchEvent(event)) {
             // We're expanding all the other ones shouldn't get this anymore
@@ -1255,17 +1274,28 @@
         if (!isFullyCollapsed()) {
             return;
         }
-        mExpectingOpenPanelGesture = true;
+        mExpectingSynthesizedDown = true;
         onTrackingStarted();
     }
 
     /**
-     * Input focus transfer has already happened as this view decided to intercept
-     * very first down event.
+     * Called when this view is no longer waiting for input focus transfer.
+     *
+     * There are two scenarios behind this function call. First, input focus transfer
+     * has successfully happened and this view already received synthetic DOWN event.
+     * (mExpectingSynthesizedDown == false). Do nothing.
+     *
+     * Second, before input focus transfer finished, user may have lifted finger
+     * in previous window and this window never received synthetic DOWN event.
+     * (mExpectingSynthesizedDown == true).
+     * In this case, we use the velocity to trigger fling event.
+     *
+     * @param velocity unit is in px / millis
      */
-    public void stopWaitingForOpenPanelGesture() {
-        if (mExpectingOpenPanelGesture) {
-            mExpectingOpenPanelGesture = false;
+    public void stopWaitingForOpenPanelGesture(float velocity) {
+        if (mExpectingSynthesizedDown) {
+            mExpectingSynthesizedDown = false;
+            fling(velocity > 1f ? 1000f * velocity : 0, true /* animate */);
             onTrackingStopped(false);
         }
     }
@@ -1283,8 +1313,8 @@
 
     @Override
     protected boolean shouldGestureWaitForTouchSlop() {
-        if (mExpectingOpenPanelGesture) {
-            mExpectingOpenPanelGesture = false;
+        if (mExpectingSynthesizedDown) {
+            mExpectingSynthesizedDown = false;
             return false;
         }
         return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index a5b221b..853faab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -42,7 +42,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -110,7 +109,7 @@
     private FlingAnimationUtils mFlingAnimationUtils;
     private FlingAnimationUtils mFlingAnimationUtilsClosing;
     private FlingAnimationUtils mFlingAnimationUtilsDismissing;
-    private FalsingManager mFalsingManager;
+    private final FalsingManager mFalsingManager;
     private final VibratorHelper mVibratorHelper;
 
     /**
@@ -213,7 +212,7 @@
                 0.5f /* maxLengthSeconds */, 0.2f /* speedUpFactor */, 0.6f /* x2 */,
                 0.84f /* y2 */);
         mBounceInterpolator = new BounceInterpolator();
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
+        mFalsingManager = Dependency.get(FalsingManager.class);  // TODO: inject into a controller.
         mNotificationsDragEnabled =
                 getResources().getBoolean(R.bool.config_enableNotificationShadeDrag);
         mVibratorHelper = Dependency.get(VibratorHelper.class);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ad13b28..0970f2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -80,7 +80,6 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -148,7 +147,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingLog;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
@@ -202,6 +200,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationListController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
@@ -695,6 +694,7 @@
         mRecents = getComponent(Recents.class);
 
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mFalsingManager = Dependency.get(FalsingManager.class);
 
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
@@ -770,7 +770,6 @@
         putComponent(DozeHost.class, mDozeServiceHost);
 
         mScreenPinningRequest = new ScreenPinningRequest(mContext);
-        mFalsingManager = FalsingManagerFactory.getInstance(mContext);
 
         Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
 
@@ -1232,7 +1231,8 @@
                 new Handler(), mKeyguardUpdateMonitor, mKeyguardBypassController);
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                 getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
-                mStatusBarWindow.findViewById(R.id.lock_icon_container));
+                mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
+                mKeyguardBypassController, mFalsingManager);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1930,7 +1930,7 @@
     /**
      * Called when another window is about to transfer it's input focus.
      */
-    public void onInputFocusTransfer(boolean start) {
+    public void onInputFocusTransfer(boolean start, float velocity) {
         if (!mCommandQueue.panelsEnabled()) {
             return;
         }
@@ -1938,7 +1938,7 @@
         if (start) {
             mNotificationPanel.startWaitingForOpenPanelGesture();
         } else {
-            mNotificationPanel.stopWaitingForOpenPanelGesture();
+            mNotificationPanel.stopWaitingForOpenPanelGesture(velocity);
         }
     }
 
@@ -2389,7 +2389,7 @@
             mKeyguardUpdateMonitor.dump(fd, pw, args);
         }
 
-        FalsingManagerFactory.getInstance(mContext).dump(pw);
+        Dependency.get(FalsingManager.class).dump(pw);
         FalsingLog.dump(pw);
 
         pw.println("SharedPreferences:");
@@ -3169,6 +3169,7 @@
         mNotificationPanel.onAffordanceLaunchEnded();
         mNotificationPanel.animate().cancel();
         mNotificationPanel.setAlpha(1f);
+        ViewGroupFadeHelper.reset(mNotificationPanel);
         updateScrimController();
         Trace.endSection();
         return staying;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b88f57c..d678182 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_FADING;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
 
@@ -43,6 +44,7 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
@@ -51,6 +53,7 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -82,6 +85,7 @@
     // make everything a bit slower to bridge a gap until the user is unlocked and home screen has
     // dranw its first frame.
     private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
+    private static final long BYPASS_PANEL_FADE_DURATION = 67;
 
     private static String TAG = "StatusBarKeyguardViewManager";
 
@@ -132,6 +136,7 @@
 
     private ViewGroup mContainer;
     private ViewGroup mLockIconContainer;
+    private View mNotificationContainer;
 
     protected KeyguardBouncer mBouncer;
     protected boolean mShowing;
@@ -165,9 +170,10 @@
             (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class);
     private final NotificationMediaManager mMediaManager =
             Dependency.get(NotificationMediaManager.class);
-    private final StatusBarStateController mStatusBarStateController =
-            Dependency.get(StatusBarStateController.class);
+    private final SysuiStatusBarStateController mStatusBarStateController =
+            (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
     private final DockManager mDockManager;
+    private KeyguardBypassController mBypassController;
 
     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -205,7 +211,8 @@
             NotificationPanelView notificationPanelView,
             BiometricUnlockController biometricUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry,
-            ViewGroup lockIconContainer) {
+            ViewGroup lockIconContainer, View notificationContainer,
+            KeyguardBypassController bypassController, FalsingManager falsingManager) {
         mStatusBar = statusBar;
         mContainer = container;
         mLockIconContainer = lockIconContainer;
@@ -215,9 +222,11 @@
         mBiometricUnlockController = biometricUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
-                mExpansionCallback);
+                mExpansionCallback, falsingManager);
         mNotificationPanelView = notificationPanelView;
         notificationPanelView.setExpansionListener(this);
+        mBypassController = bypassController;
+        mNotificationContainer = notificationContainer;
     }
 
     @Override
@@ -555,12 +564,32 @@
             mBiometricUnlockController.startKeyguardFadingAway();
             hideBouncer(true /* destroyView */);
             if (wakeUnlockPulsing) {
-                mStatusBar.fadeKeyguardWhilePulsing();
+                if (needsBypassFading()) {
+                    ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
+                            mNotificationContainer,
+                            BYPASS_PANEL_FADE_DURATION,
+                                    () -> {
+                        mStatusBar.hideKeyguard();
+                        onKeyguardFadedAway();
+                    });
+                } else {
+                    mStatusBar.fadeKeyguardWhilePulsing();
+                }
                 wakeAndUnlockDejank();
             } else {
-                boolean staying = mStatusBar.hideKeyguard();
+                boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide();
                 if (!staying) {
                     mStatusBarWindowController.setKeyguardFadingAway(true);
+                    if (needsBypassFading()) {
+                        ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
+                                mNotificationContainer,
+                                BYPASS_PANEL_FADE_DURATION,
+                                () -> {
+                                    mStatusBar.hideKeyguard();
+                                });
+                    } else {
+                        mStatusBar.hideKeyguard();
+                    }
                     // hide() will happen asynchronously and might arrive after the scrims
                     // were already hidden, this means that the transition callback won't
                     // be triggered anymore and StatusBarWindowController will be forever in
@@ -568,6 +597,7 @@
                     mStatusBar.updateScrimController();
                     wakeAndUnlockDejank();
                 } else {
+                    mStatusBar.hideKeyguard();
                     mStatusBar.finishKeyguardFadingAway();
                     mBiometricUnlockController.finishKeyguardFadingAway();
                 }
@@ -580,6 +610,13 @@
             StatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
     }
 
+    private boolean needsBypassFading() {
+        return (mBiometricUnlockController.getMode() == MODE_UNLOCK_FADING
+                || mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING
+                || mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK)
+                && mBypassController.getBypassEnabled();
+    }
+
     @Override
     public void onDensityOrFontScaleChanged() {
         hideBouncer(true /* destroyView */);
@@ -602,6 +639,7 @@
     public void onKeyguardFadedAway() {
         mContainer.postDelayed(() -> mStatusBarWindowController.setKeyguardFadingAway(false),
                 100);
+        ViewGroupFadeHelper.reset(mNotificationPanelView);
         mStatusBar.finishKeyguardFadingAway();
         mBiometricUnlockController.finishKeyguardFadingAway();
         WindowManagerGlobal.getInstance().trimMemory(
@@ -822,8 +860,7 @@
      * @return Whether subtle animation should be used for unlocking the device.
      */
     public boolean shouldSubtleWindowAnimationsForUnlock() {
-        return mStatusBar.mKeyguardBypassController.getBypassEnabled()
-                && mStatusBar.mState == StatusBarState.KEYGUARD && !mBouncer.isAnimatingAway();
+        return needsBypassFading();
     }
 
     public boolean isGoingToNotificationShade() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 9405418..f1049f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -62,7 +62,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManagerFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.DragDownHelper;
@@ -156,7 +155,7 @@
         setMotionEventSplittingEnabled(false);
         mTransparentSrcPaint.setColor(0);
         mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
-        mFalsingManager = FalsingManagerFactory.getInstance(context);
+        mFalsingManager = Dependency.get(FalsingManager.class);  // TODO: inject into a controller.
         mGestureDetector = new GestureDetector(context, mGestureListener);
         mStatusBarStateController = Dependency.get(StatusBarStateController.class);
         Dependency.get(TunerService.class).addTunable(mTunable,
@@ -290,7 +289,7 @@
         ExpandHelper.Callback expandHelperCallback = stackScrollLayout.getExpandHelperCallback();
         DragDownHelper.DragDownCallback dragDownCallback = stackScrollLayout.getDragDownCallback();
         setDragDownHelper(new DragDownHelper(getContext(), this, expandHelperCallback,
-                dragDownCallback));
+                dragDownCallback, mFalsingManager));
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index ede3004..9b264c4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -26,7 +26,7 @@
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardSliceView;
-import com.android.systemui.SystemUIFactory;
+import com.android.systemui.SystemUIRootComponent;
 import com.android.systemui.qs.QSCarrierGroup;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
@@ -62,7 +62,7 @@
     private final LayoutInflater.Factory2 mFactory = new InjectionFactory();
 
     @Inject
-    public InjectionInflationController(SystemUIFactory.SystemUIRootComponent rootComponent) {
+    public InjectionInflationController(SystemUIRootComponent rootComponent) {
         mViewCreator = rootComponent.createViewCreator();
         initInjectionMap();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 92173cc..c1911ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -53,6 +53,7 @@
 import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -79,7 +80,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -164,7 +164,8 @@
                 mock(ActivityStarterDelegate.class),
                 mock(StatusBarStateController.class),
                 mHeadsUpManager,
-                mKeyguardBypassController);
+                mKeyguardBypassController,
+                new FalsingManagerFake());
         mStackScroller = spy(mStackScrollerInternal);
         mStackScroller.setShelf(notificationShelf);
         mStackScroller.setStatusBar(mBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index b24c3dd..06a2eec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -40,6 +40,7 @@
 
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -76,7 +77,8 @@
     public void setUp() throws Exception {
         mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
         mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
-        mSwipeHelper = spy(new NotificationSwipeHelper(SwipeHelper.X, mCallback, mContext, mListener));
+        mSwipeHelper = spy(new NotificationSwipeHelper(
+                SwipeHelper.X, mCallback, mContext, mListener, new FalsingManagerFake()));
         mView = mock(View.class);
         mEvent = mock(MotionEvent.class);
         mMenuRow = mock(NotificationMenuRowPlugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index d14b460..a96efd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -98,6 +99,8 @@
     private PanelBar mPanelBar;
     @Mock
     private KeyguardAffordanceHelper mAffordanceHelper;
+    @Mock
+    private FalsingManager mFalsingManager;
     private NotificationPanelView mNotificationPanelView;
 
     @Before
@@ -191,7 +194,8 @@
                     new InjectionInflationController(
                             SystemUIFactory.getInstance().getRootComponent()),
                     coordinator, expansionHandler, mock(DynamicPrivacyController.class),
-                    bypassController);
+                    bypassController,
+                    mFalsingManager);
             mNotificationStackScroller = mNotificationStackScrollLayout;
             mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
             mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index b6b758e..63f653b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 
@@ -38,9 +39,12 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -72,7 +76,11 @@
     @Mock
     private ViewGroup mLockIconContainer;
     @Mock
-    private StatusBarStateController mStatusBarStateController;
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private View mNotificationContainer;
+    @Mock
+    private KeyguardBypassController mBypassController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Before
@@ -87,7 +95,8 @@
                 mViewMediatorCallback, mLockPatternUtils);
         mStatusBarKeyguardViewManager.registerStatusBar(mStatusBar, mContainer,
                 mNotificationPanelView, mBiometrucUnlockController, mDismissCallbackRegistry,
-                mLockIconContainer);
+                mLockIconContainer, mNotificationContainer, mBypassController,
+                new FalsingManagerFake());
         mStatusBarKeyguardViewManager.show(null);
     }
 
@@ -225,10 +234,12 @@
                 NotificationPanelView notificationPanelView,
                 BiometricUnlockController fingerprintUnlockController,
                 DismissCallbackRegistry dismissCallbackRegistry,
-                ViewGroup lockIconContainer) {
+                ViewGroup lockIconContainer, View notificationContainer,
+                KeyguardBypassController bypassController, FalsingManager falsingManager) {
             super.registerStatusBar(statusBar, container, notificationPanelView,
-                    fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer);
+                    fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer,
+                    notificationContainer, bypassController, falsingManager);
             mBouncer = StatusBarKeyguardViewManagerTest.this.mBouncer;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/overlays/AccentColorBlackOverlay/Android.mk b/packages/overlays/AccentColorBlackOverlay/Android.mk
index a689def..86d873dc 100644
--- a/packages/overlays/AccentColorBlackOverlay/Android.mk
+++ b/packages/overlays/AccentColorBlackOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := AccentColorBlackOverlay
diff --git a/packages/overlays/AccentColorCinnamonOverlay/Android.mk b/packages/overlays/AccentColorCinnamonOverlay/Android.mk
index 3a6cbe3..a8d3f10 100644
--- a/packages/overlays/AccentColorCinnamonOverlay/Android.mk
+++ b/packages/overlays/AccentColorCinnamonOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := AccentColorCinnamonOverlay
diff --git a/packages/overlays/AccentColorGreenOverlay/Android.mk b/packages/overlays/AccentColorGreenOverlay/Android.mk
index d96dbe1..c3aa6a8 100644
--- a/packages/overlays/AccentColorGreenOverlay/Android.mk
+++ b/packages/overlays/AccentColorGreenOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := AccentColorGreenOverlay
diff --git a/packages/overlays/AccentColorOceanOverlay/Android.mk b/packages/overlays/AccentColorOceanOverlay/Android.mk
index cf0c6b3..96fbee4 100644
--- a/packages/overlays/AccentColorOceanOverlay/Android.mk
+++ b/packages/overlays/AccentColorOceanOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := AccentColorOceanOverlay
diff --git a/packages/overlays/AccentColorOrchidOverlay/Android.mk b/packages/overlays/AccentColorOrchidOverlay/Android.mk
index fc55bef..352e36b 100644
--- a/packages/overlays/AccentColorOrchidOverlay/Android.mk
+++ b/packages/overlays/AccentColorOrchidOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := AccentColorOrchidOverlay
diff --git a/packages/overlays/AccentColorPurpleOverlay/Android.mk b/packages/overlays/AccentColorPurpleOverlay/Android.mk
index 3a28efa..29d5fc9 100644
--- a/packages/overlays/AccentColorPurpleOverlay/Android.mk
+++ b/packages/overlays/AccentColorPurpleOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := AccentColorPurpleOverlay
diff --git a/packages/overlays/AccentColorSpaceOverlay/Android.mk b/packages/overlays/AccentColorSpaceOverlay/Android.mk
index 78cbf73..cbddf63 100644
--- a/packages/overlays/AccentColorSpaceOverlay/Android.mk
+++ b/packages/overlays/AccentColorSpaceOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := AccentColorSpaceOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
index b73aea3..c3e8642 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/Android.mk
@@ -6,8 +6,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationCornerOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
index 8ca2dad..09d158d 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/Android.mk
@@ -6,8 +6,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationDoubleOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
index 7458cb5..6a1c09c 100644
--- a/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationNarrowOverlay/Android.mk
@@ -6,8 +6,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationNarrowOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
index 1a405e2..cbceff0 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/Android.mk
@@ -6,8 +6,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationTallOverlay
diff --git a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
index 3ebc540..82a076a 100644
--- a/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
+++ b/packages/overlays/DisplayCutoutEmulationWideOverlay/Android.mk
@@ -6,8 +6,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := DisplayCutoutEmulationWideOverlay
diff --git a/packages/overlays/FontNotoSerifSourceOverlay/Android.mk b/packages/overlays/FontNotoSerifSourceOverlay/Android.mk
index f4eedaf..16a0173 100644
--- a/packages/overlays/FontNotoSerifSourceOverlay/Android.mk
+++ b/packages/overlays/FontNotoSerifSourceOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := FontNotoSerifSourceOverlay
diff --git a/packages/overlays/IconPackCircularAndroidOverlay/Android.mk b/packages/overlays/IconPackCircularAndroidOverlay/Android.mk
index 8f3baa5..d96185f 100644
--- a/packages/overlays/IconPackCircularAndroidOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularAndroidOverlay/Android.mk
@@ -20,8 +20,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackCircularAndroidOverlay
diff --git a/packages/overlays/IconPackCircularLauncherOverlay/Android.mk b/packages/overlays/IconPackCircularLauncherOverlay/Android.mk
index 310bdef..d736d7d 100644
--- a/packages/overlays/IconPackCircularLauncherOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularLauncherOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackCircularLauncherOverlay
diff --git a/packages/overlays/IconPackCircularSettingsOverlay/Android.mk b/packages/overlays/IconPackCircularSettingsOverlay/Android.mk
index d067322..ea2da30 100644
--- a/packages/overlays/IconPackCircularSettingsOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularSettingsOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackCircularSettingsOverlay
diff --git a/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk b/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk
index 5e0dcbe..9045e8e 100644
--- a/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularSystemUIOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackCircularSystemUIOverlay
diff --git a/packages/overlays/IconPackCircularThemePickerOverlay/Android.mk b/packages/overlays/IconPackCircularThemePickerOverlay/Android.mk
index 412c26f..c2d472d 100644
--- a/packages/overlays/IconPackCircularThemePickerOverlay/Android.mk
+++ b/packages/overlays/IconPackCircularThemePickerOverlay/Android.mk
@@ -21,8 +21,6 @@
 LOCAL_CERTIFICATE := platform
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackCircularThemePickerOverlay
diff --git a/packages/overlays/IconPackFilledAndroidOverlay/Android.mk b/packages/overlays/IconPackFilledAndroidOverlay/Android.mk
index 3036f7d..78db765 100644
--- a/packages/overlays/IconPackFilledAndroidOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledAndroidOverlay/Android.mk
@@ -20,8 +20,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackFilledAndroidOverlay
diff --git a/packages/overlays/IconPackFilledLauncherOverlay/Android.mk b/packages/overlays/IconPackFilledLauncherOverlay/Android.mk
index 2460fa4..16b8f52 100644
--- a/packages/overlays/IconPackFilledLauncherOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledLauncherOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackFilledLauncherOverlay
diff --git a/packages/overlays/IconPackFilledSettingsOverlay/Android.mk b/packages/overlays/IconPackFilledSettingsOverlay/Android.mk
index 3cc071d..d4e9000 100644
--- a/packages/overlays/IconPackFilledSettingsOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledSettingsOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackFilledSettingsOverlay
diff --git a/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk b/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk
index f027692..35e157a 100644
--- a/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledSystemUIOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackFilledSystemUIOverlay
diff --git a/packages/overlays/IconPackFilledThemePickerOverlay/Android.mk b/packages/overlays/IconPackFilledThemePickerOverlay/Android.mk
index 6d15603..835d35e 100644
--- a/packages/overlays/IconPackFilledThemePickerOverlay/Android.mk
+++ b/packages/overlays/IconPackFilledThemePickerOverlay/Android.mk
@@ -21,8 +21,6 @@
 LOCAL_CERTIFICATE := platform
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackFilledThemePickerOverlay
diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk b/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk
index c6ad4ac..70d6fc4 100644
--- a/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedAndroidOverlay/Android.mk
@@ -20,8 +20,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackRoundedAndroidOverlay
diff --git a/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk b/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk
index 713e281..63de27f 100644
--- a/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedLauncherOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackRoundedLauncherOverlay
diff --git a/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk b/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk
index 6c77519..c59bf7d 100644
--- a/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedSettingsOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackRoundedSettingsOverlay
diff --git a/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk b/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk
index 4e21b41..3b68c92 100644
--- a/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedSystemUIOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackRoundedSystemUIOverlay
diff --git a/packages/overlays/IconPackRoundedThemePickerOverlay/Android.mk b/packages/overlays/IconPackRoundedThemePickerOverlay/Android.mk
index ae48186..e31aa4a 100644
--- a/packages/overlays/IconPackRoundedThemePickerOverlay/Android.mk
+++ b/packages/overlays/IconPackRoundedThemePickerOverlay/Android.mk
@@ -21,8 +21,6 @@
 LOCAL_CERTIFICATE := platform
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconPackRoundedThemePickerOverlay
diff --git a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk
index 21cd011..c6f00d1 100644
--- a/packages/overlays/IconShapeRoundedRectOverlay/Android.mk
+++ b/packages/overlays/IconShapeRoundedRectOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconShapeRoundedRectOverlay
diff --git a/packages/overlays/IconShapeSquareOverlay/Android.mk b/packages/overlays/IconShapeSquareOverlay/Android.mk
index c872883..6020721 100644
--- a/packages/overlays/IconShapeSquareOverlay/Android.mk
+++ b/packages/overlays/IconShapeSquareOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconShapeSquareOverlay
diff --git a/packages/overlays/IconShapeSquircleOverlay/Android.mk b/packages/overlays/IconShapeSquircleOverlay/Android.mk
index fa5fe69..04409a5 100644
--- a/packages/overlays/IconShapeSquircleOverlay/Android.mk
+++ b/packages/overlays/IconShapeSquircleOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconShapeSquircleOverlay
diff --git a/packages/overlays/IconShapeTeardropOverlay/Android.mk b/packages/overlays/IconShapeTeardropOverlay/Android.mk
index d5f01f3..b127dea 100644
--- a/packages/overlays/IconShapeTeardropOverlay/Android.mk
+++ b/packages/overlays/IconShapeTeardropOverlay/Android.mk
@@ -21,8 +21,6 @@
 
 LOCAL_PRODUCT_MODULE := true
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := IconShapeTeardropOverlay
diff --git a/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk b/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk
index be86ef2..30477cc 100644
--- a/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk
+++ b/packages/overlays/NavigationBarMode2ButtonOverlay/Android.mk
@@ -20,8 +20,6 @@
 LOCAL_RRO_THEME := NavigationBarMode2Button
 
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := NavigationBarMode2ButtonOverlay
diff --git a/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk b/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk
index f44a362..3d5a5a5 100644
--- a/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk
+++ b/packages/overlays/NavigationBarMode3ButtonOverlay/Android.mk
@@ -20,8 +20,6 @@
 LOCAL_RRO_THEME := NavigationBarMode3Button
 
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := NavigationBarMode3ButtonOverlay
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk
index 02e2074..3b7605a 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/Android.mk
@@ -20,8 +20,6 @@
 LOCAL_RRO_THEME := NavigationBarModeGestural
 
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlay
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk
index 9a38efa..1a1388e 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.mk
@@ -20,8 +20,6 @@
 LOCAL_RRO_THEME := NavigationBarModeGesturalExtraWideBack
 
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayExtraWideBack
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk
index 1d004c8..8689986 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.mk
@@ -20,8 +20,6 @@
 LOCAL_RRO_THEME := NavigationBarModeGesturalNarrowBack
 
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayNarrowBack
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk
index 0ab463f..2723add 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.mk
@@ -20,8 +20,6 @@
 LOCAL_RRO_THEME := NavigationBarModeGesturalWideBack
 
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := NavigationBarModeGesturalOverlayWideBack
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 303230b..b6cbbac 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -385,9 +385,10 @@
 
         for (int i = displaysList.size() - 1; i >= 0; i--) {
             final int displayId = displaysList.get(i).getDisplayId();
+            final Context displayContext = mContext.createDisplayContext(displaysList.get(i));
 
             if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
-                TouchExplorer explorer = new TouchExplorer(mContext, mAms);
+                TouchExplorer explorer = new TouchExplorer(displayContext, mAms);
                 addFirstEventHandler(displayId, explorer);
                 mTouchExplorer.put(displayId, explorer);
             }
@@ -400,7 +401,7 @@
                 final boolean triggerable = (mEnabledFeatures
                         & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
                 MagnificationGestureHandler magnificationGestureHandler =
-                        new MagnificationGestureHandler(mContext,
+                        new MagnificationGestureHandler(displayContext,
                                 mAms.getMagnificationController(),
                                 detectControlGestures, triggerable, displayId);
                 addFirstEventHandler(displayId, magnificationGestureHandler);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 44f19ff..5a9320f 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -187,10 +187,9 @@
                 if (err instanceof TimeoutException) {
                     dispatchCancellationSignal(cancellationSink.get());
                     mCallbacks.onFillRequestTimeout(request.getId());
+                } else if (err instanceof CancellationException) {
+                    dispatchCancellationSignal(cancellationSink.get());
                 } else {
-                    if (err instanceof CancellationException) {
-                        dispatchCancellationSignal(cancellationSink.get());
-                    }
                     mCallbacks.onFillRequestFailure(request.getId(), err.getMessage());
                 }
             }
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java b/services/backup/java/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
new file mode 100644
index 0000000..ae2e150
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.tasks.DecryptedChunkOutput;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/** Writes plaintext chunks to a file, building a digest of the plaintext of the resulting file. */
+public class DecryptedChunkFileOutput implements DecryptedChunkOutput {
+    @VisibleForTesting static final String DIGEST_ALGORITHM = "SHA-256";
+
+    private final File mOutputFile;
+    private final MessageDigest mMessageDigest;
+    @Nullable private FileOutputStream mFileOutputStream;
+    private boolean mClosed;
+    @Nullable private byte[] mDigest;
+
+    /**
+     * Constructs a new instance which writes chunks to the given file and uses the default message
+     * digest algorithm.
+     */
+    public DecryptedChunkFileOutput(File outputFile) {
+        mOutputFile = outputFile;
+        try {
+            mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            throw new AssertionError(
+                    "Impossible condition: JCE thinks it does not support AES.", e);
+        }
+    }
+
+    @Override
+    public DecryptedChunkOutput open() throws IOException {
+        checkState(mFileOutputStream == null, "Cannot open twice");
+        mFileOutputStream = new FileOutputStream(mOutputFile);
+        return this;
+    }
+
+    @Override
+    public void processChunk(byte[] plaintextBuffer, int length) throws IOException {
+        checkState(mFileOutputStream != null, "Must open before processing chunks");
+        mFileOutputStream.write(plaintextBuffer, /*off=*/ 0, length);
+        mMessageDigest.update(plaintextBuffer, /*offset=*/ 0, length);
+    }
+
+    @Override
+    public byte[] getDigest() {
+        checkState(mClosed, "Must close before getting mDigest");
+
+        // After the first call to mDigest() the MessageDigest is reset, thus we must store the
+        // result.
+        if (mDigest == null) {
+            mDigest = mMessageDigest.digest();
+        }
+        return mDigest;
+    }
+
+    @Override
+    public void close() throws IOException {
+        mFileOutputStream.close();
+        mClosed = true;
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java b/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
index ebf09df..a425c72 100644
--- a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
+++ b/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
@@ -35,7 +35,7 @@
             mKeyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
             mKeyGenerator.init(KEY_SIZE_BITS, secureRandom);
         } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException(
+            throw new AssertionError(
                     "Impossible condition: JCE thinks it does not support AES.", e);
         }
     }
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/BackupEncrypter.java b/services/backup/java/com/android/server/backup/encryption/tasks/BackupEncrypter.java
new file mode 100644
index 0000000..95d0d97
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/tasks/BackupEncrypter.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static java.util.Collections.unmodifiableList;
+
+import android.annotation.Nullable;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.crypto.SecretKey;
+
+/** Task which reads data from some source, splits it into chunks and encrypts new chunks. */
+public interface BackupEncrypter {
+    /** The algorithm which we use to compute the digest of the backup file plaintext. */
+    String MESSAGE_DIGEST_ALGORITHM = "SHA-256";
+
+    /**
+     * Splits the backup input into encrypted chunks and encrypts new chunks.
+     *
+     * @param secretKey Key used to encrypt backup.
+     * @param fingerprintMixerSalt Fingerprint mixer salt used for content-defined chunking during a
+     *     full backup. Should be {@code null} for a key-value backup.
+     * @param existingChunks Set of the SHA-256 Macs of chunks the server already has.
+     * @return a result containing an array of new encrypted chunks to upload, and an ordered
+     *     listing of the chunks in the backup file.
+     * @throws IOException if a problem occurs reading from the backup data.
+     * @throws GeneralSecurityException if there is a problem encrypting the data.
+     */
+    Result backup(
+            SecretKey secretKey,
+            @Nullable byte[] fingerprintMixerSalt,
+            Set<ChunkHash> existingChunks)
+            throws IOException, GeneralSecurityException;
+
+    /**
+     * The result of an incremental backup. Contains new encrypted chunks to upload, and an ordered
+     * list of the chunks in the backup file.
+     */
+    class Result {
+        private final List<ChunkHash> mAllChunks;
+        private final List<EncryptedChunk> mNewChunks;
+        private final byte[] mDigest;
+
+        public Result(List<ChunkHash> allChunks, List<EncryptedChunk> newChunks, byte[] digest) {
+            mAllChunks = unmodifiableList(new ArrayList<>(allChunks));
+            mDigest = digest;
+            mNewChunks = unmodifiableList(new ArrayList<>(newChunks));
+        }
+
+        /**
+         * Returns an unmodifiable list of the hashes of all the chunks in the backup, in the order
+         * they appear in the plaintext.
+         */
+        public List<ChunkHash> getAllChunks() {
+            return mAllChunks;
+        }
+
+        /** Returns an unmodifiable list of the new chunks in the backup. */
+        public List<EncryptedChunk> getNewChunks() {
+            return mNewChunks;
+        }
+
+        /** Returns the message digest of the backup. */
+        public byte[] getDigest() {
+            return mDigest;
+        }
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java b/services/backup/java/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
new file mode 100644
index 0000000..45798d3
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import android.util.Slog;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkEncryptor;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+import com.android.server.backup.encryption.chunking.cdc.ContentDefinedChunker;
+import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
+import com.android.server.backup.encryption.chunking.cdc.IsChunkBreakpoint;
+import com.android.server.backup.encryption.chunking.cdc.RabinFingerprint64;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Splits backup data into variable-sized chunks using content-defined chunking, then encrypts the
+ * chunks. Given a hash of the SHA-256s of existing chunks, performs an incremental backup (i.e.,
+ * only encrypts new chunks).
+ */
+public class BackupStreamEncrypter implements BackupEncrypter {
+    private static final String TAG = "BackupStreamEncryptor";
+
+    private final InputStream mData;
+    private final int mMinChunkSizeBytes;
+    private final int mMaxChunkSizeBytes;
+    private final int mAverageChunkSizeBytes;
+
+    /**
+     * A new instance over the given distribution of chunk sizes.
+     *
+     * @param data The data to be backed up.
+     * @param minChunkSizeBytes The minimum chunk size. No chunk will be smaller than this.
+     * @param maxChunkSizeBytes The maximum chunk size. No chunk will be larger than this.
+     * @param averageChunkSizeBytes The average chunk size. The mean size of chunks will be roughly
+     *     this (with a few tens of bytes of overhead for the initialization vector and message
+     *     authentication code).
+     */
+    public BackupStreamEncrypter(
+            InputStream data,
+            int minChunkSizeBytes,
+            int maxChunkSizeBytes,
+            int averageChunkSizeBytes) {
+        this.mData = data;
+        this.mMinChunkSizeBytes = minChunkSizeBytes;
+        this.mMaxChunkSizeBytes = maxChunkSizeBytes;
+        this.mAverageChunkSizeBytes = averageChunkSizeBytes;
+    }
+
+    @Override
+    public Result backup(
+            SecretKey secretKey, byte[] fingerprintMixerSalt, Set<ChunkHash> existingChunks)
+            throws IOException, GeneralSecurityException {
+        MessageDigest messageDigest =
+                MessageDigest.getInstance(BackupEncrypter.MESSAGE_DIGEST_ALGORITHM);
+        RabinFingerprint64 rabinFingerprint64 = new RabinFingerprint64();
+        FingerprintMixer fingerprintMixer = new FingerprintMixer(secretKey, fingerprintMixerSalt);
+        IsChunkBreakpoint isChunkBreakpoint =
+                new IsChunkBreakpoint(mAverageChunkSizeBytes - mMinChunkSizeBytes);
+        ContentDefinedChunker chunker =
+                new ContentDefinedChunker(
+                        mMinChunkSizeBytes,
+                        mMaxChunkSizeBytes,
+                        rabinFingerprint64,
+                        fingerprintMixer,
+                        isChunkBreakpoint);
+        ChunkHasher chunkHasher = new ChunkHasher(secretKey);
+        ChunkEncryptor encryptor = new ChunkEncryptor(secretKey, new SecureRandom());
+        Set<ChunkHash> includedChunks = new HashSet<>();
+        // New chunks will be added only once to this list, even if they occur multiple times.
+        List<EncryptedChunk> newChunks = new ArrayList<>();
+        // All chunks (including multiple occurrences) will be added to the chunkListing.
+        List<ChunkHash> chunkListing = new ArrayList<>();
+
+        includedChunks.addAll(existingChunks);
+
+        chunker.chunkify(
+                mData,
+                chunk -> {
+                    messageDigest.update(chunk);
+                    ChunkHash key = chunkHasher.computeHash(chunk);
+
+                    if (!includedChunks.contains(key)) {
+                        newChunks.add(encryptor.encrypt(key, chunk));
+                        includedChunks.add(key);
+                    }
+                    chunkListing.add(key);
+                });
+
+        Slog.i(
+                TAG,
+                String.format(
+                        "Chunks: %d total, %d unique, %d new",
+                        chunkListing.size(), new HashSet<>(chunkListing).size(), newChunks.size()));
+        return new Result(
+                Collections.unmodifiableList(chunkListing),
+                Collections.unmodifiableList(newChunks),
+                messageDigest.digest());
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java b/services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
new file mode 100644
index 0000000..e3df3c1
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+
+/**
+ * Accepts the plaintext bytes of decrypted chunks and writes them to some output. Also keeps track
+ * of the message digest of the chunks.
+ */
+public interface DecryptedChunkOutput extends Closeable {
+    /**
+     * Opens whatever output the implementation chooses, ready to process chunks.
+     *
+     * @return {@code this}, to allow use with try-with-resources
+     */
+    DecryptedChunkOutput open() throws IOException;
+
+    /**
+     * Writes the plaintext bytes of chunk to whatever output the implementation chooses. Also
+     * updates the digest with the chunk.
+     *
+     * <p>You must call {@link #open()} before this method, and you may not call it after calling
+     * {@link Closeable#close()}.
+     *
+     * @param plaintextBuffer An array containing the bytes of the plaintext of the chunk, starting
+     *     at index 0.
+     * @param length The length in bytes of the plaintext contained in {@code plaintextBuffer}.
+     */
+    void processChunk(byte[] plaintextBuffer, int length) throws IOException, InvalidKeyException;
+
+    /**
+     * Returns the message digest of all the chunks processed by {@link #processChunk}.
+     *
+     * <p>You must call {@link Closeable#close()} before calling this method.
+     */
+    byte[] getDigest();
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java b/services/backup/java/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
new file mode 100644
index 0000000..487c0d9
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+/** Wraps any exception related to encryption which occurs during restore. */
+public class EncryptedRestoreException extends Exception {
+    public EncryptedRestoreException(String message) {
+        super(message);
+    }
+
+    public EncryptedRestoreException(Throwable cause) {
+        super(cause);
+    }
+
+    public EncryptedRestoreException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 56eacc0..eba9e4a 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -88,7 +88,6 @@
     final PackageInfo mOnlyPackage;
 
     final boolean mAllowApks;
-    private final boolean mAllowObbs;
 
     // Which package are we currently handling data for?
     private String mAgentPackage;
@@ -113,9 +112,6 @@
     // Packages we've already wiped data on when restoring their first file
     private final HashSet<String> mClearedPackages = new HashSet<>();
 
-    // How much data have we moved?
-    private long mBytes;
-
     // Working buffer
     final byte[] mBuffer;
 
@@ -130,14 +126,14 @@
     final int mEphemeralOpToken;
 
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
-    final boolean mIsAdbRestore;
+    private final boolean mIsAdbRestore;
     @GuardedBy("mPipesLock")
     private boolean mPipesClosed;
 
     public FullRestoreEngine(UserBackupManagerService backupManagerService,
             BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
             IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
-            boolean allowObbs, int ephemeralOpToken, boolean isAdbRestore) {
+            int ephemeralOpToken, boolean isAdbRestore) {
         mBackupManagerService = backupManagerService;
         mEphemeralOpToken = ephemeralOpToken;
         mMonitorTask = monitorTask;
@@ -145,9 +141,7 @@
         mMonitor = monitor;
         mOnlyPackage = onlyPackage;
         mAllowApks = allowApks;
-        mAllowObbs = allowObbs;
         mBuffer = new byte[32 * 1024];
-        mBytes = 0;
         mAgentTimeoutParameters = Preconditions.checkNotNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
@@ -170,12 +164,7 @@
             return false;
         }
 
-        BytesReadListener bytesReadListener = new BytesReadListener() {
-            @Override
-            public void onBytesRead(long bytesRead) {
-                mBytes += bytesRead;
-            }
-        };
+        BytesReadListener bytesReadListener = bytesRead -> { };
 
         TarBackupReader tarBackupReader = new TarBackupReader(instream,
                 bytesReadListener, monitor);
@@ -378,9 +367,7 @@
                                             ? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
                                             : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
                             mAgentPackage = pkg;
-                        } catch (IOException e) {
-                            // fall through to error handling
-                        } catch (NameNotFoundException e) {
+                        } catch (IOException | NameNotFoundException e) {
                             // fall through to error handling
                         }
 
@@ -485,9 +472,6 @@
                                 int toRead = (toCopy > buffer.length)
                                         ? buffer.length : (int) toCopy;
                                 int nRead = instream.read(buffer, 0, toRead);
-                                if (nRead >= 0) {
-                                    mBytes += nRead;
-                                }
                                 if (nRead <= 0) {
                                     break;
                                 }
@@ -548,9 +532,6 @@
                             int toRead = (bytesToConsume > buffer.length)
                                     ? buffer.length : (int) bytesToConsume;
                             long nRead = instream.read(buffer, 0, toRead);
-                            if (nRead >= 0) {
-                                mBytes += nRead;
-                            }
                             if (nRead <= 0) {
                                 break;
                             }
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index c904256..ec2d545 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -23,21 +23,12 @@
 import static com.android.server.backup.BackupPasswordManager.PBKDF_FALLBACK;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_HEADER_MAGIC;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_FILE_VERSION;
-import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
-import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
-import android.app.IBackupAgent;
-import android.app.backup.BackupAgent;
 import android.app.backup.IFullBackupRestoreObserver;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.Signature;
-import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.FullBackupObbConnection;
 import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
@@ -50,8 +41,6 @@
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.zip.InflaterInputStream;
 
@@ -71,34 +60,9 @@
     private final String mCurrentPassword;
     private final String mDecryptPassword;
     private final AtomicBoolean mLatchObject;
-    private final BackupAgent mPackageManagerBackupAgent;
-    private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
+    private final FullBackupObbConnection mObbConnection;
 
     private IFullBackupRestoreObserver mObserver;
-    private IBackupAgent mAgent;
-    private String mAgentPackage;
-    private ApplicationInfo mTargetApp;
-    private FullBackupObbConnection mObbConnection = null;
-    private ParcelFileDescriptor[] mPipes = null;
-    private byte[] mWidgetData = null;
-    private long mAppVersion;
-
-    private long mBytes;
-    private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
-
-    // possible handling states for a given package in the restore dataset
-    private final HashMap<String, RestorePolicy> mPackagePolicies
-            = new HashMap<>();
-
-    // installer package names for each encountered app, derived from the manifests
-    private final HashMap<String, String> mPackageInstallers = new HashMap<>();
-
-    // Signatures for a given package found in its manifest file
-    private final HashMap<String, Signature[]> mManifestSignatures
-            = new HashMap<>();
-
-    // Packages we've already wiped data on when restoring their first file
-    private final HashSet<String> mClearedPackages = new HashSet<>();
 
     public PerformAdbRestoreTask(UserBackupManagerService backupManagerService,
             ParcelFileDescriptor fd, String curPassword, String decryptPassword,
@@ -109,19 +73,7 @@
         mDecryptPassword = decryptPassword;
         mObserver = observer;
         mLatchObject = latch;
-        mAgent = null;
-        mPackageManagerBackupAgent = backupManagerService.makeMetadataAgent();
-        mAgentPackage = null;
-        mTargetApp = null;
         mObbConnection = new FullBackupObbConnection(backupManagerService);
-        mAgentTimeoutParameters = Preconditions.checkNotNull(
-                backupManagerService.getAgentTimeoutParameters(),
-                "Timeout parameters cannot be null");
-
-        // Which packages we've already wiped data on.  We prepopulate this
-        // with a whitelist of packages known to be unclearable.
-        mClearedPackages.add("android");
-        mClearedPackages.add(SETTINGS_PACKAGE);
     }
 
     @Override
@@ -130,11 +82,6 @@
         mObbConnection.establish();
         mObserver = FullBackupRestoreObserverUtils.sendStartRestore(mObserver);
 
-        // Are we able to restore shared-storage data?
-        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-            mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT);
-        }
-
         FileInputStream rawInStream = null;
         try {
             if (!mBackupManagerService.backupPasswordMatches(mCurrentPassword)) {
@@ -144,8 +91,6 @@
                 return;
             }
 
-            mBytes = 0;
-
             rawInStream = new FileInputStream(mInputFile.getFileDescriptor());
 
             InputStream tarInputStream = parseBackupFileHeaderAndReturnTarStream(rawInStream,
@@ -157,7 +102,7 @@
             }
 
             FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService, null,
-                    mObserver, null, null, true, true/*unused*/, 0 /*unused*/, true);
+                    mObserver, null, null, true, 0 /*unused*/, true);
             FullRestoreEngineThread mEngineThread = new FullRestoreEngineThread(mEngine,
                     tarInputStream);
             mEngineThread.run();
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 6714b0a..a45f0c0 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -86,7 +86,7 @@
     private final TransportClient mTransportClient;
 
     // Where per-transport saved state goes
-    File mStateDir;
+    private File mStateDir;
 
     // Restore observer; may be null
     private IRestoreObserver mObserver;
@@ -153,10 +153,9 @@
     // Key/value: bookkeeping about staged data and files for agent access
     private File mBackupDataName;
     private File mStageName;
-    private File mSavedStateName;
     private File mNewStateName;
-    ParcelFileDescriptor mBackupData;
-    ParcelFileDescriptor mNewState;
+    private ParcelFileDescriptor mBackupData;
+    private ParcelFileDescriptor mNewState;
 
     private final int mEphemeralOpToken;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
@@ -666,7 +665,7 @@
     }
 
     // Guts of a key/value restore operation
-    void initiateOneRestore(PackageInfo app, long appVersionCode) {
+    private void initiateOneRestore(PackageInfo app, long appVersionCode) {
         final String packageName = app.packageName;
 
         if (DEBUG) {
@@ -677,7 +676,6 @@
         mBackupDataName = new File(backupManagerService.getDataDir(), packageName + ".restore");
         mStageName = new File(backupManagerService.getDataDir(), packageName + ".stage");
         mNewStateName = new File(mStateDir, packageName + ".new");
-        mSavedStateName = new File(mStateDir, packageName);
 
         // don't stage the 'android' package where the wallpaper data lives.  this is
         // an optimization: we know there's no widget data hosted/published by that
@@ -870,7 +868,7 @@
                     mCurrentPackage.packageName);
 
             mEngine = new FullRestoreEngine(backupManagerService, this, null,
-                    mMonitor, mCurrentPackage, false, false, mEphemeralOpToken, false);
+                    mMonitor, mCurrentPackage, false, mEphemeralOpToken, false);
             mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
 
             ParcelFileDescriptor eWriteEnd = mEnginePipes[1];
@@ -1160,7 +1158,6 @@
         // the following from a discard of the newly-written state to the
         // "correct" operation of renaming into the canonical state blob.
         mNewStateName.delete();                      // TODO: remove; see above comment
-        //mNewStateName.renameTo(mSavedStateName);   // TODO: replace with this
 
         // If this wasn't the PM pseudopackage, tear down the agent side
         if (mCurrentPackage.applicationInfo != null) {
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index b6fa157..c0f10a3 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -37,6 +37,7 @@
 import android.os.UserHandle;
 import android.service.carrier.CarrierMessagingService;
 import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 
@@ -331,7 +332,7 @@
         @Override
         public void sendMessage(int subId, String callingPkg, Uri contentUri,
                 String locationUrl, Bundle configOverrides, PendingIntent sentIntent)
-                        throws RemoteException {
+                throws RemoteException {
             Slog.d(TAG, "sendMessage() by " + callingPkg);
             mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
             if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
@@ -341,7 +342,8 @@
             }
             contentUri = adjustUriForUserAndGrantPermission(contentUri,
                     CarrierMessagingService.SERVICE_INTERFACE,
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                    subId);
             getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
                     configOverrides, sentIntent);
         }
@@ -360,7 +362,8 @@
             }
             contentUri = adjustUriForUserAndGrantPermission(contentUri,
                     CarrierMessagingService.SERVICE_INTERFACE,
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                    subId);
 
             getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri,
                     configOverrides, downloadedIntent);
@@ -388,7 +391,7 @@
         @Override
         public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
                 String messageId, long timestampSecs, boolean seen, boolean read)
-                        throws RemoteException {
+                throws RemoteException {
             if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
                     callingPkg) != AppOpsManager.MODE_ALLOWED) {
                 // Silently fail AppOps failure due to not being the default SMS app
@@ -496,12 +499,12 @@
          * even if the caller is not in the primary user.
          *
          * @param contentUri The Uri to adjust
-         * @param action The intent action used to find the associated carrier app
+         * @param action     The intent action used to find the associated carrier app
          * @param permission The permission to add
          * @return The adjusted Uri containing the calling userId.
          */
         private Uri adjustUriForUserAndGrantPermission(Uri contentUri, String action,
-                int permission) {
+                int permission, int subId) {
             final Intent grantIntent = new Intent();
             grantIntent.setData(contentUri);
             grantIntent.setFlags(permission);
@@ -521,9 +524,10 @@
                 // Grant permission for the carrier app.
                 Intent intent = new Intent(action);
                 TelephonyManager telephonyManager =
-                    (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-                List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent(
-                        intent);
+                        (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+                List<String> carrierPackages =
+                        telephonyManager.getCarrierPackageNamesForIntentAndPhone(
+                                intent, SubscriptionManager.getPhoneId(subId));
                 if (carrierPackages != null && carrierPackages.size() == 1) {
                     LocalServices.getService(UriGrantsManagerInternal.class)
                             .grantUriPermissionFromIntent(callingUid, carrierPackages.get(0),
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e66e596..f7e825e 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1027,7 +1027,12 @@
                 log(str);
             }
             mLocalLog.log(str);
-            if (validatePhoneId(phoneId)) {
+            // for service state updates, don't notify clients when subId is invalid. This prevents
+            // us from sending incorrect notifications like b/133140128
+            // In the future, we can remove this logic for every notification here and add a
+            // callback so listeners know when their PhoneStateListener's subId becomes invalid, but
+            // for now we use the simplest fix.
+            if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) {
                 mServiceState[phoneId] = state;
 
                 for (Record r : mRecords) {
@@ -1059,7 +1064,8 @@
                     }
                 }
             } else {
-                log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId);
+                log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId
+                        + " or subId=" + subId);
             }
             handleRemoveListLocked();
         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8ef5b07..a537865 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2543,15 +2543,11 @@
         mVolumeController.postVolumeChanged(streamType, flags);
     }
 
-    // If Hdmi-CEC system audio mode is on, we show volume bar only when TV
-    // receives volume notification from Audio Receiver.
+    // If Hdmi-CEC system audio mode is on and we are a TV panel, never show volume bar.
     private int updateFlagsForTvPlatform(int flags) {
         synchronized (mHdmiClientLock) {
-            if (mHdmiTvClient != null) {
-                if (mHdmiSystemAudioSupported &&
-                        ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) {
-                    flags &= ~AudioManager.FLAG_SHOW_UI;
-                }
+            if (mHdmiTvClient != null && mHdmiSystemAudioSupported) {
+                flags &= ~AudioManager.FLAG_SHOW_UI;
             }
         }
         return flags;
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 72bac74..a38abdc 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -63,6 +63,7 @@
 import com.android.server.biometrics.AuthenticationClient;
 import com.android.server.biometrics.BiometricServiceBase;
 import com.android.server.biometrics.BiometricUtils;
+import com.android.server.biometrics.ClientMonitor;
 import com.android.server.biometrics.Constants;
 import com.android.server.biometrics.EnumerateClient;
 import com.android.server.biometrics.RemovalClient;
@@ -342,7 +343,16 @@
         @Override // Binder call
         public int revokeChallenge(IBinder token) {
             checkPermission(MANAGE_BIOMETRIC);
-            return startRevokeChallenge(token);
+            // TODO(b/137106905): Schedule binder calls in FaceService to avoid deadlocks.
+            if (getCurrentClient() == null) {
+                // if we aren't handling any other HIDL calls (mCurrentClient == null), revoke the
+                // challenge right away.
+                return startRevokeChallenge(token);
+            } else {
+                // postpone revoking the challenge until we finish processing the current HIDL call.
+                mRevokeChallengePending = true;
+                return Status.OK;
+            }
         }
 
         @Override // Binder call
@@ -812,6 +822,7 @@
     @GuardedBy("this")
     private IBiometricsFace mDaemon;
     private UsageStats mUsageStats;
+    private boolean mRevokeChallengePending = false;
     // One of the AuthenticationClient constants
     private int mCurrentUserLockoutMode;
 
@@ -1041,6 +1052,15 @@
     }
 
     @Override
+    protected void removeClient(ClientMonitor client) {
+        super.removeClient(client);
+        if (mRevokeChallengePending) {
+            startRevokeChallenge(null);
+            mRevokeChallengePending = false;
+        }
+    }
+
+    @Override
     public void onStart() {
         super.onStart();
         publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
@@ -1251,7 +1271,11 @@
             return 0;
         }
         try {
-            return daemon.revokeChallenge();
+            final int res = daemon.revokeChallenge();
+            if (res != Status.OK) {
+                Slog.e(TAG, "revokeChallenge returned " + res);
+            }
+            return res;
         } catch (RemoteException e) {
             Slog.e(TAG, "startRevokeChallenge failed", e);
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 49b42df..09e9375 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -17,7 +17,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.inputmethod.InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -1036,9 +1035,7 @@
                 // sender userId can be a real user ID or USER_ALL.
                 final int senderUserId = pendingResult.getSendingUserId();
                 if (senderUserId != UserHandle.USER_ALL) {
-                    final int resolvedUserId = PER_PROFILE_IME_ENABLED
-                            ? senderUserId : mUserManagerInternal.getProfileParentId(senderUserId);
-                    if (resolvedUserId != mSettings.getCurrentUserId()) {
+                    if (senderUserId != mSettings.getCurrentUserId()) {
                         // A background user is trying to hide the dialog. Ignore.
                         return;
                     }
@@ -1662,9 +1659,6 @@
         if (userId == mSettings.getCurrentUserId()) {
             return true;
         }
-        if (!PER_PROFILE_IME_ENABLED && mSettings.isCurrentProfile(userId)) {
-            return true;
-        }
 
         // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
         // foreground user, not for the user of that process. Accordingly InputMethodManagerService
@@ -3005,7 +2999,7 @@
             return InputBindResult.INVALID_USER;
         }
 
-        if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
+        if (userId != mSettings.getCurrentUserId()) {
             switchUserLocked(userId);
         }
         // Master feature flag that overrides other conditions and forces IME preRendering.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index b5e19ae..77e2fbd2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -35,13 +35,11 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.IntArray;
 import android.util.Pair;
 import android.util.Printer;
 import android.util.Slog;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
-import android.view.inputmethod.InputMethodSystemProperty;
 import android.view.textservice.SpellCheckerInfo;
 
 import com.android.internal.annotations.GuardedBy;
@@ -1303,9 +1301,6 @@
      * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a
      * list of real user IDs.
      *
-     * <p>This method also converts profile user ID to profile parent user ID unless
-     * {@link InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is {@code true}.</p>
-     *
      * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and
      *                           {@link UserHandle#USER_ALL} are also supported
      * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT}
@@ -1320,17 +1315,7 @@
                 LocalServices.getService(UserManagerInternal.class);
 
         if (userIdToBeResolved == UserHandle.USER_ALL) {
-            if (InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
-                return userManagerInternal.getUserIds();
-            }
-            final IntArray result = new IntArray();
-            for (int userId : userManagerInternal.getUserIds()) {
-                final int parentUserId = userManagerInternal.getProfileParentId(userId);
-                if (result.indexOf(parentUserId) < 0) {
-                    result.add(parentUserId);
-                }
-            }
-            return result.toArray();
+            return userManagerInternal.getUserIds();
         }
 
         final int sourceUserId;
@@ -1353,8 +1338,6 @@
             }
             return new int[]{};
         }
-        final int resolvedUserId = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
-                ? sourceUserId : userManagerInternal.getProfileParentId(sourceUserId);
-        return new int[]{resolvedUserId};
+        return new int[]{sourceUserId};
     }
 }
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java
index 766d8ca..3b24f46 100644
--- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java
@@ -17,8 +17,6 @@
 package com.android.server.net.watchlist;
 
 import android.content.Context;
-import android.content.Intent;
-import android.net.NetworkWatchlistManager;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -26,7 +24,6 @@
 import android.provider.Settings;
 
 import java.io.FileInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
 
@@ -74,10 +71,12 @@
         try {
             final String configXmlPath = getNextArgRequired();
             final ParcelFileDescriptor pfd = openFileForSystem(configXmlPath, "r");
-            if (pfd != null) {
-                final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor());
-                WatchlistConfig.getInstance().setTestMode(fileStream);
+            if (pfd == null) {
+                pw.println("Error: can't open input file " + configXmlPath);
+                return -1;
             }
+            final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor());
+            WatchlistConfig.getInstance().setTestMode(fileStream);
             pw.println("Success!");
         } catch (Exception ex) {
             pw.println("Error: " + ex.toString());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f032fa6..819a1d9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -207,6 +207,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.widget.RemoteViews;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -463,6 +464,9 @@
     private boolean mIsAutomotive;
     private boolean mNotificationEffectsEnabledForAutomotive;
 
+    private int mWarnRemoteViewsSizeBytes;
+    private int mStripRemoteViewsSizeBytes;
+
     private MetricsLogger mMetricsLogger;
     private TriPredicate<String, Integer, String> mAllowedManagedServicePackages;
 
@@ -1723,6 +1727,11 @@
 
         mZenModeHelper.setPriorityOnlyDndExemptPackages(getContext().getResources().getStringArray(
                 com.android.internal.R.array.config_priorityOnlyDndExemptPackages));
+
+        mWarnRemoteViewsSizeBytes = getContext().getResources().getInteger(
+                com.android.internal.R.integer.config_notificationWarnRemoteViewSizeBytes);
+        mStripRemoteViewsSizeBytes = getContext().getResources().getInteger(
+                com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);
     }
 
     @Override
@@ -4712,7 +4721,7 @@
 
         // Fix the notification as best we can.
         try {
-            fixNotification(notification, pkg, userId);
+            fixNotification(notification, pkg, tag, id, userId);
 
         } catch (NameNotFoundException e) {
             Slog.e(TAG, "Cannot create a context for sending app", e);
@@ -4817,8 +4826,8 @@
     }
 
     @VisibleForTesting
-    protected void fixNotification(Notification notification, String pkg, int userId)
-            throws NameNotFoundException {
+    protected void fixNotification(Notification notification, String pkg, String tag, int id,
+            int userId) throws NameNotFoundException {
         final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                 pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                 (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
@@ -4841,6 +4850,50 @@
                         ": Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission");
             }
         }
+
+        // Remote views? Are they too big?
+        checkRemoteViews(pkg, tag, id, notification);
+    }
+
+    private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
+        if (removeRemoteView(pkg, tag, id, notification.contentView)) {
+            notification.contentView = null;
+        }
+        if (removeRemoteView(pkg, tag, id, notification.bigContentView)) {
+            notification.bigContentView = null;
+        }
+        if (removeRemoteView(pkg, tag, id, notification.headsUpContentView)) {
+            notification.headsUpContentView = null;
+        }
+        if (notification.publicVersion != null) {
+            if (removeRemoteView(pkg, tag, id, notification.publicVersion.contentView)) {
+                notification.publicVersion.contentView = null;
+            }
+            if (removeRemoteView(pkg, tag, id, notification.publicVersion.bigContentView)) {
+                notification.publicVersion.bigContentView = null;
+            }
+            if (removeRemoteView(pkg, tag, id, notification.publicVersion.headsUpContentView)) {
+                notification.publicVersion.headsUpContentView = null;
+            }
+        }
+    }
+
+    private boolean removeRemoteView(String pkg, String tag, int id, RemoteViews contentView) {
+        if (contentView == null) {
+            return false;
+        }
+        final int contentViewSize = contentView.estimateMemoryUsage();
+        if (contentViewSize > mWarnRemoteViewsSizeBytes
+                && contentViewSize < mStripRemoteViewsSizeBytes) {
+            Slog.w(TAG, "RemoteViews too large on tag: " + tag + " id: " + id
+                    + " this might be stripped in a future release");
+        }
+        if (contentViewSize >= mStripRemoteViewsSizeBytes) {
+            mUsageStats.registerImageRemoved(pkg);
+            Slog.w(TAG, "Removed too large RemoteViews on tag: " + tag + " id: " + id);
+            return true;
+        }
+        return false;
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 6748f80..a126073 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -34,11 +34,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.media.AudioSystem;
 import android.metrics.LogMaker;
@@ -453,16 +450,11 @@
 
     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
         final Notification notification = sbn.getNotification();
-        final Icon icon = notification.getSmallIcon();
-        String iconStr = String.valueOf(icon);
-        if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
-            iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
-        }
         pw.println(prefix + this);
         prefix = prefix + "  ";
         pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
         pw.println(prefix + "opPkg=" + sbn.getOpPkg());
-        pw.println(prefix + "icon=" + iconStr);
+        pw.println(prefix + "icon=" + notification.getSmallIcon());
         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
         pw.println(prefix + "pri=" + notification.priority);
         pw.println(prefix + "key=" + sbn.getKey());
@@ -592,28 +584,6 @@
         pw.println(prefix + "mAdjustments=" + mAdjustments);
     }
 
-
-    static String idDebugString(Context baseContext, String packageName, int id) {
-        Context c;
-
-        if (packageName != null) {
-            try {
-                c = baseContext.createPackageContext(packageName, 0);
-            } catch (NameNotFoundException e) {
-                c = baseContext;
-            }
-        } else {
-            c = baseContext;
-        }
-
-        Resources r = c.getResources();
-        try {
-            return r.getResourceName(id);
-        } catch (Resources.NotFoundException e) {
-            return "<name unknown>";
-        }
-    }
-
     @Override
     public final String toString() {
         return String.format(
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index dd393b8..fe3d0eb 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -41,7 +41,6 @@
 import org.json.JSONObject;
 
 import java.io.PrintWriter;
-import java.lang.Math;
 import java.util.ArrayDeque;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
@@ -263,6 +262,17 @@
         }
     }
 
+    /**
+     * Call this when RemoteViews object has been removed from a notification because the images
+     * it contains are too big (even after rescaling).
+     */
+    public synchronized void registerImageRemoved(String packageName) {
+        AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
+        for (AggregatedStats stats : aggregatedStatsArray) {
+            stats.numImagesRemoved++;
+        }
+    }
+
     // Locked by this.
     private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
         return getAggregatedStatsLocked(record.sbn.getPackageName());
@@ -405,6 +415,7 @@
         public int numAlertViolations;
         public int numQuotaViolations;
         public long mLastAccessTime;
+        public int numImagesRemoved;
 
         public AggregatedStats(Context context, String key) {
             this.key = key;
@@ -529,6 +540,7 @@
             maybeCount("note_over_rate", (numRateViolations - previous.numRateViolations));
             maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations));
             maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations));
+            maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved));
             noisyImportance.maybeCount(previous.noisyImportance);
             quietImportance.maybeCount(previous.quietImportance);
             finalImportance.maybeCount(previous.finalImportance);
@@ -562,6 +574,7 @@
             previous.numRateViolations = numRateViolations;
             previous.numAlertViolations = numAlertViolations;
             previous.numQuotaViolations = numQuotaViolations;
+            previous.numImagesRemoved = numImagesRemoved;
             noisyImportance.update(previous.noisyImportance);
             quietImportance.update(previous.quietImportance);
             finalImportance.update(previous.finalImportance);
@@ -667,6 +680,8 @@
             output.append("numAlertViolations=").append(numAlertViolations).append("\n");
             output.append(indentPlusTwo);
             output.append("numQuotaViolations=").append(numQuotaViolations).append("\n");
+            output.append(indentPlusTwo);
+            output.append("numImagesRemoved=").append(numImagesRemoved).append("\n");
             output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
             output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
             output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
@@ -709,6 +724,7 @@
             maybePut(dump, "numQuotaLViolations", numQuotaViolations);
             maybePut(dump, "notificationEnqueueRate", getEnqueueRate());
             maybePut(dump, "numAlertViolations", numAlertViolations);
+            maybePut(dump, "numImagesRemoved", numImagesRemoved);
             noisyImportance.maybePut(dump, previous.noisyImportance);
             quietImportance.maybePut(dump, previous.quietImportance);
             finalImportance.maybePut(dump, previous.finalImportance);
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 03224d1..2301e3f 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -11,7 +11,7 @@
  * 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.s
+ * limitations under the License.
  */
 
 package com.android.server.pm;
@@ -29,12 +29,11 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageParserException;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
 import android.sysprop.ApexProperties;
 import android.util.Slog;
 
@@ -55,103 +54,33 @@
  * ApexManager class handles communications with the apex service to perform operation and queries,
  * as well as providing caching to avoid unnecessary calls to the service.
  */
-class ApexManager {
-    static final String TAG = "ApexManager";
-    private final IApexService mApexService;
-    private final Context mContext;
-    private final Object mLock = new Object();
-    /**
-     * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
-     * AndroidManifest.xml}
-     *
-     * <p>Note that key of this map is {@code packageName} field of the corresponding {@code
-     * AndroidManifest.xml}.
-      */
-    @GuardedBy("mLock")
-    private List<PackageInfo> mAllPackagesCache;
+abstract class ApexManager {
 
-    ApexManager(Context context) {
-        mContext = context;
-        if (!isApexSupported()) {
-            mApexService = null;
-            return;
-        }
-        try {
-            mApexService = IApexService.Stub.asInterface(
-                ServiceManager.getServiceOrThrow("apexservice"));
-        } catch (ServiceNotFoundException e) {
-            throw new IllegalStateException("Required service apexservice not available");
-        }
-    }
+    private static final String TAG = "ApexManager";
 
     static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
     static final int MATCH_FACTORY_PACKAGE = 1 << 1;
-    @IntDef(
-            flag = true,
-            prefix = { "MATCH_"},
-            value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
-    @Retention(RetentionPolicy.SOURCE)
-    @interface PackageInfoFlags{}
 
-    void systemReady() {
-        if (!isApexSupported()) return;
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                onBootCompleted();
-                mContext.unregisterReceiver(this);
-            }
-        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
-    }
-
-    private void populateAllPackagesCacheIfNeeded() {
-        synchronized (mLock) {
-            if (mAllPackagesCache != null) {
-                return;
-            }
+    /**
+     * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending
+     * on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to
+     * {@code true}.
+     */
+    static ApexManager create(Context systemContext) {
+        if (ApexProperties.updatable().orElse(false)) {
             try {
-                mAllPackagesCache = new ArrayList<>();
-                HashSet<String> activePackagesSet = new HashSet<>();
-                HashSet<String> factoryPackagesSet = new HashSet<>();
-                final ApexInfo[] allPkgs = mApexService.getAllPackages();
-                for (ApexInfo ai : allPkgs) {
-                    // If the device is using flattened APEX, don't report any APEX
-                    // packages since they won't be managed or updated by PackageManager.
-                    if ((new File(ai.modulePath)).isDirectory()) {
-                        break;
-                    }
-                    try {
-                        final PackageInfo pkg = PackageParser.generatePackageInfoFromApex(
-                                ai, PackageManager.GET_META_DATA
-                                        | PackageManager.GET_SIGNING_CERTIFICATES);
-                        mAllPackagesCache.add(pkg);
-                        if (ai.isActive) {
-                            if (activePackagesSet.contains(pkg.packageName)) {
-                                throw new IllegalStateException(
-                                        "Two active packages have the same name: "
-                                                + pkg.packageName);
-                            }
-                            activePackagesSet.add(pkg.packageName);
-                        }
-                        if (ai.isFactory) {
-                            if (factoryPackagesSet.contains(pkg.packageName)) {
-                                throw new IllegalStateException(
-                                        "Two factory packages have the same name: "
-                                                + pkg.packageName);
-                            }
-                            factoryPackagesSet.add(pkg.packageName);
-                        }
-                    } catch (PackageParserException pe) {
-                        throw new IllegalStateException("Unable to parse: " + ai, pe);
-                    }
-                }
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
-                throw new RuntimeException(re);
+                return new ApexManagerImpl(systemContext, IApexService.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow("apexservice")));
+            } catch (ServiceManager.ServiceNotFoundException e) {
+                throw new IllegalStateException("Required service apexservice not available");
             }
+        } else {
+            return new ApexManagerNoOp();
         }
     }
 
+    abstract void systemReady();
+
     /**
      * Retrieves information about an APEX package.
      *
@@ -164,22 +93,8 @@
      * @return a PackageInfo object with the information about the package, or null if the package
      *         is not found.
      */
-    @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
-        if (!isApexSupported()) return null;
-        populateAllPackagesCacheIfNeeded();
-        boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
-        boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
-        for (PackageInfo packageInfo: mAllPackagesCache) {
-            if (!packageInfo.packageName.equals(packageName)) {
-                continue;
-            }
-            if ((!matchActive || isActive(packageInfo))
-                    && (!matchFactory || isFactory(packageInfo))) {
-                return packageInfo;
-            }
-        }
-        return null;
-    }
+    @Nullable
+    abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags);
 
     /**
      * Retrieves information about all active APEX packages.
@@ -187,14 +102,7 @@
      * @return a List of PackageInfo object, each one containing information about a different
      *         active package.
      */
-    List<PackageInfo> getActivePackages() {
-        if (!isApexSupported()) return Collections.emptyList();
-        populateAllPackagesCacheIfNeeded();
-        return mAllPackagesCache
-                .stream()
-                .filter(item -> isActive(item))
-                .collect(Collectors.toList());
-    }
+    abstract List<PackageInfo> getActivePackages();
 
     /**
      * Retrieves information about all active pre-installed APEX packages.
@@ -202,14 +110,7 @@
      * @return a List of PackageInfo object, each one containing information about a different
      *         active pre-installed package.
      */
-    List<PackageInfo> getFactoryPackages() {
-        if (!isApexSupported()) return Collections.emptyList();
-        populateAllPackagesCacheIfNeeded();
-        return mAllPackagesCache
-                .stream()
-                .filter(item -> isFactory(item))
-                .collect(Collectors.toList());
-    }
+    abstract List<PackageInfo> getFactoryPackages();
 
     /**
      * Retrieves information about all inactive APEX packages.
@@ -217,14 +118,7 @@
      * @return a List of PackageInfo object, each one containing information about a different
      *         inactive package.
      */
-    List<PackageInfo> getInactivePackages() {
-        if (!isApexSupported()) return Collections.emptyList();
-        populateAllPackagesCacheIfNeeded();
-        return mAllPackagesCache
-                .stream()
-                .filter(item -> !isActive(item))
-                .collect(Collectors.toList());
-    }
+    abstract List<PackageInfo> getInactivePackages();
 
     /**
      * Checks if {@code packageName} is an apex package.
@@ -232,16 +126,7 @@
      * @param packageName package to check.
      * @return {@code true} if {@code packageName} is an apex package.
      */
-    boolean isApexPackage(String packageName) {
-        if (!isApexSupported()) return false;
-        populateAllPackagesCacheIfNeeded();
-        for (PackageInfo packageInfo : mAllPackagesCache) {
-            if (packageInfo.packageName.equals(packageName)) {
-                return true;
-            }
-        }
-        return false;
-    }
+    abstract boolean isApexPackage(String packageName);
 
     /**
      * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
@@ -250,19 +135,8 @@
      * @param sessionId the identifier of the session.
      * @return an ApexSessionInfo object, or null if the session is not known.
      */
-    @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
-        if (!isApexSupported()) return null;
-        try {
-            ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
-            if (apexSessionInfo.isUnknown) {
-                return null;
-            }
-            return apexSessionInfo;
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to contact apexservice", re);
-            throw new RuntimeException(re);
-        }
-    }
+    @Nullable
+    abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
 
     /**
      * Submit a staged session to apex service. This causes the apex service to perform some initial
@@ -274,22 +148,10 @@
      * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
      *                        an array of identifiers of all the child sessions. Otherwise it should
      *                        be an empty array.
-     * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
-     *                     and will be filled with a list of {@link ApexInfo} objects, each of which
-     *                     contains metadata about one of the packages being submitted as part of
-     *                     the session.
-     * @return whether the submission of the session was successful.
+     * @throws PackageManagerException if call to apexd fails
      */
-    boolean submitStagedSession(
-            int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
-        if (!isApexSupported()) return false;
-        try {
-            return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to contact apexservice", re);
-            throw new RuntimeException(re);
-        }
-    }
+    abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
+            throws PackageManagerException;
 
     /**
      * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
@@ -298,15 +160,7 @@
      * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
      * @return true upon success, false if the session is unknown.
      */
-    boolean markStagedSessionReady(int sessionId) {
-        if (!isApexSupported()) return false;
-        try {
-            return mApexService.markStagedSessionReady(sessionId);
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to contact apexservice", re);
-            throw new RuntimeException(re);
-        }
-    }
+    abstract boolean markStagedSessionReady(int sessionId);
 
     /**
      * Marks a staged session as successful.
@@ -316,44 +170,21 @@
      * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
      *                  successful.
      */
-    void markStagedSessionSuccessful(int sessionId) {
-        if (!isApexSupported()) return;
-        try {
-            mApexService.markStagedSessionSuccessful(sessionId);
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to contact apexservice", re);
-            throw new RuntimeException(re);
-        } catch (Exception e) {
-            // It is fine to just log an exception in this case. APEXd will be able to recover in
-            // case markStagedSessionSuccessful fails.
-            Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
-        }
-    }
+    abstract void markStagedSessionSuccessful(int sessionId);
 
     /**
      * Whether the current device supports the management of APEX packages.
      *
      * @return true if APEX packages can be managed on this device, false otherwise.
      */
-    boolean isApexSupported() {
-        return ApexProperties.updatable().orElse(false);
-    }
+    abstract boolean isApexSupported();
 
     /**
      * Abandons the (only) active session previously submitted.
      *
      * @return {@code true} upon success, {@code false} if any remote exception occurs
      */
-    boolean abortActiveSession() {
-        if (!isApexSupported()) return false;
-        try {
-            mApexService.abortActiveSession();
-            return true;
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to contact apexservice", re);
-            return false;
-        }
-    }
+    abstract boolean abortActiveSession();
 
     /**
      * Uninstalls given {@code apexPackage}.
@@ -363,64 +194,7 @@
      * @param apexPackagePath package to uninstall.
      * @return {@code true} upon successful uninstall, {@code false} otherwise.
      */
-    boolean uninstallApex(String apexPackagePath) {
-        if (!isApexSupported()) return false;
-        try {
-            mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
-            return true;
-        } catch (Exception e) {
-            return false;
-        }
-    }
-
-    /**
-     * Whether an APEX package is active or not.
-     *
-     * @param packageInfo the package to check
-     * @return {@code true} if this package is active, {@code false} otherwise.
-     */
-    private static boolean isActive(PackageInfo packageInfo) {
-        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
-    }
-
-    /**
-     * Whether the APEX package is pre-installed or not.
-     *
-     * @param packageInfo the package to check
-     * @return {@code true} if this package is pre-installed, {@code false} otherwise.
-     */
-    private static boolean isFactory(PackageInfo packageInfo) {
-        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-    }
-
-    /**
-     * Dump information about the packages contained in a particular cache
-     * @param packagesCache the cache to print information about.
-     * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
-     *                    information about that specific package will be dumped.
-     * @param ipw the {@link IndentingPrintWriter} object to send information to.
-     */
-    void dumpFromPackagesCache(
-            List<PackageInfo> packagesCache,
-            @Nullable String packageName,
-            IndentingPrintWriter ipw) {
-        ipw.println();
-        ipw.increaseIndent();
-        for (PackageInfo pi : packagesCache) {
-            if (packageName != null && !packageName.equals(pi.packageName)) {
-                continue;
-            }
-            ipw.println(pi.packageName);
-            ipw.increaseIndent();
-            ipw.println("Version: " + pi.versionCode);
-            ipw.println("Path: " + pi.applicationInfo.sourceDir);
-            ipw.println("IsActive: " + isActive(pi));
-            ipw.println("IsFactory: " + isFactory(pi));
-            ipw.decreaseIndent();
-        }
-        ipw.decreaseIndent();
-        ipw.println();
-    }
+    abstract boolean uninstallApex(String apexPackagePath);
 
     /**
      * Dumps various state information to the provided {@link PrintWriter} object.
@@ -429,54 +203,406 @@
      * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
      *                    information about that specific package will be dumped.
      */
-    void dump(PrintWriter pw, @Nullable String packageName) {
-        if (!isApexSupported()) return;
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
-        try {
-            populateAllPackagesCacheIfNeeded();
-            ipw.println();
-            ipw.println("Active APEX packages:");
-            dumpFromPackagesCache(getActivePackages(), packageName, ipw);
-            ipw.println("Inactive APEX packages:");
-            dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
-            ipw.println("Factory APEX packages:");
-            dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
-            ipw.increaseIndent();
-            ipw.println("APEX session state:");
-            ipw.increaseIndent();
-            final ApexSessionInfo[] sessions = mApexService.getSessions();
-            for (ApexSessionInfo si : sessions) {
-                ipw.println("Session ID: " + si.sessionId);
-                ipw.increaseIndent();
-                if (si.isUnknown) {
-                    ipw.println("State: UNKNOWN");
-                } else if (si.isVerified) {
-                    ipw.println("State: VERIFIED");
-                } else if (si.isStaged) {
-                    ipw.println("State: STAGED");
-                } else if (si.isActivated) {
-                    ipw.println("State: ACTIVATED");
-                } else if (si.isActivationFailed) {
-                    ipw.println("State: ACTIVATION FAILED");
-                } else if (si.isSuccess) {
-                    ipw.println("State: SUCCESS");
-                } else if (si.isRollbackInProgress) {
-                    ipw.println("State: ROLLBACK IN PROGRESS");
-                } else if (si.isRolledBack) {
-                    ipw.println("State: ROLLED BACK");
-                } else if (si.isRollbackFailed) {
-                    ipw.println("State: ROLLBACK FAILED");
+    abstract void dump(PrintWriter pw, @Nullable String packageName);
+
+    @IntDef(
+            flag = true,
+            prefix = { "MATCH_"},
+            value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PackageInfoFlags{}
+
+    /**
+     * An implementation of {@link ApexManager} that should be used in case device supports updating
+     * APEX packages.
+     */
+    private static class ApexManagerImpl extends ApexManager {
+        private final IApexService mApexService;
+        private final Context mContext;
+        private final Object mLock = new Object();
+        /**
+         * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
+         * AndroidManifest.xml}
+         *
+         * <p>Note that key of this map is {@code packageName} field of the corresponding {@code
+         * AndroidManifest.xml}.
+          */
+        @GuardedBy("mLock")
+        private List<PackageInfo> mAllPackagesCache;
+
+        ApexManagerImpl(Context context, IApexService apexService) {
+            mContext = context;
+            mApexService = apexService;
+        }
+
+        /**
+         * Whether an APEX package is active or not.
+         *
+         * @param packageInfo the package to check
+         * @return {@code true} if this package is active, {@code false} otherwise.
+         */
+        private static boolean isActive(PackageInfo packageInfo) {
+            return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
+        }
+
+        /**
+         * Whether the APEX package is pre-installed or not.
+         *
+         * @param packageInfo the package to check
+         * @return {@code true} if this package is pre-installed, {@code false} otherwise.
+         */
+        private static boolean isFactory(PackageInfo packageInfo) {
+            return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+        }
+
+        @Override
+        void systemReady() {
+            mContext.registerReceiver(new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    populateAllPackagesCacheIfNeeded();
+                    mContext.unregisterReceiver(this);
                 }
+            }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+        }
+
+        private void populateAllPackagesCacheIfNeeded() {
+            synchronized (mLock) {
+                if (mAllPackagesCache != null) {
+                    return;
+                }
+                try {
+                    mAllPackagesCache = new ArrayList<>();
+                    HashSet<String> activePackagesSet = new HashSet<>();
+                    HashSet<String> factoryPackagesSet = new HashSet<>();
+                    final ApexInfo[] allPkgs = mApexService.getAllPackages();
+                    for (ApexInfo ai : allPkgs) {
+                        // If the device is using flattened APEX, don't report any APEX
+                        // packages since they won't be managed or updated by PackageManager.
+                        if ((new File(ai.modulePath)).isDirectory()) {
+                            break;
+                        }
+                        try {
+                            final PackageInfo pkg = PackageParser.generatePackageInfoFromApex(
+                                    ai, PackageManager.GET_META_DATA
+                                            | PackageManager.GET_SIGNING_CERTIFICATES);
+                            mAllPackagesCache.add(pkg);
+                            if (ai.isActive) {
+                                if (activePackagesSet.contains(pkg.packageName)) {
+                                    throw new IllegalStateException(
+                                            "Two active packages have the same name: "
+                                                    + pkg.packageName);
+                                }
+                                activePackagesSet.add(pkg.packageName);
+                            }
+                            if (ai.isFactory) {
+                                if (factoryPackagesSet.contains(pkg.packageName)) {
+                                    throw new IllegalStateException(
+                                            "Two factory packages have the same name: "
+                                                    + pkg.packageName);
+                                }
+                                factoryPackagesSet.add(pkg.packageName);
+                            }
+                        } catch (PackageParser.PackageParserException pe) {
+                            throw new IllegalStateException("Unable to parse: " + ai, pe);
+                        }
+                    }
+                } catch (RemoteException re) {
+                    Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
+                    throw new RuntimeException(re);
+                }
+            }
+        }
+
+        @Override
+        @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
+            populateAllPackagesCacheIfNeeded();
+            boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
+            boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
+            for (PackageInfo packageInfo: mAllPackagesCache) {
+                if (!packageInfo.packageName.equals(packageName)) {
+                    continue;
+                }
+                if ((!matchActive || isActive(packageInfo))
+                        && (!matchFactory || isFactory(packageInfo))) {
+                    return packageInfo;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        List<PackageInfo> getActivePackages() {
+            populateAllPackagesCacheIfNeeded();
+            return mAllPackagesCache
+                    .stream()
+                    .filter(item -> isActive(item))
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        List<PackageInfo> getFactoryPackages() {
+            populateAllPackagesCacheIfNeeded();
+            return mAllPackagesCache
+                    .stream()
+                    .filter(item -> isFactory(item))
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        List<PackageInfo> getInactivePackages() {
+            populateAllPackagesCacheIfNeeded();
+            return mAllPackagesCache
+                    .stream()
+                    .filter(item -> !isActive(item))
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        boolean isApexPackage(String packageName) {
+            if (!isApexSupported()) return false;
+            populateAllPackagesCacheIfNeeded();
+            for (PackageInfo packageInfo : mAllPackagesCache) {
+                if (packageInfo.packageName.equals(packageName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
+            try {
+                ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
+                if (apexSessionInfo.isUnknown) {
+                    return null;
+                }
+                return apexSessionInfo;
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                throw new RuntimeException(re);
+            }
+        }
+
+        @Override
+        ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
+                throws PackageManagerException {
+            try {
+                final ApexInfoList apexInfoList = new ApexInfoList();
+                mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
+                return apexInfoList;
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                throw new RuntimeException(re);
+            } catch (Exception e) {
+                throw new PackageManagerException(
+                        PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
+                        "apexd verification failed : " + e.getMessage());
+            }
+        }
+
+        @Override
+        boolean markStagedSessionReady(int sessionId) {
+            try {
+                return mApexService.markStagedSessionReady(sessionId);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                throw new RuntimeException(re);
+            }
+        }
+
+        @Override
+        void markStagedSessionSuccessful(int sessionId) {
+            try {
+                mApexService.markStagedSessionSuccessful(sessionId);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                throw new RuntimeException(re);
+            } catch (Exception e) {
+                // It is fine to just log an exception in this case. APEXd will be able to recover
+                // in case markStagedSessionSuccessful fails.
+                Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
+            }
+        }
+
+        @Override
+        boolean isApexSupported() {
+            return true;
+        }
+
+        @Override
+        boolean abortActiveSession() {
+            try {
+                mApexService.abortActiveSession();
+                return true;
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Unable to contact apexservice", re);
+                return false;
+            }
+        }
+
+        @Override
+        boolean uninstallApex(String apexPackagePath) {
+            try {
+                mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
+                return true;
+            } catch (Exception e) {
+                return false;
+            }
+        }
+
+        /**
+         * Dump information about the packages contained in a particular cache
+         * @param packagesCache the cache to print information about.
+         * @param packageName a {@link String} containing a package name, or {@code null}. If set,
+         *                    only information about that specific package will be dumped.
+         * @param ipw the {@link IndentingPrintWriter} object to send information to.
+         */
+        void dumpFromPackagesCache(
+                List<PackageInfo> packagesCache,
+                @Nullable String packageName,
+                IndentingPrintWriter ipw) {
+            ipw.println();
+            ipw.increaseIndent();
+            for (PackageInfo pi : packagesCache) {
+                if (packageName != null && !packageName.equals(pi.packageName)) {
+                    continue;
+                }
+                ipw.println(pi.packageName);
+                ipw.increaseIndent();
+                ipw.println("Version: " + pi.versionCode);
+                ipw.println("Path: " + pi.applicationInfo.sourceDir);
+                ipw.println("IsActive: " + isActive(pi));
+                ipw.println("IsFactory: " + isFactory(pi));
                 ipw.decreaseIndent();
             }
             ipw.decreaseIndent();
-        } catch (RemoteException e) {
-            ipw.println("Couldn't communicate with apexd.");
+            ipw.println();
+        }
+
+        @Override
+        void dump(PrintWriter pw, @Nullable String packageName) {
+            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+            try {
+                populateAllPackagesCacheIfNeeded();
+                ipw.println();
+                ipw.println("Active APEX packages:");
+                dumpFromPackagesCache(getActivePackages(), packageName, ipw);
+                ipw.println("Inactive APEX packages:");
+                dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
+                ipw.println("Factory APEX packages:");
+                dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
+                ipw.increaseIndent();
+                ipw.println("APEX session state:");
+                ipw.increaseIndent();
+                final ApexSessionInfo[] sessions = mApexService.getSessions();
+                for (ApexSessionInfo si : sessions) {
+                    ipw.println("Session ID: " + si.sessionId);
+                    ipw.increaseIndent();
+                    if (si.isUnknown) {
+                        ipw.println("State: UNKNOWN");
+                    } else if (si.isVerified) {
+                        ipw.println("State: VERIFIED");
+                    } else if (si.isStaged) {
+                        ipw.println("State: STAGED");
+                    } else if (si.isActivated) {
+                        ipw.println("State: ACTIVATED");
+                    } else if (si.isActivationFailed) {
+                        ipw.println("State: ACTIVATION FAILED");
+                    } else if (si.isSuccess) {
+                        ipw.println("State: SUCCESS");
+                    } else if (si.isRollbackInProgress) {
+                        ipw.println("State: ROLLBACK IN PROGRESS");
+                    } else if (si.isRolledBack) {
+                        ipw.println("State: ROLLED BACK");
+                    } else if (si.isRollbackFailed) {
+                        ipw.println("State: ROLLBACK FAILED");
+                    }
+                    ipw.decreaseIndent();
+                }
+                ipw.decreaseIndent();
+            } catch (RemoteException e) {
+                ipw.println("Couldn't communicate with apexd.");
+            }
         }
     }
 
-    public void onBootCompleted() {
-        if (!isApexSupported()) return;
-        populateAllPackagesCacheIfNeeded();
+    /**
+     * An implementation of {@link ApexManager} that should be used in case device does not support
+     * updating APEX packages.
+     */
+    private static final class ApexManagerNoOp extends ApexManager {
+
+        @Override
+        void systemReady() {
+            // No-op
+        }
+
+        @Override
+        PackageInfo getPackageInfo(String packageName, int flags) {
+            return null;
+        }
+
+        @Override
+        List<PackageInfo> getActivePackages() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        List<PackageInfo> getFactoryPackages() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        List<PackageInfo> getInactivePackages() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        boolean isApexPackage(String packageName) {
+            return false;
+        }
+
+        @Override
+        ApexSessionInfo getStagedSessionInfo(int sessionId) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds)
+                throws PackageManagerException {
+            throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+                    "Device doesn't support updating APEX");
+        }
+
+        @Override
+        boolean markStagedSessionReady(int sessionId) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        void markStagedSessionSuccessful(int sessionId) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        boolean isApexSupported() {
+            return false;
+        }
+
+        @Override
+        boolean abortActiveSession() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        boolean uninstallApex(String apexPackagePath) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        void dump(PrintWriter pw, String packageName) {
+            // No-op
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index ad9ac12..2b33ace 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -254,9 +254,16 @@
             @Override
             public void run() {
                 int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
-                if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                if (result == OPTIMIZE_PROCESSED) {
+                    Log.i(TAG, "Idle optimizations completed.");
+                } else if (result == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
                     Log.w(TAG, "Idle optimizations aborted because of space constraints.");
-                    // If we didn't abort we ran to completion (or stopped because of space).
+                } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                    Log.w(TAG, "Idle optimizations aborted by job scheduler.");
+                } else {
+                    Log.w(TAG, "Idle optimizations ended with unexpected code: " + result);
+                }
+                if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                     // Abandon our timeslice and do not reschedule.
                     jobFinished(jobParams, /* reschedule */ false);
                 }
@@ -339,6 +346,7 @@
             long lowStorageThreshold, boolean isForPrimaryDex) {
         ArraySet<String> updatedPackages = new ArraySet<>();
         Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
+        boolean hadSomeLowSpaceFailure = false;
         Log.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
         // Only downgrade apps when space is low on device.
         // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
@@ -359,6 +367,7 @@
             } else {
                 if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
                     // can't dexopt because of low space.
+                    hadSomeLowSpaceFailure = true;
                     continue;
                 }
                 dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex);
@@ -369,7 +378,7 @@
         }
 
         notifyPinService(updatedPackages);
-        return OPTIMIZE_PROCESSED;
+        return hadSomeLowSpaceFailure ? OPTIMIZE_ABORT_NO_SPACE_LEFT : OPTIMIZE_PROCESSED;
     }
 
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 232bca8..5ab8ec3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1768,11 +1768,12 @@
 
     private PermissionCallback mPermissionCallback = new PermissionCallback() {
         @Override
-        public void onGidsChanged(int appId, int userId) {
+        public void onGidsChanged(int appId, @UserIdInt int userId) {
             mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
         }
+
         @Override
-        public void onPermissionGranted(int uid, int userId) {
+        public void onPermissionGranted(int uid, @UserIdInt int userId) {
             mOnPermissionChangeListeners.onPermissionsChanged(uid);
 
             // Not critical; if this is lost, the application has to request again.
@@ -1780,14 +1781,16 @@
                 mSettings.writeRuntimePermissionsForUserLPr(userId, false);
             }
         }
+
         @Override
         public void onInstallPermissionGranted() {
             synchronized (mPackages) {
                 scheduleWriteSettingsLocked();
             }
         }
+
         @Override
-        public void onPermissionRevoked(int uid, int userId) {
+        public void onPermissionRevoked(int uid, @UserIdInt int userId) {
             mOnPermissionChangeListeners.onPermissionsChanged(uid);
 
             synchronized (mPackages) {
@@ -1798,26 +1801,43 @@
             final int appId = UserHandle.getAppId(uid);
             killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
         }
+
         @Override
         public void onInstallPermissionRevoked() {
             synchronized (mPackages) {
                 scheduleWriteSettingsLocked();
             }
         }
+
         @Override
-        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
+        public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {
             synchronized (mPackages) {
                 for (int userId : updatedUserIds) {
                     mSettings.writeRuntimePermissionsForUserLPr(userId, sync);
                 }
             }
         }
+
+        @Override
+        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
+                int uid) {
+            onPermissionUpdated(updatedUserIds, sync);
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+        }
+
         @Override
         public void onInstallPermissionUpdated() {
             synchronized (mPackages) {
                 scheduleWriteSettingsLocked();
             }
         }
+
+        @Override
+        public void onInstallPermissionUpdatedNotifyListener(int uid) {
+            onInstallPermissionUpdated();
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+        }
+
         @Override
         public void onPermissionRemoved() {
             synchronized (mPackages) {
@@ -2489,7 +2509,7 @@
 
         mProtectedPackages = new ProtectedPackages(mContext);
 
-        mApexManager = new ApexManager(context);
+        mApexManager = ApexManager.create(context);
         // CHECKSTYLE:OFF IndentationCheck
         synchronized (mInstallLock) {
         // writer
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index f9a0197..404b515 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -155,13 +155,10 @@
                 }
             }
         }
-        final ApexInfoList apexInfoList = new ApexInfoList();
-        boolean submittedToApexd = mApexManager.submitStagedSession(session.sessionId,
-                childSessionsIds.toArray(), apexInfoList);
-        if (!submittedToApexd) {
-            throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
-                    "APEX staging failed, check logcat messages from apexd for more details.");
-        }
+        // submitStagedSession will throw a PackageManagerException if apexd verification fails,
+        // which will be propagated to populate stagedSessionErrorMessage of this session.
+        final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
+                childSessionsIds.toArray());
         final List<PackageInfo> result = new ArrayList<>();
         for (ApexInfo newPackage : apexInfoList.apexInfos) {
             final PackageInfo pkg;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8a3b3e3..f4ba449 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -90,7 +90,6 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
@@ -2217,10 +2216,11 @@
 
     @GuardedBy({"mPackagesLock", "mRestrictionsLock"})
     private void fallbackToSingleUserLP() {
-        int flags = UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN;
-        // In headless system user mode, the primary flag is assigned to the first human user.
-        if (!RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
-            flags |= UserInfo.FLAG_PRIMARY;
+        int flags = UserInfo.FLAG_INITIALIZED;
+        // In split system user mode, the admin and primary flags are assigned to the first human
+        // user.
+        if (!UserManager.isSplitSystemUser()) {
+            flags |= UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY;
         }
         // Create the system user
         UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags);
@@ -2734,9 +2734,9 @@
                         return null;
                     }
                 }
-                // In headless system user mode, we assign the first human user the primary flag.
+                // In split system user mode, we assign the first human user the primary flag.
                 // And if there is no device owner, we also assign the admin flag to primary user.
-                if (RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER
+                if (UserManager.isSplitSystemUser()
                         && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
                     flags |= UserInfo.FLAG_PRIMARY;
                     synchronized (mUsersLock) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 43a8373..9b00dca 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2334,6 +2334,11 @@
             return;
         }
 
+        // Permission is already revoked, no need to do anything.
+        if (!permissionsState.hasRuntimePermission(permName, userId)) {
+            return;
+        }
+
         if (permissionsState.revokeRuntimePermission(bp, userId) ==
                 PERMISSION_OPERATION_FAILURE) {
             return;
@@ -2952,10 +2957,11 @@
             // Install and runtime permissions are stored in different places,
             // so figure out what permission changed and persist the change.
             if (permissionsState.getInstallPermissionState(permName) != null) {
-                callback.onInstallPermissionUpdated();
+                callback.onInstallPermissionUpdatedNotifyListener(pkg.applicationInfo.uid);
             } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
                     || hadState) {
-                callback.onPermissionUpdated(new int[] { userId }, false);
+                callback.onPermissionUpdatedNotifyListener(new int[] { userId }, false,
+                        pkg.applicationInfo.uid);
             }
         }
     }
@@ -2989,6 +2995,8 @@
             PermissionsState permissionsState = ps.getPermissionsState();
             changed |= permissionsState.updatePermissionFlagsForAllPermissions(
                     userId, flagMask, flagValues);
+            callback.onPermissionUpdatedNotifyListener(new int[] { userId }, false,
+                    pkg.applicationInfo.uid);
         }
         return changed;
     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 23d0114..a785575 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm.permission;
 
+import android.annotation.AppIdInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -42,24 +43,29 @@
      * callback methods.
      */
     public static class PermissionCallback {
-        public void onGidsChanged(int appId, int userId) {
+        public void onGidsChanged(@AppIdInt int appId, @UserIdInt int userId) {
         }
         public void onPermissionChanged() {
         }
-        public void onPermissionGranted(int uid, int userId) {
+        public void onPermissionGranted(int uid, @UserIdInt int userId) {
         }
         public void onInstallPermissionGranted() {
         }
-        public void onPermissionRevoked(int uid, int userId) {
+        public void onPermissionRevoked(int uid, @UserIdInt int userId) {
         }
         public void onInstallPermissionRevoked() {
         }
-        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
+        public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {
+        }
+        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
+                int uid) {
         }
         public void onPermissionRemoved() {
         }
         public void onInstallPermissionUpdated() {
         }
+        public void onInstallPermissionUpdatedNotifyListener(int uid) {
+        }
     }
 
     public abstract void systemReady();
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index eb2f648e..4404b73 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -86,9 +86,9 @@
 
     private static final String TAG = "RollbackManager";
 
-    // Rollbacks expire after 48 hours.
+    // Rollbacks expire after 14 days.
     private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS =
-            TimeUnit.HOURS.toMillis(48);
+            TimeUnit.DAYS.toMillis(14);
 
     // Lock used to synchronize accesses to in-memory rollback data
     // structures. By convention, methods with the suffix "Locked" require
@@ -157,9 +157,6 @@
         mHandlerThread.start();
         Watchdog.getInstance().addThread(getHandler(), HANDLER_THREAD_TIMEOUT_DURATION_MILLIS);
 
-        // TODO: Make sure to register these call backs when a new user is
-        // added too.
-        SessionCallback sessionCallback = new SessionCallback();
         for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
             registerUserCallbacks(userInfo.getUserHandle());
         }
@@ -253,9 +250,6 @@
             return;
         }
 
-        // TODO: Reuse the same SessionCallback and broadcast receiver
-        // instances, rather than creating new instances for each user.
-
         context.getPackageManager().getPackageInstaller()
                 .registerSessionCallback(new SessionCallback(), getHandler());
 
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 5f00148..ab3d7b7 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -30,6 +30,7 @@
 import android.service.textclassifier.ITextClassifierCallback;
 import android.service.textclassifier.ITextClassifierService;
 import android.service.textclassifier.TextClassifierService;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.textclassifier.ConversationActions;
@@ -54,6 +55,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
+import java.util.Map;
 import java.util.Queue;
 
 /**
@@ -119,6 +121,8 @@
     private final Object mLock;
     @GuardedBy("mLock")
     final SparseArray<UserState> mUserStates = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final Map<TextClassificationSessionId, Integer> mSessionUserIds = new ArrayMap<>();
 
     private TextClassificationManagerService(Context context) {
         mContext = Preconditions.checkNotNull(context);
@@ -127,15 +131,16 @@
 
     @Override
     public void onSuggestSelection(
-            TextClassificationSessionId sessionId,
+            @Nullable TextClassificationSessionId sessionId,
             TextSelection.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
-        validateInput(mContext, request.getCallingPackageName());
+        final int userId = request.getUserId();
+        validateInput(mContext, request.getCallingPackageName(), userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
@@ -150,15 +155,16 @@
 
     @Override
     public void onClassifyText(
-            TextClassificationSessionId sessionId,
+            @Nullable TextClassificationSessionId sessionId,
             TextClassification.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
-        validateInput(mContext, request.getCallingPackageName());
+        final int userId = request.getUserId();
+        validateInput(mContext, request.getCallingPackageName(), userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
@@ -173,15 +179,16 @@
 
     @Override
     public void onGenerateLinks(
-            TextClassificationSessionId sessionId,
+            @Nullable TextClassificationSessionId sessionId,
             TextLinks.Request request, ITextClassifierCallback callback)
             throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
-        validateInput(mContext, request.getCallingPackageName());
+        final int userId = request.getUserId();
+        validateInput(mContext, request.getCallingPackageName(), userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
@@ -196,12 +203,14 @@
 
     @Override
     public void onSelectionEvent(
-            TextClassificationSessionId sessionId, SelectionEvent event) throws RemoteException {
+            @Nullable TextClassificationSessionId sessionId, SelectionEvent event)
+            throws RemoteException {
         Preconditions.checkNotNull(event);
-        validateInput(mContext, event.getPackageName());
+        final int userId = event.getUserId();
+        validateInput(mContext, event.getPackageName(), userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (userState.isBoundLocked()) {
                 userState.mService.onSelectionEvent(sessionId, event);
             } else {
@@ -213,16 +222,19 @@
     }
     @Override
     public void onTextClassifierEvent(
-            TextClassificationSessionId sessionId,
+            @Nullable TextClassificationSessionId sessionId,
             TextClassifierEvent event) throws RemoteException {
         Preconditions.checkNotNull(event);
         final String packageName = event.getEventContext() == null
                 ? null
                 : event.getEventContext().getPackageName();
-        validateInput(mContext, packageName);
+        final int userId = event.getEventContext() == null
+                ? UserHandle.getCallingUserId()
+                : event.getEventContext().getUserId();
+        validateInput(mContext, packageName, userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (userState.isBoundLocked()) {
                 userState.mService.onTextClassifierEvent(sessionId, event);
             } else {
@@ -235,15 +247,16 @@
 
     @Override
     public void onDetectLanguage(
-            TextClassificationSessionId sessionId,
+            @Nullable TextClassificationSessionId sessionId,
             TextLanguage.Request request,
             ITextClassifierCallback callback) throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
-        validateInput(mContext, request.getCallingPackageName());
+        final int userId = request.getUserId();
+        validateInput(mContext, request.getCallingPackageName(), userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
@@ -258,15 +271,16 @@
 
     @Override
     public void onSuggestConversationActions(
-            TextClassificationSessionId sessionId,
+            @Nullable TextClassificationSessionId sessionId,
             ConversationActions.Request request,
             ITextClassifierCallback callback) throws RemoteException {
         Preconditions.checkNotNull(request);
         Preconditions.checkNotNull(callback);
-        validateInput(mContext, request.getCallingPackageName());
+        final int userId = request.getUserId();
+        validateInput(mContext, request.getCallingPackageName(), userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (!userState.bindLocked()) {
                 callback.onFailure();
             } else if (userState.isBoundLocked()) {
@@ -285,13 +299,15 @@
             throws RemoteException {
         Preconditions.checkNotNull(sessionId);
         Preconditions.checkNotNull(classificationContext);
-        validateInput(mContext, classificationContext.getPackageName());
+        final int userId = classificationContext.getUserId();
+        validateInput(mContext, classificationContext.getPackageName(), userId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            UserState userState = getUserStateLocked(userId);
             if (userState.isBoundLocked()) {
                 userState.mService.onCreateTextClassificationSession(
                         classificationContext, sessionId);
+                mSessionUserIds.put(sessionId, userId);
             } else {
                 userState.mPendingRequests.add(new PendingRequest(
                         () -> onCreateTextClassificationSession(classificationContext, sessionId),
@@ -306,9 +322,15 @@
         Preconditions.checkNotNull(sessionId);
 
         synchronized (mLock) {
-            UserState userState = getCallingUserStateLocked();
+            final int userId = mSessionUserIds.containsKey(sessionId)
+                    ? mSessionUserIds.get(sessionId)
+                    : UserHandle.getCallingUserId();
+            validateInput(mContext, null /* packageName */, userId);
+
+            UserState userState = getUserStateLocked(userId);
             if (userState.isBoundLocked()) {
                 userState.mService.onDestroyTextClassificationSession(sessionId);
+                mSessionUserIds.remove(sessionId);
             } else {
                 userState.mPendingRequests.add(new PendingRequest(
                         () -> onDestroyTextClassificationSession(sessionId),
@@ -318,11 +340,6 @@
     }
 
     @GuardedBy("mLock")
-    private UserState getCallingUserStateLocked() {
-        return getUserStateLocked(UserHandle.getCallingUserId());
-    }
-
-    @GuardedBy("mLock")
     private UserState getUserStateLocked(int userId) {
         UserState result = mUserStates.get(userId);
         if (result == null) {
@@ -356,6 +373,7 @@
                     pw.decreaseIndent();
                 }
             }
+            pw.println("Number of active sessions: " + mSessionUserIds.size());
         }
     }
 
@@ -420,20 +438,32 @@
                 e -> Slog.d(LOG_TAG, "Error " + opDesc + ": " + e.getMessage()));
     }
 
-    private static void validateInput(Context context, @Nullable String packageName)
+    private static void validateInput(
+            Context context, @Nullable String packageName, @UserIdInt int userId)
             throws RemoteException {
-        if (packageName == null) return;
 
         try {
-            final int packageUid = context.getPackageManager()
-                    .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
-            final int callingUid = Binder.getCallingUid();
-            Preconditions.checkArgument(callingUid == packageUid
-                    // Trust the system process:
-                    || callingUid == android.os.Process.SYSTEM_UID);
+            if (packageName != null) {
+                final int packageUid = context.getPackageManager()
+                        .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
+                final int callingUid = Binder.getCallingUid();
+                Preconditions.checkArgument(callingUid == packageUid
+                        // Trust the system process:
+                        || callingUid == android.os.Process.SYSTEM_UID,
+                        "Invalid package name. Package=" + packageName
+                                + ", CallingUid=" + callingUid);
+            }
+
+            Preconditions.checkArgument(userId != UserHandle.USER_NULL, "Null userId");
+            final int callingUserId = UserHandle.getCallingUserId();
+            if (callingUserId != userId) {
+                context.enforceCallingPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        "Invalid userId. UserId=" + userId + ", CallingUserId=" + callingUserId);
+            }
         } catch (Exception e) {
-            throw new RemoteException(
-                    String.format("Invalid package: name=%s, error=%s", packageName, e));
+            throw new RemoteException("Invalid request", e,
+                    /* enableSuppression */ true, /* writableStackTrace */ true);
         }
     }
 
diff --git a/services/core/java/com/android/server/textservices/LazyIntToIntMap.java b/services/core/java/com/android/server/textservices/LazyIntToIntMap.java
deleted file mode 100644
index 2e7f2a9..0000000
--- a/services/core/java/com/android/server/textservices/LazyIntToIntMap.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.textservices;
-
-import android.annotation.NonNull;
-import android.util.SparseIntArray;
-
-import java.util.function.IntUnaryOperator;
-
-/**
- * Simple int-to-int key-value-store that is to be lazily initialized with the given
- * {@link IntUnaryOperator}.
- */
-final class LazyIntToIntMap {
-
-    private final SparseIntArray mMap = new SparseIntArray();
-
-    @NonNull
-    private final IntUnaryOperator mMappingFunction;
-
-    /**
-     * @param mappingFunction int to int mapping rules to be (lazily) evaluated
-     */
-    public LazyIntToIntMap(@NonNull IntUnaryOperator mappingFunction) {
-        mMappingFunction = mappingFunction;
-    }
-
-    /**
-     * Deletes {@code key} and associated value.
-     * @param key key to be deleted
-     */
-    public void delete(int key) {
-        mMap.delete(key);
-    }
-
-    /**
-     * @param key key associated with the value
-     * @return value associated with the {@code key}. If this is the first time to access
-     * {@code key}, then {@code mappingFunction} passed to the constructor will be evaluated
-     */
-    public int get(int key) {
-        final int index = mMap.indexOfKey(key);
-        if (index >= 0) {
-            return mMap.valueAt(index);
-        }
-        final int value = mMappingFunction.applyAsInt(key);
-        mMap.append(key, value);
-        return value;
-    }
-}
diff --git a/services/core/java/com/android/server/textservices/TextServicesManagerService.java b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
index d4aa59d..e0bac93 100644
--- a/services/core/java/com/android/server/textservices/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/textservices/TextServicesManagerService.java
@@ -43,7 +43,6 @@
 import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.inputmethod.InputMethodSystemProperty;
 import android.view.textservice.SpellCheckerInfo;
 import android.view.textservice.SpellCheckerSubtype;
 
@@ -86,10 +85,6 @@
     private final UserManager mUserManager;
     private final Object mLock = new Object();
 
-    @NonNull
-    @GuardedBy("mLock")
-    private final LazyIntToIntMap mSpellCheckerOwnerUserIdMap;
-
     private static class TextServicesData {
         @UserIdInt
         private final int mUserId;
@@ -312,9 +307,6 @@
 
     void onStopUser(@UserIdInt int userId) {
         synchronized (mLock) {
-            // Clear user ID mapping table.
-            mSpellCheckerOwnerUserIdMap.delete(userId);
-
             // Clean per-user data
             TextServicesData tsd = mUserData.get(userId);
             if (tsd == null) return;
@@ -334,33 +326,12 @@
     public TextServicesManagerService(Context context) {
         mContext = context;
         mUserManager = mContext.getSystemService(UserManager.class);
-        mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> {
-            if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    final UserInfo parent = mUserManager.getProfileParent(callingUserId);
-                    return (parent != null) ? parent.id : callingUserId;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-            } else {
-                return callingUserId;
-            }
-        });
-
         mMonitor = new TextServicesMonitor();
         mMonitor.register(context, null, UserHandle.ALL, true);
     }
 
     @GuardedBy("mLock")
     private void initializeInternalStateLocked(@UserIdInt int userId) {
-        // When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users
-        // will never have non-null TextServicesData for their user ID.
-        if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
-                && userId != mSpellCheckerOwnerUserIdMap.get(userId)) {
-            return;
-        }
-
         TextServicesData tsd = mUserData.get(userId);
         if (tsd == null) {
             tsd = new TextServicesData(userId, mContext);
@@ -506,8 +477,7 @@
     @Nullable
     private SpellCheckerInfo getCurrentSpellCheckerForUser(@UserIdInt int userId) {
         synchronized (mLock) {
-            final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(userId);
-            final TextServicesData data = mUserData.get(spellCheckerOwnerUserId);
+            final TextServicesData data = mUserData.get(userId);
             return data != null ? data.getCurrentSpellChecker() : null;
         }
     }
@@ -790,27 +760,7 @@
     @GuardedBy("mLock")
     @Nullable
     private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) {
-        final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId);
-        final TextServicesData data = mUserData.get(spellCheckerOwnerUserId);
-        if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
-            if (spellCheckerOwnerUserId != callingUserId) {
-                // Calling process is running under child profile.
-                if (data == null) {
-                    return null;
-                }
-                final SpellCheckerInfo info = data.getCurrentSpellChecker();
-                if (info == null) {
-                    return null;
-                }
-                final ServiceInfo serviceInfo = info.getServiceInfo();
-                if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
-                    // To be conservative, non pre-installed spell checker services are not allowed
-                    // to be used for child profiles.
-                    return null;
-                }
-            }
-        }
-        return data;
+        return mUserData.get(callingUserId);
     }
 
     private static final class SessionRequest {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9908b36..b0f1e5d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1172,8 +1172,16 @@
                 return false;
             }
             final int displayId = display.getDisplayId();
-            return displayId == DEFAULT_DISPLAY
-                    || mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
+            if (displayId == DEFAULT_DISPLAY) {
+                return true;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
 
         void forEachDisplayConnector(Consumer<DisplayConnector> action) {
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 637ad03..fe4811d 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -29,6 +29,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.os.Build.VERSION_CODES.N;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_PRIVATE;
 import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
@@ -55,18 +56,24 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.am.EventLogTags;
 
 import java.io.PrintWriter;
@@ -156,6 +163,9 @@
     // Used in updating the display size
     private Point mTmpDisplaySize = new Point();
 
+    // Used in updating override configurations
+    private final Configuration mTempConfig = new Configuration();
+
     private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
 
     ActivityDisplay(RootActivityContainer root, Display display) {
@@ -999,6 +1009,89 @@
         return mStacks.indexOf(stack);
     }
 
+    boolean updateDisplayOverrideConfigurationLocked() {
+        Configuration values = new Configuration();
+        mDisplayContent.computeScreenConfiguration(values);
+
+        if (mService.mWindowManager != null) {
+            final Message msg = PooledLambda.obtainMessage(
+                    ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal,
+                    mDisplayId);
+            mService.mH.sendMessage(msg);
+        }
+
+        Settings.System.clearConfiguration(values);
+        updateDisplayOverrideConfigurationLocked(values, null /* starting */,
+                false /* deferResume */, mService.mTmpUpdateConfigurationResult);
+        return mService.mTmpUpdateConfigurationResult.changes != 0;
+    }
+
+    /**
+     * Updates override configuration specific for the selected display. If no config is provided,
+     * new one will be computed in WM based on current display info.
+     */
+    boolean updateDisplayOverrideConfigurationLocked(Configuration values,
+            ActivityRecord starting, boolean deferResume,
+            ActivityTaskManagerService.UpdateConfigurationResult result) {
+
+        int changes = 0;
+        boolean kept = true;
+
+        if (mService.mWindowManager != null) {
+            mService.mWindowManager.deferSurfaceLayout();
+        }
+        try {
+            if (values != null) {
+                if (mDisplayId == DEFAULT_DISPLAY) {
+                    // Override configuration of the default display duplicates global config, so
+                    // we're calling global config update instead for default display. It will also
+                    // apply the correct override config.
+                    changes = mService.updateGlobalConfigurationLocked(values,
+                            false /* initLocale */, false /* persistent */,
+                            UserHandle.USER_NULL /* userId */, deferResume);
+                } else {
+                    changes = performDisplayOverrideConfigUpdate(values, deferResume);
+                }
+            }
+
+            kept = mService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
+        } finally {
+            if (mService.mWindowManager != null) {
+                mService.mWindowManager.continueSurfaceLayout();
+            }
+        }
+
+        if (result != null) {
+            result.changes = changes;
+            result.activityRelaunched = !kept;
+        }
+        return kept;
+    }
+
+    int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume) {
+        mTempConfig.setTo(getRequestedOverrideConfiguration());
+        final int changes = mTempConfig.updateFrom(values);
+        if (changes != 0) {
+            Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
+                    + mTempConfig + " for displayId=" + mDisplayId);
+            onRequestedOverrideConfigurationChanged(mTempConfig);
+
+            final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+            if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) {
+                mService.mAppWarnings.onDensityChanged();
+
+                // Post message to start process to avoid possible deadlock of calling into AMS with
+                // the ATMS lock held.
+                final Message msg = PooledLambda.obtainMessage(
+                        ActivityManagerInternal::killAllBackgroundProcessesExcept,
+                        mService.mAmInternal, N,
+                        ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+                mService.mH.sendMessage(msg);
+            }
+        }
+        return changes;
+    }
+
     @Override
     public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
         final int currRotation =
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 8455f43..18a57ae 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -3263,6 +3263,9 @@
                 // tell WindowManager that r is visible even though it is at the back of the stack.
                 r.setVisibility(true);
                 ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+                // Go ahead to execute app transition for this activity since the app transition
+                // will not be triggered through the resume channel.
+                getDisplay().mDisplayContent.executeAppTransition();
             } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
                 // Figure out if we are transitioning from another activity that is
                 // "has the same starting icon" as the next one.  This allows the
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a6e9c05..feb4a36 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2072,7 +2072,7 @@
         r.mLaunchTaskBehind = false;
         mRecentTasks.add(task);
         mService.getTaskChangeNotificationController().notifyTaskStackChanged();
-        r.setVisibility(false);
+        stack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
 
         // When launching tasks behind, update the last active time of the top task after the new
         // task has been shown briefly
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bbc1d7f..54eb07f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1846,7 +1846,7 @@
         // of this in the record so that we can skip it when trying to find
         // the top running activity.
         mDoResume = doResume;
-        if (!doResume || !r.okToShowLocked()) {
+        if (!doResume || !r.okToShowLocked() || mLaunchTaskBehind) {
             r.delayedResume = true;
             mDoResume = false;
         }
@@ -2680,7 +2680,8 @@
 
         if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
                  || mPreferredDisplayId != DEFAULT_DISPLAY) {
-            final boolean onTop = aOptions == null || !aOptions.getAvoidMoveToFront();
+            final boolean onTop =
+                    (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;
             final ActivityStack stack =
                     mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams);
             return stack;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 20ccf07..971a24d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -459,7 +459,7 @@
 
     boolean mSuppressResizeConfigChanges;
 
-    private final UpdateConfigurationResult mTmpUpdateConfigurationResult =
+    final UpdateConfigurationResult mTmpUpdateConfigurationResult =
             new UpdateConfigurationResult();
 
     static final class UpdateConfigurationResult {
@@ -631,7 +631,7 @@
     /** If non-null, we are tracking the time the user spends in the currently focused app. */
     AppTimeTracker mCurAppTimeTracker;
 
-    private AppWarnings mAppWarnings;
+    AppWarnings mAppWarnings;
 
     /**
      * Packages that the user has asked to have run in screen size
@@ -4388,46 +4388,6 @@
     }
 
     @Override
-    public boolean updateDisplayOverrideConfiguration(Configuration values, int displayId) {
-        mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateDisplayOverrideConfiguration()");
-
-        synchronized (mGlobalLock) {
-            // Check if display is initialized in AM.
-            if (!mRootActivityContainer.isDisplayAdded(displayId)) {
-                // Call might come when display is not yet added or has already been removed.
-                if (DEBUG_CONFIGURATION) {
-                    Slog.w(TAG, "Trying to update display configuration for non-existing displayId="
-                            + displayId);
-                }
-                return false;
-            }
-
-            if (values == null && mWindowManager != null) {
-                // sentinel: fetch the current configuration from the window manager
-                values = mWindowManager.computeNewConfiguration(displayId);
-            }
-
-            if (mWindowManager != null) {
-                final Message msg = PooledLambda.obtainMessage(
-                        ActivityManagerInternal::updateOomLevelsForDisplay, mAmInternal, displayId);
-                mH.sendMessage(msg);
-            }
-
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                if (values != null) {
-                    Settings.System.clearConfiguration(values);
-                }
-                updateDisplayOverrideConfigurationLocked(values, null /* starting */,
-                        false /* deferResume */, displayId, mTmpUpdateConfigurationResult);
-                return mTmpUpdateConfigurationResult.changes != 0;
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
-    }
-
-    @Override
     public boolean updateConfiguration(Configuration values) {
         mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
 
@@ -5165,8 +5125,12 @@
     }
 
     /** Update default (global) configuration and notify listeners about changes. */
-    private int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
+    int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
             boolean persistent, int userId, boolean deferResume) {
+
+        final ActivityDisplay defaultDisplay =
+                mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY);
+
         mTempConfig.setTo(getGlobalConfiguration());
         final int changes = mTempConfig.updateFrom(values);
         if (changes == 0) {
@@ -5174,7 +5138,7 @@
             // setting WindowManagerService.mWaitingForConfig to true, it is important that we call
             // performDisplayOverrideConfigUpdate in order to send the new display configuration
             // (even if there are no actual changes) to unfreeze the window.
-            performDisplayOverrideConfigUpdate(values, deferResume, DEFAULT_DISPLAY);
+            defaultDisplay.performDisplayOverrideConfigUpdate(values, deferResume);
             return 0;
         }
 
@@ -5272,82 +5236,12 @@
 
         // Override configuration of the default display duplicates global config, so we need to
         // update it also. This will also notify WindowManager about changes.
-        performDisplayOverrideConfigUpdate(mRootActivityContainer.getConfiguration(), deferResume,
-                DEFAULT_DISPLAY);
+        defaultDisplay.performDisplayOverrideConfigUpdate(mRootActivityContainer.getConfiguration(),
+                deferResume);
 
         return changes;
     }
 
-    boolean updateDisplayOverrideConfigurationLocked(Configuration values, ActivityRecord starting,
-            boolean deferResume, int displayId) {
-        return updateDisplayOverrideConfigurationLocked(values, starting, deferResume /* deferResume */,
-                displayId, null /* result */);
-    }
-
-    /**
-     * Updates override configuration specific for the selected display. If no config is provided,
-     * new one will be computed in WM based on current display info.
-     */
-    boolean updateDisplayOverrideConfigurationLocked(Configuration values,
-            ActivityRecord starting, boolean deferResume, int displayId,
-            ActivityTaskManagerService.UpdateConfigurationResult result) {
-        int changes = 0;
-        boolean kept = true;
-
-        if (mWindowManager != null) {
-            mWindowManager.deferSurfaceLayout();
-        }
-        try {
-            if (values != null) {
-                if (displayId == DEFAULT_DISPLAY) {
-                    // Override configuration of the default display duplicates global config, so
-                    // we're calling global config update instead for default display. It will also
-                    // apply the correct override config.
-                    changes = updateGlobalConfigurationLocked(values, false /* initLocale */,
-                            false /* persistent */, UserHandle.USER_NULL /* userId */, deferResume);
-                } else {
-                    changes = performDisplayOverrideConfigUpdate(values, deferResume, displayId);
-                }
-            }
-
-            kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
-        } finally {
-            if (mWindowManager != null) {
-                mWindowManager.continueSurfaceLayout();
-            }
-        }
-
-        if (result != null) {
-            result.changes = changes;
-            result.activityRelaunched = !kept;
-        }
-        return kept;
-    }
-
-    private int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume,
-            int displayId) {
-        mTempConfig.setTo(mRootActivityContainer.getDisplayOverrideConfiguration(displayId));
-        final int changes = mTempConfig.updateFrom(values);
-        if (changes != 0) {
-            Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
-                    + mTempConfig + " for displayId=" + displayId);
-            mRootActivityContainer.setDisplayOverrideConfiguration(mTempConfig, displayId);
-
-            final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
-            if (isDensityChange && displayId == DEFAULT_DISPLAY) {
-                mAppWarnings.onDensityChanged();
-
-                // Post message to start process to avoid possible deadlock of calling into AMS with
-                // the ATMS lock held.
-                final Message msg = PooledLambda.obtainMessage(
-                        ActivityManagerInternal::killAllBackgroundProcessesExcept, mAmInternal,
-                        N, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
-                mH.sendMessage(msg);
-            }
-        }
-        return changes;
-    }
-
     private void updateEventDispatchingLocked(boolean booted) {
         mWindowManager.setEventDispatching(booted && !mShuttingDown);
     }
@@ -5786,7 +5680,7 @@
     }
 
     /** Applies latest configuration and/or visibility updates if needed. */
-    private boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
+    boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
         boolean kept = true;
         final ActivityStack mainStack = mRootActivityContainer.getTopDisplayFocusedStack();
         // mainStack is null during startup.
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 03cae42..8007c0f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -33,6 +33,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
 import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_UNSET;
 import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN;
 
@@ -79,6 +80,7 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.logWithStack;
 import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 
@@ -540,6 +542,18 @@
                 // If the app was already visible, don't reset the waitingToShow state.
                 if (isHidden()) {
                     waitingToShow = true;
+
+                    // Let's reset the draw state in order to prevent the starting window to be
+                    // immediately dismissed when the app still has the surface.
+                    forAllWindows(w -> {
+                        if (w.mWinAnimator.mDrawState == HAS_DRAWN) {
+                            w.mWinAnimator.resetDrawState();
+
+                            // Force add to mResizingWindows, so that we are guaranteed to get
+                            // another reportDrawn callback.
+                            w.resetLastContentInsets();
+                        }
+                    },  true /* traverseTopToBottom */);
                 }
             }
 
@@ -568,8 +582,7 @@
                 displayContent.mClosingApps.add(this);
                 mEnteringAnimation = false;
             }
-            if (appTransition.getAppTransition()
-                    == WindowManager.TRANSIT_TASK_OPEN_BEHIND) {
+            if (appTransition.getAppTransition() == TRANSIT_TASK_OPEN_BEHIND) {
                 // We're launchingBehind, add the launching activity to mOpeningApps.
                 final WindowState win = getDisplayContent().findFocusedWindow();
                 if (win != null) {
@@ -580,7 +593,6 @@
                                     + " adding " + focusedToken + " to mOpeningApps");
                         }
                         // Force animation to be loaded.
-                        focusedToken.setHidden(true);
                         displayContent.mOpeningApps.add(focusedToken);
                     }
                 }
@@ -607,9 +619,14 @@
         // * token is transitioning visibility state
         // * or the token was marked as hidden and is exiting before we had a chance to play the
         // transition animation
-        // * or this is an opening app and windows are being replaced.
+        // * or this is an opening app and windows are being replaced
+        // * or the token is the opening app and visible while opening task behind existing one.
+        final DisplayContent displayContent = getDisplayContent();
         boolean visibilityChanged = false;
-        if (isHidden() == visible || (isHidden() && mIsExiting) || (visible && waitingForReplacement())) {
+        if (isHidden() == visible || (isHidden() && mIsExiting)
+                || (visible && waitingForReplacement())
+                || (visible && displayContent.mOpeningApps.contains(this)
+                && displayContent.mAppTransition.getAppTransition() == TRANSIT_TASK_OPEN_BEHIND)) {
             final AccessibilityController accessibilityController =
                     mWmService.mAccessibilityController;
             boolean changed = false;
@@ -662,13 +679,13 @@
             }
 
             if (changed) {
-                getDisplayContent().getInputMonitor().setUpdateInputWindowsNeededLw();
+                displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
                 if (performLayout) {
                     mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                             false /*updateInputWindows*/);
                     mWmService.mWindowPlacerLocked.performSurfacePlacement();
                 }
-                getDisplayContent().getInputMonitor().updateInputWindowsLw(false /*force*/);
+                displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
             }
         }
         mUseTransferredAnimation = false;
@@ -707,14 +724,14 @@
                 setClientHidden(!visible);
             }
 
-            if (!getDisplayContent().mClosingApps.contains(this)
-                    && !getDisplayContent().mOpeningApps.contains(this)) {
+            if (!displayContent.mClosingApps.contains(this)
+                    && !displayContent.mOpeningApps.contains(this)) {
                 // The token is not closing nor opening, so even if there is an animation set, that
                 // doesn't mean that it goes through the normal app transition cycle so we have
                 // to inform the docked controller about visibility change.
                 // TODO(multi-display): notify docked divider on all displays where visibility was
                 // affected.
-                getDisplayContent().getDockedDividerController().notifyAppVisibilityChanged();
+                displayContent.getDockedDividerController().notifyAppVisibilityChanged();
 
                 // Take the screenshot before possibly hiding the WSA, otherwise the screenshot
                 // will not be taken.
@@ -731,7 +748,7 @@
             // no animation but there will still be a transition set.
             // We still need to delay hiding the surface such that it
             // can be synchronized with showing the next surface in the transition.
-            if (isHidden() && !delayed && !getDisplayContent().mAppTransition.isTransitionSet()) {
+            if (isHidden() && !delayed && !displayContent.mAppTransition.isTransitionSet()) {
                 SurfaceControl.openTransaction();
                 for (int i = mChildren.size() - 1; i >= 0; i--) {
                     mChildren.get(i).mWinAnimator.hide("immediately hidden");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4d548bf..e2d5928 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -140,7 +140,7 @@
 import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
-import static com.android.server.wm.utils.RegionUtils.forEachRect;
+import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
 import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
 
 import android.animation.AnimationHandler;
@@ -1213,10 +1213,29 @@
     }
 
     /** Notify the configuration change of this display. */
-    void sendNewConfiguration() {
+    void postNewConfigurationToHandler() {
         mWmService.mH.obtainMessage(SEND_NEW_CONFIGURATION, this).sendToTarget();
     }
 
+    void sendNewConfiguration() {
+        synchronized (mWmService.mGlobalLock) {
+            final boolean configUpdated = mAcitvityDisplay
+                    .updateDisplayOverrideConfigurationLocked();
+            if (!configUpdated) {
+                // Something changed (E.g. device rotation), but no configuration update is needed.
+                // E.g. changing device rotation by 180 degrees. Go ahead and perform surface
+                // placement to unfreeze the display since we froze it when the rotation was updated
+                // in DisplayContent#updateRotationUnchecked.
+                if (mWaitingForConfig) {
+                    mWaitingForConfig = false;
+                    mWmService.mLastFinishedFreezeSource = "config-unchanged";
+                    setLayoutNeeded();
+                    mWmService.mWindowPlacerLocked.performSurfacePlacement();
+                }
+            }
+        }
+    }
+
     @Override
     boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
             ConfigurationContainer requestingContainer) {
@@ -1232,8 +1251,8 @@
 
         if (handled && requestingContainer instanceof ActivityRecord) {
             final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
-            final boolean kept = mWmService.mAtmService.updateDisplayOverrideConfigurationLocked(
-                    config, activityRecord, false /* deferResume */, getDisplayId());
+            final boolean kept = mAcitvityDisplay.updateDisplayOverrideConfigurationLocked(
+                    config, activityRecord, false /* deferResume */, null /* result */);
             activityRecord.frozenBeforeDestroy = true;
             if (!kept) {
                 mWmService.mAtmService.mRootActivityContainer.resumeFocusedStacksTopActivities();
@@ -1241,8 +1260,8 @@
         } else {
             // We have a new configuration to push so we need to update ATMS for now.
             // TODO: Clean up display configuration push between ATMS and WMS after unification.
-            mWmService.mAtmService.updateDisplayOverrideConfigurationLocked(
-                    config, null /* starting */, false /* deferResume */, getDisplayId());
+            mAcitvityDisplay.updateDisplayOverrideConfigurationLocked(
+                    config, null /* starting */, false /* deferResume */, null);
         }
         return handled;
     }
@@ -1332,7 +1351,7 @@
     boolean updateRotationAndSendNewConfigIfNeeded() {
         final boolean changed = updateRotationUnchecked(false /* forceUpdate */);
         if (changed) {
-            sendNewConfiguration();
+            postNewConfigurationToHandler();
         }
         return changed;
     }
@@ -1351,7 +1370,7 @@
      * Update rotation of the DisplayContent with an option to force the update. This updates
      * the container's perception of rotation and, depending on the top activities, will freeze
      * the screen or start seamless rotation. The display itself gets rotated in
-     * {@link #applyRotationLocked} during {@link WindowManagerService#sendNewConfiguration}.
+     * {@link #applyRotationLocked} during {@link DisplayContent#sendNewConfiguration}.
      *
      * @param forceUpdate Force the rotation update. Sometimes in WM we might skip updating
      *                    orientation because we're waiting for some rotation to finish or display
@@ -3711,7 +3730,7 @@
                 if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
                 if (updateOrientationFromAppTokens()) {
                     setLayoutNeeded();
-                    sendNewConfiguration();
+                    postNewConfigurationToHandler();
                 }
             }
 
@@ -5162,7 +5181,7 @@
         final int[] remainingLeftRight =
                 {mSystemGestureExclusionLimit, mSystemGestureExclusionLimit};
 
-        // Traverse all windows bottom up to assemble the gesture exclusion rects.
+        // Traverse all windows top down to assemble the gesture exclusion rects.
         // For each window, we only take the rects that fall within its touchable region.
         forAllWindows(w -> {
             if (w.cantReceiveTouchInput() || !w.isVisible()
@@ -5170,23 +5189,25 @@
                     || unhandled.isEmpty()) {
                 return;
             }
-            final boolean modal =
-                    (w.mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
 
             // Get the touchable region of the window, and intersect with where the screen is still
             // touchable, i.e. touchable regions on top are not covering it yet.
-            w.getTouchableRegion(touchableRegion);
+            w.getEffectiveTouchableRegion(touchableRegion);
             touchableRegion.op(unhandled, Op.INTERSECT);
 
-            rectListToRegion(w.getSystemGestureExclusion(), local);
+            if (w.isImplicitlyExcludingAllSystemGestures()) {
+                local.set(touchableRegion);
+            } else {
+                rectListToRegion(w.getSystemGestureExclusion(), local);
 
-            // Transform to display coordinates
-            local.scale(w.mGlobalScale);
-            final Rect frame = w.getWindowFrames().mFrame;
-            local.translate(frame.left, frame.top);
+                // Transform to display coordinates
+                local.scale(w.mGlobalScale);
+                final Rect frame = w.getWindowFrames().mFrame;
+                local.translate(frame.left, frame.top);
 
-            // A window can only exclude system gestures where it is actually touchable
-            local.op(touchableRegion, Op.INTERSECT);
+                // A window can only exclude system gestures where it is actually touchable
+                local.op(touchableRegion, Op.INTERSECT);
+            }
 
             // Apply restriction if necessary.
             if (needsGestureExclusionRestrictions(w, mLastDispatchedSystemUiVisibility)) {
@@ -5245,13 +5266,13 @@
         r.op(edge, Op.INTERSECT);
 
         final int[] remaining = {limit};
-        forEachRect(r, rect -> {
+        forEachRectReverse(r, rect -> {
             if (remaining[0] <= 0) {
                 return;
             }
             final int height = rect.height();
             if (height > remaining[0]) {
-                rect.bottom = rect.top + remaining[0];
+                rect.top = rect.bottom - remaining[0];
             }
             remaining[0] -= height;
             global.op(rect, Op.UNION);
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 1d76a71..b1bc2197 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -569,7 +569,6 @@
             mMaximizeMeetFraction = getClipRevealMeetFraction(stack);
             animDuration = (long) (mAnimationDuration * mMaximizeMeetFraction);
         }
-        mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock);
         final int size = mDockedStackListeners.beginBroadcast();
         for (int i = 0; i < size; ++i) {
             final IDockedStackListener listener = mDockedStackListeners.getBroadcastItem(i);
@@ -581,6 +580,9 @@
             }
         }
         mDockedStackListeners.finishBroadcast();
+        // Only notify ATM after we update the remote listeners, otherwise it may trigger another
+        // minimize change, which would lead to an inversion of states send to the listeners
+        mService.mAtmInternal.notifyDockedStackMinimizedChanged(minimizedDock);
     }
 
     void notifyDockSideChanged(int newDockSide) {
diff --git a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
index c0f53b8..315de91 100644
--- a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
+++ b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
@@ -16,60 +16,102 @@
 
 package com.android.server.wm;
 
+import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+
 import android.annotation.NonNull;
-import android.os.SystemProperties;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
 import android.util.ArraySet;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+
+import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 /**
  * A Blacklist for packages that should force the display out of high refresh rate.
  */
 class HighRefreshRateBlacklist {
 
-    private static final String SYSPROP_KEY = "ro.window_manager.high_refresh_rate_blacklist";
-    private static final String SYSPROP_KEY_LENGTH_SUFFIX = "_length";
-    private static final String SYSPROP_KEY_ENTRY_SUFFIX = "_entry";
-    private static final int MAX_ENTRIES = 50;
+    private final ArraySet<String> mBlacklistedPackages = new ArraySet<>();
+    @NonNull
+    private final String[] mDefaultBlacklist;
+    private final Object mLock = new Object();
 
-    private ArraySet<String> mBlacklistedPackages = new ArraySet<>();
-
-    static HighRefreshRateBlacklist create() {
-        return new HighRefreshRateBlacklist(new SystemPropertyGetter() {
+    static HighRefreshRateBlacklist create(@NonNull Resources r) {
+        return new HighRefreshRateBlacklist(r, new DeviceConfigInterface() {
             @Override
-            public int getInt(String key, int def) {
-                return SystemProperties.getInt(key, def);
+            public @Nullable String getProperty(@NonNull String namespace, @NonNull String name) {
+                return DeviceConfig.getProperty(namespace, name);
             }
-
-            @Override
-            public String get(String key) {
-                return SystemProperties.get(key);
+            public void addOnPropertiesChangedListener(@NonNull String namespace,
+                    @NonNull Executor executor,
+                    @NonNull DeviceConfig.OnPropertiesChangedListener listener) {
+                DeviceConfig.addOnPropertiesChangedListener(namespace, executor, listener);
             }
         });
     }
 
     @VisibleForTesting
-    HighRefreshRateBlacklist(SystemPropertyGetter propertyGetter) {
+    HighRefreshRateBlacklist(Resources r, DeviceConfigInterface deviceConfig) {
+        mDefaultBlacklist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
+        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                BackgroundThread.getExecutor(), new OnPropertiesChangedListener());
+        final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                KEY_HIGH_REFRESH_RATE_BLACKLIST);
+        updateBlacklist(property);
+    }
 
-        // Read and populate the blacklist
-        final int length = Math.min(
-                propertyGetter.getInt(SYSPROP_KEY + SYSPROP_KEY_LENGTH_SUFFIX, 0),
-                MAX_ENTRIES);
-        for (int i = 1; i <= length; i++) {
-            final String packageName = propertyGetter.get(
-                    SYSPROP_KEY + SYSPROP_KEY_ENTRY_SUFFIX + i);
-            if (!packageName.isEmpty()) {
-                mBlacklistedPackages.add(packageName);
+    private void updateBlacklist(@Nullable String property) {
+        synchronized (mLock) {
+            mBlacklistedPackages.clear();
+            if (property != null) {
+                String[] packages = property.split(",");
+                for (String pkg : packages) {
+                    String pkgName = pkg.trim();
+                    if (!pkgName.isEmpty()) {
+                        mBlacklistedPackages.add(pkgName);
+                    }
+                }
+            } else {
+                // If there's no config, or the config has been deleted, fallback to the device's
+                // default blacklist
+                for (String pkg : mDefaultBlacklist) {
+                    mBlacklistedPackages.add(pkg);
+                }
             }
         }
     }
 
     boolean isBlacklisted(String packageName) {
-        return mBlacklistedPackages.contains(packageName);
+        synchronized (mLock) {
+            return mBlacklistedPackages.contains(packageName);
+        }
+    }
+    void dump(PrintWriter pw) {
+        pw.println("High Refresh Rate Blacklist");
+        pw.println("  Packages:");
+        synchronized (mLock) {
+            for (String pkg : mBlacklistedPackages) {
+                pw.println("    " + pkg);
+            }
+        }
     }
 
-    interface SystemPropertyGetter {
-        int getInt(String key, int def);
-        @NonNull String get(String key);
+    interface DeviceConfigInterface {
+        @Nullable String getProperty(@NonNull String namespace, @NonNull String name);
+        void addOnPropertiesChangedListener(@NonNull String namespace, @NonNull Executor executor,
+                @NonNull DeviceConfig.OnPropertiesChangedListener listener);
+    }
+
+    private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener {
+        public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+            updateBlacklist(
+                    properties.getString(KEY_HIGH_REFRESH_RATE_BLACKLIST, null /*default*/));
+        }
     }
 }
+
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 6b500967..b16d956 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -133,7 +133,7 @@
     @Override
     public void notifyConfigurationChanged() {
         // TODO(multi-display): Notify proper displays that are associated with this input device.
-        mService.sendNewConfiguration(DEFAULT_DISPLAY);
+        mService.mRoot.getDisplayContent(DEFAULT_DISPLAY).sendNewConfiguration();
 
         synchronized (mInputDevicesReadyMonitor) {
             if (!mInputDevicesReady) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index c03dabe..163be1e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
 
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
 import static com.android.server.wm.AnimationAdapterProto.REMOTE;
@@ -651,8 +652,8 @@
     }
 
     boolean isWallpaperVisible(WindowState w) {
-        return w != null && w.mAppToken != null && mTargetAppToken == w.mAppToken
-                && isTargetOverWallpaper();
+        return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION && w.mAppToken != null
+                && mTargetAppToken == w.mAppToken && isTargetOverWallpaper();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index d58c613..66d42db 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -317,11 +317,6 @@
         return activityDisplay;
     }
 
-    /** Check if display with specified id is added to the list. */
-    boolean isDisplayAdded(int displayId) {
-        return getActivityDisplayOrCreate(displayId) != null;
-    }
-
     ActivityRecord getDefaultDisplayHomeActivity() {
         return getDefaultDisplayHomeActivityForUser(mCurrentUser);
     }
@@ -656,9 +651,13 @@
             starting.frozenBeforeDestroy = true;
         }
 
-        // Update the configuration of the activities on the display.
-        return mService.updateDisplayOverrideConfigurationLocked(config, starting, deferResume,
-                displayId);
+        if (displayContent != null && displayContent.mAcitvityDisplay != null) {
+            // Update the configuration of the activities on the display.
+            return displayContent.mAcitvityDisplay.updateDisplayOverrideConfigurationLocked(config,
+                    starting, deferResume, null /* result */);
+        } else {
+            return true;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54f712c..f52bde9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -32,6 +32,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.myPid;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE;
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -846,6 +847,7 @@
     boolean mWindowsChanged = false;
 
     int mSystemGestureExclusionLimitDp;
+    boolean mSystemGestureExcludedByPreQStickyImmersive;
 
     public interface WindowChangeListener {
         public void windowsChanged();
@@ -854,7 +856,7 @@
 
     final Configuration mTempConfiguration = new Configuration();
 
-    final HighRefreshRateBlacklist mHighRefreshRateBlacklist = HighRefreshRateBlacklist.create();
+    final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
 
     // If true, only the core apps and services are being launched because the device
     // is in a special boot mode, such as being encrypted or waiting for a decryption password.
@@ -1141,16 +1143,26 @@
                 this, mInputManager, mActivityTaskManager, mH.getLooper());
         mDragDropController = new DragDropController(this, mH.getLooper());
 
+        mHighRefreshRateBlacklist = HighRefreshRateBlacklist.create(context.getResources());
+
         mSystemGestureExclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
                 DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                         KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
+        mSystemGestureExcludedByPreQStickyImmersive =
+                DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                        KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 new HandlerExecutor(mH), properties -> {
                     synchronized (mGlobalLock) {
                         final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
                                 properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
-                        if (mSystemGestureExclusionLimitDp != exclusionLimitDp) {
+                        final boolean excludedByPreQSticky = DeviceConfig.getBoolean(
+                                DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                                KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
+                        if (mSystemGestureExcludedByPreQStickyImmersive != excludedByPreQSticky
+                                || mSystemGestureExclusionLimitDp != exclusionLimitDp) {
                             mSystemGestureExclusionLimitDp = exclusionLimitDp;
+                            mSystemGestureExcludedByPreQStickyImmersive = excludedByPreQSticky;
                             mRoot.forAllDisplays(DisplayContent::updateSystemGestureExclusionLimit);
                         }
                     }
@@ -1597,10 +1609,10 @@
             if (win.isVisibleOrAdding() && displayContent.updateOrientationFromAppTokens()) {
                 reportNewConfig = true;
             }
-        }
 
-        if (reportNewConfig) {
-            sendNewConfiguration(displayId);
+            if (reportNewConfig) {
+                displayContent.sendNewConfiguration();
+            }
         }
 
         Binder.restoreCallingIdentity(origId);
@@ -1638,7 +1650,7 @@
             final Display display = mDisplayManager.getDisplay(displayId);
 
             if (display != null) {
-                displayContent = mRoot.createDisplayContent(display, null /* controller */);
+                displayContent = mRoot.createDisplayContent(display, null /* activityDisplay */);
             }
         }
 
@@ -2280,13 +2292,15 @@
                 Slog.v(TAG_WM, "Relayout complete " + win + ": outFrame=" + outFrame.toShortString());
             }
             win.mInRelayout = false;
+
+            if (configChanged) {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                        "relayoutWindow: postNewConfigurationToHandler");
+                displayContent.sendNewConfiguration();
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+            }
         }
 
-        if (configChanged) {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: sendNewConfiguration");
-            sendNewConfiguration(displayId);
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
         Binder.restoreCallingIdentity(origId);
         return result;
     }
@@ -3737,7 +3751,7 @@
                         layoutNeeded = true;
                     }
                     if (rotationChanged || alwaysSendConfiguration) {
-                        displayContent.sendNewConfiguration();
+                        displayContent.postNewConfigurationToHandler();
                     }
                 }
 
@@ -4279,34 +4293,6 @@
         }
     }
 
-    /**
-     * Instruct the Activity Manager to fetch and update the current display's configuration and
-     * broadcast them to config-changed listeners if appropriate.
-     * NOTE: Can't be called with the window manager lock held since it call into activity manager.
-     */
-    void sendNewConfiguration(int displayId) {
-        try {
-            final boolean configUpdated = mActivityTaskManager.updateDisplayOverrideConfiguration(
-                    null /* values */, displayId);
-            if (!configUpdated) {
-                // Something changed (E.g. device rotation), but no configuration update is needed.
-                // E.g. changing device rotation by 180 degrees. Go ahead and perform surface
-                // placement to unfreeze the display since we froze it when the rotation was updated
-                // in DisplayContent#updateRotationUnchecked.
-                synchronized (mGlobalLock) {
-                    final DisplayContent dc = mRoot.getDisplayContent(displayId);
-                    if (dc != null && dc.mWaitingForConfig) {
-                        dc.mWaitingForConfig = false;
-                        mLastFinishedFreezeSource = "config-unchanged";
-                        dc.setLayoutNeeded();
-                        mWindowPlacerLocked.performSurfacePlacement();
-                    }
-                }
-            }
-        } catch (RemoteException e) {
-        }
-    }
-
     public Configuration computeNewConfiguration(int displayId) {
         synchronized (mGlobalLock) {
             return computeNewConfigurationLocked(displayId);
@@ -4730,7 +4716,7 @@
                     final DisplayContent displayContent = (DisplayContent) msg.obj;
                     removeMessages(SEND_NEW_CONFIGURATION, displayContent);
                     if (displayContent.isReady()) {
-                        sendNewConfiguration(displayContent.getDisplayId());
+                        displayContent.sendNewConfiguration();
                     } else {
                         // Message could come after display has already been removed.
                         if (DEBUG_CONFIGURATION) {
@@ -5214,7 +5200,7 @@
             displayContent.mWaitingForConfig = true;
             startFreezingDisplayLocked(0 /* exitAnim */,
                     0 /* enterAnim */, displayContent);
-            displayContent.sendNewConfiguration();
+            displayContent.postNewConfigurationToHandler();
         }
 
         mWindowPlacerLocked.performSurfacePlacement();
@@ -5561,7 +5547,7 @@
         }
 
         if (configChanged) {
-            displayContent.sendNewConfiguration();
+            displayContent.postNewConfigurationToHandler();
         }
         mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
     }
@@ -5913,6 +5899,12 @@
         mRoot.dumpTokens(pw, dumpAll);
     }
 
+
+    private void dumpHighRefreshRateBlacklist(PrintWriter pw) {
+        pw.println("WINDOW MANAGER HIGH REFRESH RATE BLACKLIST (dumpsys window refresh)");
+        mHighRefreshRateBlacklist.dump(pw);
+    }
+
     private void dumpTraceStatus(PrintWriter pw) {
         pw.println("WINDOW MANAGER TRACE (dumpsys window trace)");
         pw.print(mWindowTracing.getStatus() + "\n");
@@ -6313,6 +6305,9 @@
             } else if ("trace".equals(cmd)) {
                 dumpTraceStatus(pw);
                 return;
+            } else if ("refresh".equals(cmd)) {
+                dumpHighRefreshRateBlacklist(pw);
+                return;
             } else {
                 // Dumping a single name?
                 if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
@@ -6368,6 +6363,10 @@
                 pw.println(separator);
             }
             dumpTraceStatus(pw);
+            if (dumpAll) {
+                pw.println(separator);
+            }
+            dumpHighRefreshRateBlacklist(pw);
         }
     }
 
@@ -6874,7 +6873,7 @@
                 final long origId = Binder.clearCallingIdentity();
                 try {
                     // direct call since lock is shared.
-                    sendNewConfiguration(displayId);
+                    displayContent.sendNewConfiguration();
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bf874be..8b1baeb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -24,6 +24,8 @@
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.SurfaceControl.Transaction;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -155,6 +157,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -667,6 +670,15 @@
         return true;
     }
 
+    boolean isImplicitlyExcludingAllSystemGestures() {
+        final int immersiveStickyFlags =
+                SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        final boolean immersiveSticky =
+                (mSystemUiVisibility & immersiveStickyFlags) == immersiveStickyFlags;
+        return immersiveSticky && mWmService.mSystemGestureExcludedByPreQStickyImmersive
+                && mAppToken != null && mAppToken.mTargetSdk < Build.VERSION_CODES.Q;
+    }
+
     interface PowerManagerWrapper {
         void wakeUp(long time, @WakeReason int reason, String details);
 
@@ -2051,7 +2063,7 @@
             if (wasVisible) {
                 final DisplayContent displayContent = getDisplayContent();
                 if (displayContent.updateOrientationFromAppTokens()) {
-                    displayContent.sendNewConfiguration();
+                    displayContent.postNewConfigurationToHandler();
                 }
             }
             mWmService.updateFocusedWindowLocked(isFocused()
@@ -3018,6 +3030,25 @@
         subtractTouchExcludeRegionIfNeeded(outRegion);
     }
 
+    /**
+     * Get the effective touchable region in global coordinates.
+     *
+     * In contrast to {@link #getTouchableRegion}, this takes into account
+     * {@link WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL touch modality.}
+     */
+    void getEffectiveTouchableRegion(Region outRegion) {
+        final boolean modal = (mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
+        final DisplayContent dc = getDisplayContent();
+
+        if (modal && dc != null) {
+            outRegion.set(dc.getBounds());
+            cropRegionToStackBoundsIfNeeded(outRegion);
+            subtractTouchExcludeRegionIfNeeded(outRegion);
+        } else {
+            getTouchableRegion(outRegion);
+        }
+    }
+
     private void setTouchableRegionCropIfNeeded(InputWindowHandle handle) {
         final Task task = getTask();
         if (task == null || !task.cropWindowsToStackBounds()) {
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index 8cd6f88..b1b3070 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -20,6 +20,8 @@
 import android.graphics.Region;
 import android.graphics.RegionIterator;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -48,14 +50,21 @@
     /**
      * Applies actions on each rect contained within a {@code Region}.
      *
+     * Order is bottom to top, then right to left.
+     *
      * @param region the given region.
      * @param rectConsumer the action holder.
      */
-    public static void forEachRect(Region region, Consumer<Rect> rectConsumer) {
+    public static void forEachRectReverse(Region region, Consumer<Rect> rectConsumer) {
         final RegionIterator it = new RegionIterator(region);
+        final ArrayList<Rect> rects = new ArrayList<>();
         final Rect rect = new Rect();
         while (it.next(rect)) {
-            rectConsumer.accept(rect);
+            rects.add(new Rect(rect));
         }
+        // TODO: instead of creating an array and reversing it, expose the reverse iterator through
+        //       JNI.
+        Collections.reverse(rects);
+        rects.forEach(rectConsumer);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 48921ea..de8612a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -228,7 +228,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -9403,12 +9402,6 @@
             return false;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
-
-        // TODO When InputMethodManager supports per user calls remove this restriction.
-        if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
-                && !checkCallerIsCurrentUserOrProfile()) {
-            return false;
-        }
         final int callingUserId = mInjector.userHandleGetCallingUserId();
         if (packageList != null) {
             List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
@@ -9464,26 +9457,16 @@
         final int callingUserId = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
             List<String> result = null;
-            // If we have multiple profiles we return the intersection of the
-            // permitted lists. This can happen in cases where we have a device
-            // and profile owner.
-            int[] profileIds = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
-                    ? new int[]{callingUserId}
-                    : mUserManager.getProfileIdsWithDisabled(callingUserId);
-            for (int profileId : profileIds) {
-                // Just loop though all admins, only device or profiles
-                // owners can have permitted lists set.
-                DevicePolicyData policy = getUserDataUnchecked(profileId);
-                final int N = policy.mAdminList.size();
-                for (int j = 0; j < N; j++) {
-                    ActiveAdmin admin = policy.mAdminList.get(j);
-                    List<String> fromAdmin = admin.permittedInputMethods;
-                    if (fromAdmin != null) {
-                        if (result == null) {
-                            result = new ArrayList<String>(fromAdmin);
-                        } else {
-                            result.retainAll(fromAdmin);
-                        }
+            // Only device or profile owners can have permitted lists set.
+            DevicePolicyData policy = getUserDataUnchecked(callingUserId);
+            for (int i = 0; i < policy.mAdminList.size(); i++) {
+                ActiveAdmin admin = policy.mAdminList.get(i);
+                List<String> fromAdmin = admin.permittedInputMethods;
+                if (fromAdmin != null) {
+                    if (result == null) {
+                        result = new ArrayList<String>(fromAdmin);
+                    } else {
+                        result.retainAll(fromAdmin);
                     }
                 }
             }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 699bec2..261a83c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -33,7 +33,6 @@
 import android.content.pm.ResolveInfo;
 import android.util.ArraySet;
 import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSystemProperty;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -61,17 +60,11 @@
 
     @VisibleForTesting
     interface Injector {
-        boolean isPerProfileImeEnabled();
         @NonNull
         List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
     }
 
     private static final class DefaultInjector implements Injector {
-        @Override
-        public boolean isPerProfileImeEnabled() {
-            return InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
-        }
-
         @NonNull
         @Override
         public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
@@ -108,13 +101,7 @@
         // Newly installed system apps are uninstalled when they are not required and are either
         // disallowed or have a launcher icon.
         nonRequiredApps.removeAll(getRequiredApps(provisioningAction, admin.getPackageName()));
-        if (mInjector.isPerProfileImeEnabled()) {
-            nonRequiredApps.removeAll(getSystemInputMethods(userId));
-        } else if (ACTION_PROVISION_MANAGED_DEVICE.equals(provisioningAction)
-                || ACTION_PROVISION_MANAGED_USER.equals(provisioningAction)) {
-            // Don't delete the system input method packages in case of Device owner provisioning.
-            nonRequiredApps.removeAll(getSystemInputMethods(userId));
-        }
+        nonRequiredApps.removeAll(getSystemInputMethods(userId));
         nonRequiredApps.addAll(getDisallowedApps(provisioningAction));
         return nonRequiredApps;
     }
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
new file mode 100644
index 0000000..823a63c
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.tasks.DecryptedChunkOutput;
+
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class DecryptedChunkFileOutputTest {
+    private static final byte[] TEST_CHUNK_1 = {1, 2, 3};
+    private static final byte[] TEST_CHUNK_2 = {4, 5, 6, 7, 8, 9, 10};
+    private static final int TEST_BUFFER_LENGTH =
+            Math.max(TEST_CHUNK_1.length, TEST_CHUNK_2.length);
+
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    private File mOutputFile;
+    private DecryptedChunkFileOutput mDecryptedChunkFileOutput;
+
+    @Before
+    public void setUp() throws Exception {
+        mOutputFile = temporaryFolder.newFile();
+        mDecryptedChunkFileOutput = new DecryptedChunkFileOutput(mOutputFile);
+    }
+
+    @Test
+    public void open_returnsInstance() throws Exception {
+        DecryptedChunkOutput result = mDecryptedChunkFileOutput.open();
+        assertThat(result).isEqualTo(mDecryptedChunkFileOutput);
+    }
+
+    @Test
+    public void open_nonExistentOutputFolder_throwsException() throws Exception {
+        mDecryptedChunkFileOutput =
+                new DecryptedChunkFileOutput(
+                        new File(temporaryFolder.newFolder(), "mOutput/directory"));
+        assertThrows(FileNotFoundException.class, () -> mDecryptedChunkFileOutput.open());
+    }
+
+    @Test
+    public void open_whenRunTwice_throwsException() throws Exception {
+        mDecryptedChunkFileOutput.open();
+        assertThrows(IllegalStateException.class, () -> mDecryptedChunkFileOutput.open());
+    }
+
+    @Test
+    public void processChunk_beforeOpen_throwsException() throws Exception {
+        assertThrows(IllegalStateException.class,
+                () -> mDecryptedChunkFileOutput.processChunk(new byte[0], 0));
+    }
+
+    @Test
+    public void processChunk_writesChunksToFile() throws Exception {
+        processTestChunks();
+
+        assertThat(Files.toByteArray(mOutputFile))
+                .isEqualTo(Bytes.concat(TEST_CHUNK_1, TEST_CHUNK_2));
+    }
+
+    @Test
+    public void getDigest_beforeClose_throws() throws Exception {
+        mDecryptedChunkFileOutput.open();
+        assertThrows(IllegalStateException.class, () -> mDecryptedChunkFileOutput.getDigest());
+    }
+
+    @Test
+    public void getDigest_returnsCorrectDigest() throws Exception {
+        processTestChunks();
+
+        byte[] actualDigest = mDecryptedChunkFileOutput.getDigest();
+
+        MessageDigest expectedDigest =
+                MessageDigest.getInstance(DecryptedChunkFileOutput.DIGEST_ALGORITHM);
+        expectedDigest.update(TEST_CHUNK_1);
+        expectedDigest.update(TEST_CHUNK_2);
+        assertThat(actualDigest).isEqualTo(expectedDigest.digest());
+    }
+
+    @Test
+    public void getDigest_whenRunTwice_returnsIdenticalDigestBothTimes() throws Exception {
+        processTestChunks();
+
+        byte[] digest1 = mDecryptedChunkFileOutput.getDigest();
+        byte[] digest2 = mDecryptedChunkFileOutput.getDigest();
+
+        assertThat(digest1).isEqualTo(digest2);
+    }
+
+    private void processTestChunks() throws IOException {
+        mDecryptedChunkFileOutput.open();
+        mDecryptedChunkFileOutput.processChunk(Arrays.copyOf(TEST_CHUNK_1, TEST_BUFFER_LENGTH),
+                TEST_CHUNK_1.length);
+        mDecryptedChunkFileOutput.processChunk(Arrays.copyOf(TEST_CHUNK_2, TEST_BUFFER_LENGTH),
+                TEST_CHUNK_2.length);
+        mDecryptedChunkFileOutput.close();
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
new file mode 100644
index 0000000..21c4e07
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+import com.android.server.backup.testing.CryptoTestUtils;
+import com.android.server.backup.testing.RandomInputStream;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayInputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+
+import javax.crypto.SecretKey;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class BackupStreamEncrypterTest {
+    private static final int SALT_LENGTH = 32;
+    private static final int BITS_PER_BYTE = 8;
+    private static final int BYTES_PER_KILOBYTE = 1024;
+    private static final int BYTES_PER_MEGABYTE = 1024 * 1024;
+    private static final int MIN_CHUNK_SIZE = 2 * BYTES_PER_KILOBYTE;
+    private static final int AVERAGE_CHUNK_SIZE = 4 * BYTES_PER_KILOBYTE;
+    private static final int MAX_CHUNK_SIZE = 64 * BYTES_PER_KILOBYTE;
+    private static final int BACKUP_SIZE = 2 * BYTES_PER_MEGABYTE;
+    private static final int SMALL_BACKUP_SIZE = BYTES_PER_KILOBYTE;
+    // 16 bytes for the mac. iv is encoded in a separate field.
+    private static final int BYTES_OVERHEAD_PER_CHUNK = 16;
+    private static final int MESSAGE_DIGEST_SIZE_IN_BYTES = 256 / BITS_PER_BYTE;
+    private static final int RANDOM_SEED = 42;
+    private static final double TOLERANCE = 0.1;
+
+    private Random mRandom;
+    private SecretKey mSecretKey;
+    private byte[] mSalt;
+
+    @Before
+    public void setUp() throws Exception {
+        mSecretKey = CryptoTestUtils.generateAesKey();
+
+        mSalt = new byte[SALT_LENGTH];
+        // Make these tests deterministic
+        mRandom = new Random(RANDOM_SEED);
+        mRandom.nextBytes(mSalt);
+    }
+
+    @Test
+    public void testBackup_producesChunksOfTheGivenAverageSize() throws Exception {
+        BackupEncrypter.Result result = runBackup(BACKUP_SIZE);
+
+        long totalSize = 0;
+        for (EncryptedChunk chunk : result.getNewChunks()) {
+            totalSize += chunk.encryptedBytes().length;
+        }
+
+        double meanSize = totalSize / result.getNewChunks().size();
+        double expectedChunkSize = AVERAGE_CHUNK_SIZE + BYTES_OVERHEAD_PER_CHUNK;
+        assertThat(Math.abs(meanSize - expectedChunkSize) / expectedChunkSize)
+                .isLessThan(TOLERANCE);
+    }
+
+    @Test
+    public void testBackup_producesNoChunksSmallerThanMinSize() throws Exception {
+        BackupEncrypter.Result result = runBackup(BACKUP_SIZE);
+        List<EncryptedChunk> chunks = result.getNewChunks();
+
+        // Last chunk could be smaller, depending on the file size and how it is chunked
+        for (EncryptedChunk chunk : chunks.subList(0, chunks.size() - 2)) {
+            assertThat(chunk.encryptedBytes().length)
+                    .isAtLeast(MIN_CHUNK_SIZE + BYTES_OVERHEAD_PER_CHUNK);
+        }
+    }
+
+    @Test
+    public void testBackup_producesNoChunksLargerThanMaxSize() throws Exception {
+        BackupEncrypter.Result result = runBackup(BACKUP_SIZE);
+        List<EncryptedChunk> chunks = result.getNewChunks();
+
+        for (EncryptedChunk chunk : chunks) {
+            assertThat(chunk.encryptedBytes().length)
+                    .isAtMost(MAX_CHUNK_SIZE + BYTES_OVERHEAD_PER_CHUNK);
+        }
+    }
+
+    @Test
+    public void testBackup_producesAFileOfTheExpectedSize() throws Exception {
+        BackupEncrypter.Result result = runBackup(BACKUP_SIZE);
+        HashMap<ChunkHash, EncryptedChunk> chunksBySha256 =
+                chunksIndexedByKey(result.getNewChunks());
+
+        int expectedSize = BACKUP_SIZE + result.getAllChunks().size() * BYTES_OVERHEAD_PER_CHUNK;
+        int size = 0;
+        for (ChunkHash byteString : result.getAllChunks()) {
+            size += chunksBySha256.get(byteString).encryptedBytes().length;
+        }
+        assertThat(size).isEqualTo(expectedSize);
+    }
+
+    @Test
+    public void testBackup_forSameFile_producesNoNewChunks() throws Exception {
+        byte[] backupData = getRandomData(BACKUP_SIZE);
+        BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of());
+
+        BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks());
+
+        assertThat(incrementalResult.getNewChunks()).isEmpty();
+    }
+
+    @Test
+    public void testBackup_onlyUpdatesChangedChunks() throws Exception {
+        byte[] backupData = getRandomData(BACKUP_SIZE);
+        BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of());
+
+        // Let's update the 2nd and 5th chunk
+        backupData[positionOfChunk(result, 1)]++;
+        backupData[positionOfChunk(result, 4)]++;
+        BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks());
+
+        assertThat(incrementalResult.getNewChunks()).hasSize(2);
+    }
+
+    @Test
+    public void testBackup_doesNotIncludeUpdatedChunksInNewListing() throws Exception {
+        byte[] backupData = getRandomData(BACKUP_SIZE);
+        BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of());
+
+        // Let's update the 2nd and 5th chunk
+        backupData[positionOfChunk(result, 1)]++;
+        backupData[positionOfChunk(result, 4)]++;
+        BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks());
+
+        List<EncryptedChunk> newChunks = incrementalResult.getNewChunks();
+        List<ChunkHash> chunkListing = result.getAllChunks();
+        assertThat(newChunks).doesNotContain(chunkListing.get(1));
+        assertThat(newChunks).doesNotContain(chunkListing.get(4));
+    }
+
+    @Test
+    public void testBackup_includesUnchangedChunksInNewListing() throws Exception {
+        byte[] backupData = getRandomData(BACKUP_SIZE);
+        BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of());
+
+        // Let's update the 2nd and 5th chunk
+        backupData[positionOfChunk(result, 1)]++;
+        backupData[positionOfChunk(result, 4)]++;
+        BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks());
+
+        HashSet<ChunkHash> chunksPresentInIncremental =
+                new HashSet<>(incrementalResult.getAllChunks());
+        chunksPresentInIncremental.removeAll(result.getAllChunks());
+
+        assertThat(chunksPresentInIncremental).hasSize(2);
+    }
+
+    @Test
+    public void testBackup_forSameData_createsSameDigest() throws Exception {
+        byte[] backupData = getRandomData(SMALL_BACKUP_SIZE);
+
+        BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of());
+        BackupEncrypter.Result result2 = runBackup(backupData, ImmutableList.of());
+        assertThat(result.getDigest()).isEqualTo(result2.getDigest());
+    }
+
+    @Test
+    public void testBackup_forDifferentData_createsDifferentDigest() throws Exception {
+        byte[] backup1Data = getRandomData(SMALL_BACKUP_SIZE);
+        byte[] backup2Data = getRandomData(SMALL_BACKUP_SIZE);
+
+        BackupEncrypter.Result result = runBackup(backup1Data, ImmutableList.of());
+        BackupEncrypter.Result result2 = runBackup(backup2Data, ImmutableList.of());
+        assertThat(result.getDigest()).isNotEqualTo(result2.getDigest());
+    }
+
+    @Test
+    public void testBackup_createsDigestOf32Bytes() throws Exception {
+        assertThat(runBackup(getRandomData(SMALL_BACKUP_SIZE), ImmutableList.of()).getDigest())
+                .hasLength(MESSAGE_DIGEST_SIZE_IN_BYTES);
+    }
+
+    private byte[] getRandomData(int size) throws Exception {
+        RandomInputStream randomInputStream = new RandomInputStream(mRandom, size);
+        byte[] backupData = new byte[size];
+        randomInputStream.read(backupData);
+        return backupData;
+    }
+
+    private BackupEncrypter.Result runBackup(int backupSize) throws Exception {
+        RandomInputStream dataStream = new RandomInputStream(mRandom, backupSize);
+        BackupStreamEncrypter task =
+                new BackupStreamEncrypter(
+                        dataStream, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE, AVERAGE_CHUNK_SIZE);
+        return task.backup(mSecretKey, mSalt, ImmutableSet.of());
+    }
+
+    private BackupEncrypter.Result runBackup(byte[] data, List<ChunkHash> existingChunks)
+            throws Exception {
+        ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
+        BackupStreamEncrypter task =
+                new BackupStreamEncrypter(
+                        dataStream, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE, AVERAGE_CHUNK_SIZE);
+        return task.backup(mSecretKey, mSalt, ImmutableSet.copyOf(existingChunks));
+    }
+
+    /** Returns a {@link HashMap} of the chunks, indexed by the SHA-256 Mac key. */
+    private static HashMap<ChunkHash, EncryptedChunk> chunksIndexedByKey(
+            List<EncryptedChunk> chunks) {
+        HashMap<ChunkHash, EncryptedChunk> chunksByKey = new HashMap<>();
+        for (EncryptedChunk chunk : chunks) {
+            chunksByKey.put(chunk.key(), chunk);
+        }
+        return chunksByKey;
+    }
+
+    /**
+     * Returns the start position of the chunk in the plaintext backup data.
+     *
+     * @param result The result from a backup.
+     * @param index The index of the chunk in question.
+     * @return the start position.
+     */
+    private static int positionOfChunk(BackupEncrypter.Result result, int index) {
+        HashMap<ChunkHash, EncryptedChunk> byKey = chunksIndexedByKey(result.getNewChunks());
+        List<ChunkHash> listing = result.getAllChunks();
+
+        int position = 0;
+        for (int i = 0; i < index - 1; i++) {
+            EncryptedChunk chunk = byKey.get(listing.get(i));
+            position += chunk.encryptedBytes().length - BYTES_OVERHEAD_PER_CHUNK;
+        }
+
+        return position;
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/RandomInputStream.java b/services/robotests/backup/src/com/android/server/backup/testing/RandomInputStream.java
new file mode 100644
index 0000000..998da0b
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/testing/RandomInputStream.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.testing;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Random;
+
+/** {@link InputStream} that generates random bytes up to a given length. For testing purposes. */
+public class RandomInputStream extends InputStream {
+    private static final int BYTE_MAX_VALUE = 255;
+
+    private final Random mRandom;
+    private final int mSizeBytes;
+    private int mBytesRead;
+
+    /**
+     * A new instance, generating {@code sizeBytes} from {@code random} as a source.
+     *
+     * @param random Source of random bytes.
+     * @param sizeBytes The number of bytes to generate before closing the stream.
+     */
+    public RandomInputStream(Random random, int sizeBytes) {
+        mRandom = random;
+        mSizeBytes = sizeBytes;
+        mBytesRead = 0;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (isFinished()) {
+            return -1;
+        }
+        mBytesRead++;
+        return mRandom.nextInt(BYTE_MAX_VALUE);
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        checkArgument(off + len <= b.length);
+        if (isFinished()) {
+            return -1;
+        }
+        int length = Math.min(len, mSizeBytes - mBytesRead);
+        int end = off + length;
+
+        for (int i = off; i < end; ) {
+            for (int rnd = mRandom.nextInt(), n = Math.min(end - i, Integer.SIZE / Byte.SIZE);
+                    n-- > 0;
+                    rnd >>= Byte.SIZE) {
+                b[i++] = (byte) rnd;
+            }
+        }
+
+        mBytesRead += length;
+        return length;
+    }
+
+    private boolean isFinished() {
+        return mBytesRead >= mSizeBytes;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
index 757a046..a3cc915 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java
@@ -79,7 +79,6 @@
                 InstrumentationRegistry.getTargetContext().getCacheDir());
 
         setSystemInputMethods();
-        setIsPerProfileModeEnabled(false);
         setRequiredAppsManagedDevice();
         setVendorRequiredAppsManagedDevice();
         setDisallowedAppsManagedDevice();
@@ -164,15 +163,6 @@
     }
 
     @Test
-    public void testProfileOwnerImesAreRequiredForPerProfileImeMode() {
-        setSystemAppsWithLauncher("app.a", "app.b");
-        setSystemInputMethods("app.a");
-        setIsPerProfileModeEnabled(true);
-
-        verifyAppsAreNonRequired(ACTION_PROVISION_MANAGED_PROFILE, "app.b");
-    }
-
-    @Test
     public void testManagedUserImesAreRequired() {
         setSystemAppsWithLauncher("app.a", "app.b");
         setSystemInputMethods("app.a");
@@ -344,10 +334,6 @@
         when(mInjector.getInputMethodListAsUser(eq(TEST_USER_ID))).thenReturn(inputMethods);
     }
 
-    private void setIsPerProfileModeEnabled(boolean enabled) {
-        when(mInjector.isPerProfileImeEnabled()).thenReturn(enabled);
-    }
-
     private void setSystemAppsWithLauncher(String... apps) {
         mSystemAppsWithLauncher = apps;
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index d3f33a1..43bcd4f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -36,7 +36,9 @@
 import libcore.io.IoUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -56,6 +58,9 @@
 
 @RunWith(AndroidJUnit4.class)
 public class PackageInstallerSessionTest {
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
     private File mTmpDir;
     private AtomicFile mSessionsFile;
     private static final String TAG_SESSIONS = "sessions";
@@ -65,7 +70,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mTmpDir = IoUtils.createTemporaryDirectory("PackageInstallerSessionTest");
+        mTmpDir = mTemporaryFolder.newFolder("PackageInstallerSessionTest");
         mSessionsFile = new AtomicFile(
                 new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session");
         MockitoAnnotations.initMocks(this);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 3c3721c..13a8eb1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -43,13 +43,14 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import libcore.io.IoUtils;
-
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
@@ -62,13 +63,16 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class PackageParserTest {
+    @Rule
+    public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
     private File mTmpDir;
     private static final File FRAMEWORK = new File("/system/framework/framework-res.apk");
 
     @Before
-    public void setUp() {
+    public void setUp() throws IOException {
         // Create a new temporary directory for each of our tests.
-        mTmpDir = IoUtils.createTemporaryDirectory("PackageParserTest");
+        mTmpDir = mTemporaryFolder.newFolder("PackageParserTest");
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/textservices/LazyIntToIntMapTest.java b/services/tests/servicestests/src/com/android/server/textservices/LazyIntToIntMapTest.java
deleted file mode 100644
index f80afb2..0000000
--- a/services/tests/servicestests/src/com/android/server/textservices/LazyIntToIntMapTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.textservices;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.IntUnaryOperator;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LazyIntToIntMapTest {
-    @Test
-    public void testLaziness() {
-        final IntUnaryOperator func = mock(IntUnaryOperator.class);
-        when(func.applyAsInt(eq(1))).thenReturn(11);
-        when(func.applyAsInt(eq(2))).thenReturn(22);
-
-        final LazyIntToIntMap map = new LazyIntToIntMap(func);
-
-        verify(func, never()).applyAsInt(anyInt());
-
-        assertEquals(22, map.get(2));
-        verify(func, times(0)).applyAsInt(eq(1));
-        verify(func, times(1)).applyAsInt(eq(2));
-
-        // Accessing to the same key does not evaluate the function again.
-        assertEquals(22, map.get(2));
-        verify(func, times(0)).applyAsInt(eq(1));
-        verify(func, times(1)).applyAsInt(eq(2));
-    }
-
-    @Test
-    public void testDelete() {
-        final IntUnaryOperator func1 = mock(IntUnaryOperator.class);
-        when(func1.applyAsInt(eq(1))).thenReturn(11);
-        when(func1.applyAsInt(eq(2))).thenReturn(22);
-
-        final IntUnaryOperator func2 = mock(IntUnaryOperator.class);
-        when(func2.applyAsInt(eq(1))).thenReturn(111);
-        when(func2.applyAsInt(eq(2))).thenReturn(222);
-
-        final AtomicReference<IntUnaryOperator> funcRef = new AtomicReference<>(func1);
-        final LazyIntToIntMap map = new LazyIntToIntMap(i -> funcRef.get().applyAsInt(i));
-
-        verify(func1, never()).applyAsInt(anyInt());
-        verify(func2, never()).applyAsInt(anyInt());
-
-        assertEquals(22, map.get(2));
-        verify(func1, times(1)).applyAsInt(eq(2));
-        verify(func2, times(0)).applyAsInt(eq(2));
-
-        // Swap func1 with func2 then invalidate the key=2
-        funcRef.set(func2);
-        map.delete(2);
-
-        // Calling get(2) again should re-evaluate the value.
-        assertEquals(222, map.get(2));
-        verify(func1, times(1)).applyAsInt(eq(2));
-        verify(func2, times(1)).applyAsInt(eq(2));
-
-        // Trying to delete non-existing keys does nothing.
-        map.delete(1);
-    }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9ecf198..f14e8d2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -132,6 +132,7 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Xml;
+import android.widget.RemoteViews;
 
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
@@ -174,6 +175,7 @@
 import java.util.Map;
 import java.util.function.Consumer;
 
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -348,12 +350,17 @@
         mHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
         // MockPackageManager - default returns ApplicationInfo with matching calling UID
         mContext.setMockPackageManager(mPackageManagerClient);
-        final ApplicationInfo applicationInfo = new ApplicationInfo();
-        applicationInfo.uid = mUid;
+
         when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
-                .thenReturn(applicationInfo);
+                .thenAnswer((Answer<ApplicationInfo>) invocation -> {
+                    Object[] args = invocation.getArguments();
+                    return getApplicationInfo((String) args[0], mUid);
+                });
         when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
-                .thenReturn(applicationInfo);
+                .thenAnswer((Answer<ApplicationInfo>) invocation -> {
+                    Object[] args = invocation.getArguments();
+                    return getApplicationInfo((String) args[0], mUid);
+                });
         when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
         final LightsManager mockLightsManager = mock(LightsManager.class);
         when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
@@ -389,7 +396,6 @@
 
         when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
 
-
         mService.init(mTestableLooper.getLooper(),
                 mPackageManager, mPackageManagerClient, mockLightsManager,
                 mListeners, mAssistants, mConditionProviders,
@@ -413,12 +419,32 @@
 
     @After
     public void tearDown() throws Exception {
-        mFile.delete();
+        if (mFile != null) mFile.delete();
         clearDeviceConfig();
         InstrumentationRegistry.getInstrumentation()
                 .getUiAutomation().dropShellPermissionIdentity();
     }
 
+    private ApplicationInfo getApplicationInfo(String pkg, int uid) {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = uid;
+        switch (pkg) {
+            case PKG_N_MR1:
+                applicationInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+                break;
+            case PKG_O:
+                applicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+                break;
+            case PKG_P:
+                applicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+                break;
+            default:
+                applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+                break;
+        }
+        return applicationInfo;
+    }
+
     public void waitForIdle() {
         mTestableLooper.processAllMessages();
     }
@@ -5122,4 +5148,66 @@
         assertEquals(1, notifsAfter.length);
         assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
     }
+
+    @Test
+    public void testRemoveLargeRemoteViews() throws Exception {
+        int removeSize = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);
+
+        RemoteViews rv = mock(RemoteViews.class);
+        when(rv.estimateMemoryUsage()).thenReturn(removeSize);
+        when(rv.clone()).thenReturn(rv);
+        RemoteViews rv1 = mock(RemoteViews.class);
+        when(rv1.estimateMemoryUsage()).thenReturn(removeSize);
+        when(rv1.clone()).thenReturn(rv1);
+        RemoteViews rv2 = mock(RemoteViews.class);
+        when(rv2.estimateMemoryUsage()).thenReturn(removeSize);
+        when(rv2.clone()).thenReturn(rv2);
+        RemoteViews rv3 = mock(RemoteViews.class);
+        when(rv3.estimateMemoryUsage()).thenReturn(removeSize);
+        when(rv3.clone()).thenReturn(rv3);
+        RemoteViews rv4 = mock(RemoteViews.class);
+        when(rv4.estimateMemoryUsage()).thenReturn(removeSize);
+        when(rv4.clone()).thenReturn(rv4);
+        // note: different!
+        RemoteViews rv5 = mock(RemoteViews.class);
+        when(rv5.estimateMemoryUsage()).thenReturn(removeSize - 1);
+        when(rv5.clone()).thenReturn(rv5);
+
+        Notification np = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setCustomContentView(rv)
+                .setCustomBigContentView(rv1)
+                .setCustomHeadsUpContentView(rv2)
+                .build();
+        Notification n = new Notification.Builder(mContext, "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentText("test")
+                .setCustomContentView(rv3)
+                .setCustomBigContentView(rv4)
+                .setCustomHeadsUpContentView(rv5)
+                .setPublicVersion(np)
+                .build();
+
+        assertNotNull(np.contentView);
+        assertNotNull(np.bigContentView);
+        assertNotNull(np.headsUpContentView);
+
+        assertTrue(n.publicVersion.extras.containsKey(Notification.EXTRA_CONTAINS_CUSTOM_VIEW));
+        assertNotNull(n.publicVersion.contentView);
+        assertNotNull(n.publicVersion.bigContentView);
+        assertNotNull(n.publicVersion.headsUpContentView);
+
+        mService.fixNotification(n, PKG, "tag", 9, 0);
+
+        assertNull(n.contentView);
+        assertNull(n.bigContentView);
+        assertNotNull(n.headsUpContentView);
+        assertNull(n.publicVersion.contentView);
+        assertNull(n.publicVersion.bigContentView);
+        assertNull(n.publicVersion.headsUpContentView);
+
+        verify(mUsageStats, times(5)).registerImageRemoved(PKG);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index f49a575..c5e7c47 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -26,8 +26,13 @@
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -663,8 +668,8 @@
                 dc.onDescendantOrientationChanged(window.mToken.token, activityRecord));
 
         final ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
-        verify(mWm.mAtmService).updateDisplayOverrideConfigurationLocked(captor.capture(),
-                same(activityRecord), anyBoolean(), eq(dc.getDisplayId()));
+        verify(dc.mAcitvityDisplay).updateDisplayOverrideConfigurationLocked(captor.capture(),
+                same(activityRecord), anyBoolean(), same(null));
         final Configuration newDisplayConfig = captor.getValue();
         assertEquals(Configuration.ORIENTATION_PORTRAIT, newDisplayConfig.orientation);
     }
@@ -688,8 +693,8 @@
         assertFalse("Display shouldn't rotate to handle orientation request if fixed to"
                         + " user rotation.",
                 dc.onDescendantOrientationChanged(window.mToken.token, activityRecord));
-        verify(mWm.mAtmService, never()).updateDisplayOverrideConfigurationLocked(any(),
-                eq(activityRecord), anyBoolean(), eq(dc.getDisplayId()));
+        verify(dc.mAcitvityDisplay, never()).updateDisplayOverrideConfigurationLocked(any(),
+                eq(activityRecord), anyBoolean(), same(null));
     }
 
     @Test
@@ -789,6 +794,58 @@
     }
 
     @Test
+    public void testCalculateSystemGestureExclusion_modal() throws Exception {
+        final DisplayContent dc = createNewDisplay();
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "base");
+        win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+        win.setSystemGestureExclusion(Collections.singletonList(new Rect(0, 0, 1000, 1000)));
+
+        final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "modal");
+        win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+        win2.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+        win2.getAttrs().width = 10;
+        win2.getAttrs().height = 10;
+        win2.setSystemGestureExclusion(Collections.emptyList());
+
+        dc.setLayoutNeeded();
+        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+
+        win.setHasSurface(true);
+        win2.setHasSurface(true);
+
+        final Region expected = Region.obtain();
+        assertEquals(expected, dc.calculateSystemGestureExclusion());
+    }
+
+    @Test
+    public void testCalculateSystemGestureExclusion_immersiveStickyLegacyWindow() throws Exception {
+        synchronized (mWm.mGlobalLock) {
+            mWm.mSystemGestureExcludedByPreQStickyImmersive = true;
+
+            final DisplayContent dc = createNewDisplay();
+            final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
+            win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+            win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+            win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
+            win.getAttrs().subtreeSystemUiVisibility = win.mSystemUiVisibility =
+                    SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                            | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+            win.mAppToken.mTargetSdk = P;
+
+            dc.setLayoutNeeded();
+            dc.performLayout(true /* initial */, false /* updateImeWindows */);
+
+            win.setHasSurface(true);
+
+            final Region expected = Region.obtain();
+            expected.set(dc.getBounds());
+            assertEquals(expected, dc.calculateSystemGestureExclusion());
+
+            win.setHasSurface(false);
+        }
+    }
+
+    @Test
     public void testOrientationChangeLogging() {
         MetricsLogger mockLogger = mock(MetricsLogger.class);
         Configuration oldConfig = new Configuration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
index 5586726..daee911 100644
--- a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
@@ -16,71 +16,168 @@
 
 package com.android.server.wm;
 
+import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
 import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.util.Pair;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
-import com.android.server.wm.HighRefreshRateBlacklist.SystemPropertyGetter;
+import com.android.internal.R;
+import com.android.server.wm.HighRefreshRateBlacklist.DeviceConfigInterface;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Build/Install/Run:
- *  atest WmTests:HighRefreshRateBlacklistTest
+ * atest WmTests:HighRefreshRateBlacklistTest
  */
 @SmallTest
 @Presubmit
-@FlakyTest
 public class HighRefreshRateBlacklistTest {
 
     @Test
-    public void testBlacklist() {
-        HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(
-                new SystemPropertyGetter() {
-
-                    @Override
-                    public int getInt(String key, int def) {
-                        if ("ro.window_manager.high_refresh_rate_blacklist_length".equals(key)) {
-                            return 2;
-                        }
-                        return def;
-                    }
-
-                    @Override
-                    public String get(String key) {
-                        if ("ro.window_manager.high_refresh_rate_blacklist_entry1".equals(key)) {
-                            return "com.android.sample1";
-                        }
-                        if ("ro.window_manager.high_refresh_rate_blacklist_entry2".equals(key)) {
-                            return "com.android.sample2";
-                        }
-                        return "";
-                    }
-                });
+    public void testDefaultBlacklist() {
+        final Resources r = createResources("com.android.sample1", "com.android.sample2");
+        HighRefreshRateBlacklist blacklist =
+                new HighRefreshRateBlacklist(r, new FakeDeviceConfigInterface());
         assertTrue(blacklist.isBlacklisted("com.android.sample1"));
         assertTrue(blacklist.isBlacklisted("com.android.sample2"));
         assertFalse(blacklist.isBlacklisted("com.android.sample3"));
     }
 
     @Test
-    public void testNoBlacklist() {
-        HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(
-                new SystemPropertyGetter() {
-
-                    @Override
-                    public int getInt(String key, int def) {
-                        return def;
-                    }
-
-                    @Override
-                    public String get(String key) {
-                        return "";
-                    }
-                });
+    public void testNoDefaultBlacklist() {
+        final Resources r = createResources();
+        HighRefreshRateBlacklist blacklist =
+                new HighRefreshRateBlacklist(r, new FakeDeviceConfigInterface());
         assertFalse(blacklist.isBlacklisted("com.android.sample1"));
     }
+
+    @Test
+    public void testDefaultBlacklistIsOverriddenByDeviceConfig() {
+        final Resources r = createResources("com.android.sample1");
+        final FakeDeviceConfigInterface config = new FakeDeviceConfigInterface();
+        config.setBlacklist("com.android.sample2,com.android.sample3");
+        HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(r, config);
+        assertFalse(blacklist.isBlacklisted("com.android.sample1"));
+        assertTrue(blacklist.isBlacklisted("com.android.sample2"));
+        assertTrue(blacklist.isBlacklisted("com.android.sample3"));
+    }
+
+    @Test
+    public void testDefaultBlacklistIsOverriddenByEmptyDeviceConfig() {
+        final Resources r = createResources("com.android.sample1");
+        final FakeDeviceConfigInterface config = new FakeDeviceConfigInterface();
+        config.setBlacklist("");
+        HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(r, config);
+        assertFalse(blacklist.isBlacklisted("com.android.sample1"));
+    }
+
+    @Test
+    public void testDefaultBlacklistIsOverriddenByDeviceConfigUpdate() {
+        final Resources r = createResources("com.android.sample1");
+        final FakeDeviceConfigInterface config = new FakeDeviceConfigInterface();
+        HighRefreshRateBlacklist blacklist = new HighRefreshRateBlacklist(r, config);
+
+        // First check that the default blacklist is in effect
+        assertTrue(blacklist.isBlacklisted("com.android.sample1"));
+        assertFalse(blacklist.isBlacklisted("com.android.sample2"));
+        assertFalse(blacklist.isBlacklisted("com.android.sample3"));
+
+        //  Then confirm that the DeviceConfig list has propagated and taken effect.
+        config.setBlacklist("com.android.sample2,com.android.sample3");
+        assertFalse(blacklist.isBlacklisted("com.android.sample1"));
+        assertTrue(blacklist.isBlacklisted("com.android.sample2"));
+        assertTrue(blacklist.isBlacklisted("com.android.sample3"));
+
+        //  Finally make sure we go back to the default list if the DeviceConfig gets deleted.
+        config.setBlacklist(null);
+        assertTrue(blacklist.isBlacklisted("com.android.sample1"));
+        assertFalse(blacklist.isBlacklisted("com.android.sample2"));
+        assertFalse(blacklist.isBlacklisted("com.android.sample3"));
+    }
+
+    private Resources createResources(String... defaultBlacklist) {
+        Resources r = mock(Resources.class);
+        when(r.getStringArray(R.array.config_highRefreshRateBlacklist))
+                .thenReturn(defaultBlacklist);
+        return r;
+    }
+
+
+    class FakeDeviceConfigInterface implements DeviceConfigInterface {
+        private List<Pair<DeviceConfig.OnPropertiesChangedListener, Executor>> mListeners =
+                new ArrayList<>();
+        private String mBlacklist;
+
+        @Override
+        public String getProperty(String namespace, String name) {
+            if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)
+                    || !KEY_HIGH_REFRESH_RATE_BLACKLIST.equals(name)) {
+                throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+                        + "supported.");
+            }
+            return mBlacklist;
+        }
+
+        @Override
+        public void addOnPropertiesChangedListener(String namespace, Executor executor,
+                DeviceConfig.OnPropertiesChangedListener listener) {
+
+            if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)) {
+                throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+                        + "supported.");
+            }
+            mListeners.add(new Pair<>(listener, executor));
+        }
+
+        void setBlacklist(String blacklist) {
+            mBlacklist = blacklist;
+            CountDownLatch latch = new CountDownLatch(mListeners.size());
+            for (Pair<DeviceConfig.OnPropertiesChangedListener, Executor> listenerInfo :
+                    mListeners) {
+                final Executor executor = listenerInfo.second;
+                final DeviceConfig.OnPropertiesChangedListener listener = listenerInfo.first;
+                DeviceConfig.Properties properties = createBlacklistProperties(blacklist);
+                executor.execute(() -> {
+                    listener.onPropertiesChanged(properties);
+                    latch.countDown();
+                });
+            }
+            try {
+                latch.await(10, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to notify all blacklist listeners in time.", e);
+            }
+        }
+
+        private DeviceConfig.Properties createBlacklistProperties(final String blacklist) {
+            DeviceConfig.Properties properties = mock(DeviceConfig.Properties.class);
+            when(properties.getString(anyString(), any())).thenAnswer(invocation -> {
+                final Object[] args = invocation.getArguments();
+                if (KEY_HIGH_REFRESH_RATE_BLACKLIST.equals(args[0])) {
+                    return blacklist;
+                } else {
+                    return args[1];
+                }
+            });
+            return properties;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 1c9e504..2e5ce69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -49,6 +49,7 @@
 
     int mRotationToReport = 0;
     boolean mKeyguardShowingAndNotOccluded = false;
+    boolean mOkToAnimate = true;
 
     private Runnable mRunnableWhenAddingSplashScreen;
 
@@ -222,7 +223,7 @@
 
     @Override
     public boolean okToAnimate() {
-        return true;
+        return mOkToAnimate;
     }
 
     @Override
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 7c41988..7f7a78b 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -909,6 +909,8 @@
                     if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                         // If the screen is unlocked, also set current functions.
                         setScreenUnlockedFunctions();
+                    } else {
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                     }
                     break;
                 case MSG_UPDATE_SCREEN_LOCK:
diff --git a/startop/apps/ColorChanging/.gitignore b/startop/apps/ColorChanging/.gitignore
new file mode 100644
index 0000000..2b75303
--- /dev/null
+++ b/startop/apps/ColorChanging/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/startop/apps/ColorChanging/.idea/encodings.xml b/startop/apps/ColorChanging/.idea/encodings.xml
new file mode 100644
index 0000000..15a15b2
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/encodings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" addBOMForNewFiles="with NO BOM" />
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/.idea/gradle.xml b/startop/apps/ColorChanging/.idea/gradle.xml
new file mode 100644
index 0000000..2996d53
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/gradle.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <compositeConfiguration>
+          <compositeBuild compositeDefinitionSource="SCRIPT" />
+        </compositeConfiguration>
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="resolveModulePerSourceSet" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/.idea/misc.xml b/startop/apps/ColorChanging/.idea/misc.xml
new file mode 100644
index 0000000..37a7509
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/misc.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/.idea/runConfigurations.xml b/startop/apps/ColorChanging/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/startop/apps/ColorChanging/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
+        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/README.md b/startop/apps/ColorChanging/README.md
new file mode 100644
index 0000000..eb8b9cc
--- /dev/null
+++ b/startop/apps/ColorChanging/README.md
@@ -0,0 +1,5 @@
+This directory contains a simple Android app that is meant to help in 
+syncing a trace along with a video in Perfetto.
+
+This app changes the colors of the screen that has traces to go along
+with the colors.
diff --git a/startop/apps/ColorChanging/app/.gitignore b/startop/apps/ColorChanging/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/startop/apps/ColorChanging/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/startop/apps/ColorChanging/app/build.gradle b/startop/apps/ColorChanging/app/build.gradle
new file mode 100644
index 0000000..ab955aa
--- /dev/null
+++ b/startop/apps/ColorChanging/app/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 29
+    buildToolsVersion "29.0.0"
+    defaultConfig {
+        applicationId "com.android.startop.colorchanging"
+        minSdkVersion 15
+        targetSdkVersion 29
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'androidx.appcompat:appcompat:1.0.2'
+    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test:runner:1.2.0'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
diff --git a/startop/apps/ColorChanging/app/proguard-rules.pro b/startop/apps/ColorChanging/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/startop/apps/ColorChanging/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/startop/apps/ColorChanging/app/src/androidTest/java/com/android/startop/colorchanging/ExampleInstrumentedTest.java b/startop/apps/ColorChanging/app/src/androidTest/java/com/android/startop/colorchanging/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..31736f3
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/androidTest/java/com/android/startop/colorchanging/ExampleInstrumentedTest.java
@@ -0,0 +1,27 @@
+package com.android.startop.colorchanging;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.android.startop.colorchanging", appContext.getPackageName());
+    }
+}
diff --git a/startop/apps/ColorChanging/app/src/main/AndroidManifest.xml b/startop/apps/ColorChanging/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..37193b5
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.startop.colorchanging">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity android:name="com.android.startop.colorchanging.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/java/com/android/startop/colorchanging/MainActivity.java b/startop/apps/ColorChanging/app/src/main/java/com/android/startop/colorchanging/MainActivity.java
new file mode 100644
index 0000000..b8f4faf
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/java/com/android/startop/colorchanging/MainActivity.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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.startop.colorchanging;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.os.Bundle;
+import android.os.Trace;
+import android.view.View;
+
+public class MainActivity extends AppCompatActivity {
+    View view;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        view = this.getWindow().getDecorView();
+        view.setBackgroundResource(R.color.gray);
+        Trace.beginSection("gray");
+    }
+
+    public void goRed(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.red);
+        Trace.beginSection("red");
+    }
+
+    public void goOrange(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.orange);
+        Trace.beginSection("orange");
+    }
+
+    public void goYellow(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.yellow);
+        Trace.beginSection("yellow");
+    }
+
+    public void goGreen(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.green);
+        Trace.beginSection("green");
+    }
+
+    public void goBlue(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.blue);
+        Trace.beginSection("blue");
+    }
+
+    public void goIndigo(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.indigo);
+        Trace.beginSection("indigo");
+    }
+
+    public void goViolet(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.violet);
+        Trace.beginSection("violet");
+    }
+
+    public void goCyan(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.cyan);
+        Trace.beginSection("cyan");
+    }
+
+    public void goBlack(View v) {
+        Trace.endSection();
+        view.setBackgroundResource(R.color.black);
+        Trace.beginSection("black");
+    }
+
+}
diff --git a/startop/apps/ColorChanging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/startop/apps/ColorChanging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
diff --git a/startop/apps/ColorChanging/app/src/main/res/drawable/ic_launcher_background.xml b/startop/apps/ColorChanging/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#008577"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/startop/apps/ColorChanging/app/src/main/res/layout/activity_main.xml b/startop/apps/ColorChanging/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..fb18df7
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginLeft="16dp"
+        android:layout_marginTop="16dp"
+        android:onClick="goRed"
+        android:text="red"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/button4"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
+        android:onClick="goYellow"
+        android:text="YELLOW"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/button6"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginLeft="16dp"
+        android:layout_marginTop="32dp"
+        android:onClick="goGreen"
+        android:text="GREEN"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button" />
+
+    <Button
+        android:id="@+id/button7"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="165dp"
+        android:layout_marginLeft="165dp"
+        android:layout_marginTop="115dp"
+        android:layout_marginEnd="165dp"
+        android:layout_marginRight="165dp"
+        android:onClick="goViolet"
+        android:text="VIOLET"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.428"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button8" />
+
+    <Button
+        android:id="@+id/button10"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="165dp"
+        android:layout_marginLeft="165dp"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="165dp"
+        android:layout_marginRight="165dp"
+        android:onClick="goBlue"
+        android:text="BLUE"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button8" />
+
+    <Button
+        android:id="@+id/button8"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="165dp"
+        android:layout_marginLeft="165dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="165dp"
+        android:layout_marginRight="165dp"
+        android:onClick="goOrange"
+        android:text="ORANGE"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/button11"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
+        android:onClick="goIndigo"
+        android:text="INDIGO"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button4" />
+
+    <Button
+        android:id="@+id/button12"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="162dp"
+        android:layout_marginLeft="162dp"
+        android:layout_marginTop="25dp"
+        android:layout_marginEnd="161dp"
+        android:layout_marginRight="161dp"
+        android:onClick="goCyan"
+        android:text="CYAN"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button7" />
+
+    <Button
+        android:id="@+id/button13"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="162dp"
+        android:layout_marginLeft="162dp"
+        android:layout_marginTop="25dp"
+        android:layout_marginEnd="161dp"
+        android:layout_marginRight="161dp"
+        android:onClick="goBlack"
+        android:text="BLACK"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button12" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af3
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/startop/apps/ColorChanging/app/src/main/res/values/colors.xml b/startop/apps/ColorChanging/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..209790f
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/values/colors.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+    <color name="black">#000000</color>
+    <color name="red">#F44336</color>
+    <color name="green">#2CF035</color>
+    <color name="blue">#2C70F0</color>
+    <color name="yellow">#F0EA2C</color>
+    <color name="gray">#D3D3D3</color>
+    <color name="orange">#E57E0A</color>
+    <color name="indigo">#4B0082</color>
+    <color name="violet">#EE82EE</color>
+    <color name="cyan">#00E8FF</color>
+</resources>
diff --git a/startop/apps/ColorChanging/app/src/main/res/values/strings.xml b/startop/apps/ColorChanging/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ff062fb
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">ColorChanging</string>
+</resources>
diff --git a/startop/apps/ColorChanging/app/src/main/res/values/styles.xml b/startop/apps/ColorChanging/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
diff --git a/startop/apps/ColorChanging/app/src/test/java/com/android/startop/colorchanging/ExampleUnitTest.java b/startop/apps/ColorChanging/app/src/test/java/com/android/startop/colorchanging/ExampleUnitTest.java
new file mode 100644
index 0000000..8423674
--- /dev/null
+++ b/startop/apps/ColorChanging/app/src/test/java/com/android/startop/colorchanging/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.android.startop.colorchanging;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}
\ No newline at end of file
diff --git a/startop/apps/ColorChanging/build.gradle b/startop/apps/ColorChanging/build.gradle
new file mode 100644
index 0000000..a960ab3
--- /dev/null
+++ b/startop/apps/ColorChanging/build.gradle
@@ -0,0 +1,24 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.4.1'
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/startop/apps/ColorChanging/gradle.properties b/startop/apps/ColorChanging/gradle.properties
new file mode 100644
index 0000000..199d16e
--- /dev/null
+++ b/startop/apps/ColorChanging/gradle.properties
@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
diff --git a/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.jar b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
--- /dev/null
+++ b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.properties b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..09f2718
--- /dev/null
+++ b/startop/apps/ColorChanging/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Jun 17 13:40:58 PDT 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/startop/apps/ColorChanging/gradlew b/startop/apps/ColorChanging/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/startop/apps/ColorChanging/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/startop/apps/ColorChanging/gradlew.bat b/startop/apps/ColorChanging/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/startop/apps/ColorChanging/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/startop/apps/ColorChanging/settings.gradle b/startop/apps/ColorChanging/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/startop/apps/ColorChanging/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index a87e2f5..cc260ac 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -7,6 +7,7 @@
   }
 
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
+    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public boolean arePermissionsIndividuallyControlled();
     method public String getDefaultBrowserPackageNameAsUser(int);
     method public int getInstallReason(String, android.os.UserHandle);
@@ -18,6 +19,7 @@
     method @NonNull public String getServicesSystemSharedLibraryPackageName();
     method @NonNull public String getSharedSystemSharedLibraryPackageName();
     method public void grantRuntimePermission(String, String, android.os.UserHandle);
+    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public void revokeRuntimePermission(String, String, android.os.UserHandle);
     method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
   }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1b2a1288..5cc95d9 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -208,6 +208,8 @@
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.testutils.ExceptionUtils;
 import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.RecorderCallback.CallbackRecord;
+import com.android.testutils.TestableNetworkCallback;
 
 import org.junit.After;
 import org.junit.Before;
@@ -234,7 +236,6 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -243,7 +244,8 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Predicate;
+
+import kotlin.reflect.KClass;
 
 /**
  * Tests for {@link ConnectivityService}.
@@ -467,7 +469,7 @@
         fail("expected race condition at least once in " + attempts + " attempts");
     }
 
-    private class MockNetworkAgent {
+    private class MockNetworkAgent implements TestableNetworkCallback.HasNetwork {
         private static final int VALIDATION_RESULT_BASE = NETWORK_VALIDATION_PROBE_DNS
                 | NETWORK_VALIDATION_PROBE_HTTP
                 | NETWORK_VALIDATION_PROBE_HTTPS;
@@ -1703,281 +1705,33 @@
                 mCm.getDefaultRequest().networkCapabilities));
     }
 
-    enum CallbackState {
-        NONE,
-        AVAILABLE,
-        NETWORK_CAPABILITIES,
-        LINK_PROPERTIES,
-        SUSPENDED,
-        RESUMED,
-        LOSING,
-        LOST,
-        UNAVAILABLE,
-        BLOCKED_STATUS
-    }
-
-    private static class CallbackInfo {
-        public final CallbackState state;
-        public final Network network;
-        public final Object arg;
-        public CallbackInfo(CallbackState s, Network n, Object o) {
-            state = s; network = n; arg = o;
-        }
-        public String toString() {
-            return String.format("%s (%s) (%s)", state, network, arg);
-        }
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof CallbackInfo)) return false;
-            // Ignore timeMs, since it's unpredictable.
-            CallbackInfo other = (CallbackInfo) o;
-            return (state == other.state) && Objects.equals(network, other.network);
-        }
-        @Override
-        public int hashCode() {
-            return Objects.hash(state, network);
-        }
-    }
-
     /**
      * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks
      * this class receives, by calling expectCallback() exactly once each time a callback is
      * received. assertNoCallback may be called at any time.
      */
-    private class TestNetworkCallback extends NetworkCallback {
-        private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
-        private Network mLastAvailableNetwork;
-
-        protected void setLastCallback(CallbackState state, Network network, Object o) {
-            mCallbacks.offer(new CallbackInfo(state, network, o));
+    private class TestNetworkCallback extends TestableNetworkCallback {
+        @Override
+        public void assertNoCallback() {
+            // TODO: better support this use case in TestableNetworkCallback
+            waitForIdle();
+            assertNoCallback(0 /* timeout */);
         }
 
         @Override
-        public void onAvailable(Network network) {
-            mLastAvailableNetwork = network;
-            setLastCallback(CallbackState.AVAILABLE, network, null);
-        }
-
-        @Override
-        public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
-            setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
-        }
-
-        @Override
-        public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
-            setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
-        }
-
-        @Override
-        public void onUnavailable() {
-            setLastCallback(CallbackState.UNAVAILABLE, null, null);
-        }
-
-        @Override
-        public void onNetworkSuspended(Network network) {
-            setLastCallback(CallbackState.SUSPENDED, network, null);
-        }
-
-        @Override
-        public void onNetworkResumed(Network network) {
-            setLastCallback(CallbackState.RESUMED, network, null);
-        }
-
-        @Override
-        public void onLosing(Network network, int maxMsToLive) {
-            setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
-        }
-
-        @Override
-        public void onLost(Network network) {
-            mLastAvailableNetwork = null;
-            setLastCallback(CallbackState.LOST, network, null);
-        }
-
-        @Override
-        public void onBlockedStatusChanged(Network network, boolean blocked) {
-            setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
-        }
-
-        public Network getLastAvailableNetwork() {
-            return mLastAvailableNetwork;
-        }
-
-        CallbackInfo nextCallback(int timeoutMs) throws InterruptedException {
-            CallbackInfo cb = null;
-            cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
-            if (cb == null) {
-                // LinkedBlockingQueue.poll() returns null if it timeouts.
-                fail("Did not receive callback after " + timeoutMs + "ms");
-            }
-            return cb;
-        }
-
-        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent, int timeoutMs)
-                throws Exception {
-            final Network expectedNetwork = (agent != null) ? agent.getNetwork() : null;
-            CallbackInfo expected = new CallbackInfo(state, expectedNetwork, 0);
-            CallbackInfo actual = nextCallback(timeoutMs);
-            assertEquals("Unexpected callback:", expected, actual);
-
-            if (state == CallbackState.LOSING) {
+        public <T extends CallbackRecord> T expectCallback(final KClass<T> type, final HasNetwork n,
+                final long timeoutMs) {
+            final T callback = super.expectCallback(type, n, timeoutMs);
+            if (callback instanceof CallbackRecord.Losing) {
+                // TODO : move this to the specific test(s) needing this rather than here.
+                final CallbackRecord.Losing losing = (CallbackRecord.Losing) callback;
+                final int maxMsToLive = losing.getMaxMsToLive();
                 String msg = String.format(
                         "Invalid linger time value %d, must be between %d and %d",
-                        actual.arg, 0, mService.mLingerDelayMs);
-                int maxMsToLive = (Integer) actual.arg;
+                        maxMsToLive, 0, mService.mLingerDelayMs);
                 assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= mService.mLingerDelayMs);
             }
-
-            return actual;
-        }
-
-        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent) throws Exception {
-            return expectCallback(state, agent, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn) throws Exception {
-            return expectCallbackLike(fn, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        CallbackInfo expectCallbackLike(Predicate<CallbackInfo> fn, int timeoutMs)
-                throws Exception {
-            int timeLeft = timeoutMs;
-            while (timeLeft > 0) {
-                long start = SystemClock.elapsedRealtime();
-                CallbackInfo info = nextCallback(timeLeft);
-                if (fn.test(info)) {
-                    return info;
-                }
-                timeLeft -= (SystemClock.elapsedRealtime() - start);
-            }
-            fail("Did not receive expected callback after " + timeoutMs + "ms");
-            return null;
-        }
-
-        // Expects onAvailable and the callbacks that follow it. These are:
-        // - onSuspended, iff the network was suspended when the callbacks fire.
-        // - onCapabilitiesChanged.
-        // - onLinkPropertiesChanged.
-        // - onBlockedStatusChanged.
-        //
-        // @param agent the network to expect the callbacks on.
-        // @param expectSuspended whether to expect a SUSPENDED callback.
-        // @param expectValidated the expected value of the VALIDATED capability in the
-        //        onCapabilitiesChanged callback.
-        // @param timeoutMs how long to wait for the callbacks.
-        void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended,
-                boolean expectValidated, boolean expectBlocked, int timeoutMs) throws Exception {
-            expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
-            if (expectSuspended) {
-                expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
-            }
-            if (expectValidated) {
-                expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent, timeoutMs);
-            } else {
-                expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent, timeoutMs);
-            }
-            expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
-            expectBlockedStatusCallback(expectBlocked, agent);
-        }
-
-        // Expects the available callbacks (validated), plus onSuspended.
-        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated)
-                throws Exception {
-            expectAvailableCallbacks(agent, true, expectValidated, false, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksValidated(MockNetworkAgent agent)
-                throws Exception {
-            expectAvailableCallbacks(agent, false, true, false, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksValidatedAndBlocked(MockNetworkAgent agent) throws Exception {
-            expectAvailableCallbacks(agent, false, true, true, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) throws Exception {
-            expectAvailableCallbacks(agent, false, false, false, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        void expectAvailableCallbacksUnvalidatedAndBlocked(MockNetworkAgent agent)
-                throws Exception {
-            expectAvailableCallbacks(agent, false, false, true, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        // Expects the available callbacks (where the onCapabilitiesChanged must contain the
-        // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
-        // one we just sent.
-        // TODO: this is likely a bug. Fix it and remove this method.
-        void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) throws Exception {
-            expectCallback(CallbackState.AVAILABLE, agent, TEST_CALLBACK_TIMEOUT_MS);
-            NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
-            expectCallback(CallbackState.LINK_PROPERTIES, agent, TEST_CALLBACK_TIMEOUT_MS);
-            // Implicitly check the network is allowed to use.
-            // TODO: should we need to consider if network is in blocked status in this case?
-            expectBlockedStatusCallback(false, agent);
-            NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
-            assertEquals(nc1, nc2);
-        }
-
-        // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
-        // then expects another onCapabilitiesChanged that has the validated bit set. This is used
-        // when a network connects and satisfies a callback, and then immediately validates.
-        void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) throws Exception {
-            expectAvailableCallbacksUnvalidated(agent);
-            expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
-        }
-
-        NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent)
-                throws Exception {
-            return expectCapabilitiesWith(capability, agent, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent,
-                int timeoutMs) throws Exception {
-            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
-            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
-            assertTrue(nc.hasCapability(capability));
-            return nc;
-        }
-
-        NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent)
-                throws Exception {
-            return expectCapabilitiesWithout(capability, agent, TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent,
-                int timeoutMs) throws Exception {
-            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
-            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
-            assertFalse(nc.hasCapability(capability));
-            return nc;
-        }
-
-        void expectCapabilitiesLike(Predicate<NetworkCapabilities> fn, MockNetworkAgent agent)
-                throws Exception {
-            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
-            assertTrue("Received capabilities don't match expectations : " + cbi.arg,
-                    fn.test((NetworkCapabilities) cbi.arg));
-        }
-
-        void expectLinkPropertiesLike(Predicate<LinkProperties> fn, MockNetworkAgent agent)
-                throws Exception {
-            CallbackInfo cbi = expectCallback(CallbackState.LINK_PROPERTIES, agent);
-            assertTrue("Received LinkProperties don't match expectations : " + cbi.arg,
-                    fn.test((LinkProperties) cbi.arg));
-        }
-
-        void expectBlockedStatusCallback(boolean expectBlocked, MockNetworkAgent agent)
-                throws Exception {
-            CallbackInfo cbi = expectCallback(CallbackState.BLOCKED_STATUS, agent);
-            boolean actualBlocked = (boolean) cbi.arg;
-            assertEquals(expectBlocked, actualBlocked);
-        }
-
-        void assertNoCallback() {
-            waitForIdle();
-            CallbackInfo c = mCallbacks.peek();
-            assertNull("Unexpected callback: " + c, c);
+            return callback;
         }
     }
 
@@ -2031,16 +1785,16 @@
 
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         cellNetworkCallback.assertNoCallback();
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
@@ -2061,21 +1815,21 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         mWiFiNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         mCellNetworkAgent.disconnect();
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
     }
 
@@ -2115,7 +1869,7 @@
         // We then get LOSING when wifi validates and cell is outscored.
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -2124,15 +1878,15 @@
         mEthernetNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -2148,7 +1902,7 @@
                 newNetwork = mWiFiNetworkAgent;
 
             }
-            callback.expectCallback(CallbackState.LOSING, oldNetwork);
+            callback.expectCallback(CallbackRecord.LOSING, oldNetwork);
             // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
             // longer lingering?
             defaultCallback.expectAvailableCallbacksValidated(newNetwork);
@@ -2162,7 +1916,7 @@
         // We expect a notification about the capabilities change, and nothing else.
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
         defaultCallback.assertNoCallback();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Wifi no longer satisfies our listen, which is for an unmetered network.
@@ -2171,11 +1925,11 @@
 
         // Disconnect our test networks.
         mWiFiNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
         mCellNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
@@ -2206,8 +1960,8 @@
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -2218,15 +1972,15 @@
         mWiFiNetworkAgent.adjustScore(50);
         mWiFiNetworkAgent.connect(false);   // Score: 70
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Tear down wifi.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -2237,19 +1991,19 @@
         mWiFiNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
@@ -2264,7 +2018,7 @@
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -2275,13 +2029,13 @@
         // TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer
         // lingering?
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
 
         // Similar to the above: lingering can start even after the lingered request is removed.
         // Disconnect wifi and switch to cell.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -2300,12 +2054,12 @@
         callback.assertNoCallback();
         // Now unregister cellRequest and expect cell to start lingering.
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
 
         // Let linger run its course.
         callback.assertNoCallback();
         final int lingerTimeoutMs = mService.mLingerDelayMs + mService.mLingerDelayMs / 4;
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
+        callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent, lingerTimeoutMs);
 
         // Register a TRACK_DEFAULT request and check that it does not affect lingering.
         TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
@@ -2314,20 +2068,20 @@
         mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
         mEthernetNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mWiFiNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
         trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Let linger run its course.
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
 
         // Clean up.
         mEthernetNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        trackDefaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
+        trackDefaultCallback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -2357,7 +2111,7 @@
         mWiFiNetworkAgent.connect(true);
         defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         // File a request for cellular, then release it.
@@ -2366,7 +2120,7 @@
         NetworkCallback noopCallback = new NetworkCallback();
         mCm.requestNetwork(cellRequest, noopCallback);
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
 
         // Let linger run its course.
         callback.assertNoCallback();
@@ -2410,12 +2164,12 @@
         // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
         // wifi even though it's unvalidated.
         mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Disconnect wifi, and then reconnect, again with explicitlySelected=true.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(true, false);
         mWiFiNetworkAgent.connect(false);
@@ -2424,14 +2178,14 @@
         // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
         // network to disconnect.
         mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Reconnect, again with explicitlySelected=true, but this time validate.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(true, false);
         mWiFiNetworkAgent.connect(true);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
@@ -2447,20 +2201,20 @@
         // (i.e., with explicitlySelected=true and acceptUnvalidated=true). Expect to switch to
         // wifi immediately.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(true, true);
         mWiFiNetworkAgent.connect(false);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mEthernetNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mEthernetNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         mEthernetNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mEthernetNetworkAgent);
 
         // Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true.
         // Check that the network is not scored specially and that the device prefers cell data.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.explicitlySelected(false, true);
         mWiFiNetworkAgent.connect(false);
@@ -2471,8 +2225,8 @@
         mWiFiNetworkAgent.disconnect();
         mCellNetworkAgent.disconnect();
 
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
     }
 
     private int[] makeIntArray(final int size, final int value) {
@@ -2721,7 +2475,7 @@
         // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
         // validated.
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED,
                 mWiFiNetworkAgent);
         assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
@@ -2729,7 +2483,7 @@
 
         // Disconnect and reconnect wifi with partial connectivity again.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connectWithPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
@@ -2741,7 +2495,7 @@
         // If the user chooses no, disconnect wifi immediately.
         mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */,
                 false /* always */);
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // If user accepted partial connectivity before, and device reconnects to that network
         // again, but now the network has full connectivity. The network shouldn't contain
@@ -2758,14 +2512,14 @@
         waitForIdle();
         verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
 
         // Wifi should be the default network.
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // The user accepted partial connectivity and selected "don't ask again". Now the user
         // reconnects to the partial connectivity network. Switch to wifi as soon as partial
@@ -2780,7 +2534,7 @@
         waitForIdle();
         verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
         mWiFiNetworkAgent.setNetworkValid();
@@ -2790,7 +2544,7 @@
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // If the user accepted partial connectivity, and the device auto-reconnects to the partial
         // connectivity network, it should contain both PARTIAL_CONNECTIVITY and VALIDATED.
@@ -2805,11 +2559,11 @@
         waitForIdle();
         verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(
                 NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
     }
 
     @Test
@@ -2851,7 +2605,7 @@
                 false /* always */);
         waitForIdle();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         NetworkCapabilities nc =
                 validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
@@ -2884,7 +2638,7 @@
         // Take down network.
         // Expect onLost callback.
         mWiFiNetworkAgent.disconnect();
-        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
@@ -2898,7 +2652,7 @@
         // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
         mWiFiNetworkAgent.setNetworkValid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
         validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
@@ -2910,7 +2664,7 @@
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
         mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
-        validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
     }
 
     @Test
@@ -2942,7 +2696,7 @@
         mWiFiNetworkAgent.setNetworkPortal("http://example.com");
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
         mCm.startCaptivePortalApp(wifiNetwork);
@@ -2963,7 +2717,7 @@
         mWiFiNetworkAgent.setNetworkValid();
         mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         verify(mNotificationManager, times(1)).notifyAsUser(anyString(),
                 eq(NotificationType.LOGGED_IN.eventId), any(), eq(UserHandle.ALL));
 
@@ -3103,24 +2857,24 @@
         mWiFiNetworkAgent.setNetworkSpecifier(nsFoo);
         cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsFoo),
-                    mWiFiNetworkAgent);
+            c.expectCapabilitiesThat(mWiFiNetworkAgent,
+                    (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
         }
-        cFoo.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsFoo),
-                mWiFiNetworkAgent);
+        cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier().equals(nsFoo));
         assertEquals(nsFoo,
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
         cFoo.assertNoCallback();
 
         mWiFiNetworkAgent.setNetworkSpecifier(nsBar);
-        cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        cFoo.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsBar),
-                    mWiFiNetworkAgent);
+            c.expectCapabilitiesThat(mWiFiNetworkAgent,
+                    (caps) -> caps.getNetworkSpecifier().equals(nsBar));
         }
-        cBar.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier().equals(nsBar),
-                mWiFiNetworkAgent);
+        cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier().equals(nsBar));
         assertEquals(nsBar,
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
         cBar.assertNoCallback();
@@ -3128,23 +2882,23 @@
         mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier());
         cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         for (TestNetworkCallback c : emptyCallbacks) {
-            c.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null,
-                    mWiFiNetworkAgent);
+            c.expectCapabilitiesThat(mWiFiNetworkAgent,
+                    (caps) -> caps.getNetworkSpecifier() == null);
         }
-        cFoo.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null,
-                mWiFiNetworkAgent);
-        cBar.expectCapabilitiesLike((caps) -> caps.getNetworkSpecifier() == null,
-                mWiFiNetworkAgent);
+        cFoo.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier() == null);
+        cBar.expectCapabilitiesThat(mWiFiNetworkAgent,
+                (caps) -> caps.getNetworkSpecifier() == null);
         assertNull(
                 mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
         cFoo.assertNoCallback();
         cBar.assertNoCallback();
 
         mWiFiNetworkAgent.setNetworkSpecifier(null);
-        cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        cBar.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        cFoo.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        cBar.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         for (TestNetworkCallback c: emptyCallbacks) {
-            c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
+            c.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
         }
 
         assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
@@ -3287,7 +3041,7 @@
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -3302,11 +3056,11 @@
         // followed by AVAILABLE cell.
         mWiFiNetworkAgent.disconnect();
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
@@ -3322,7 +3076,7 @@
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         vpnNetworkAgent.disconnect();
-        defaultNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        defaultNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
     }
@@ -3350,14 +3104,15 @@
         lp.setInterfaceName("foonet_data0");
         mCellNetworkAgent.sendLinkProperties(lp);
         // We should get onLinkPropertiesChanged().
-        cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
+                mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Suspend the network.
         mCellNetworkAgent.suspend();
         cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_SUSPENDED,
                 mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.SUSPENDED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.SUSPENDED, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Register a garden variety default network request.
@@ -3372,7 +3127,7 @@
         mCellNetworkAgent.resume();
         cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_SUSPENDED,
                 mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.RESUMED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.RESUMED, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         dfltNetworkCallback = new TestNetworkCallback();
@@ -3435,10 +3190,10 @@
 
         // When wifi connects, cell lingers.
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        fgCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -3446,7 +3201,7 @@
         // When lingering is complete, cell is still there but is now in the background.
         waitForIdle();
         int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
-        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
+        fgCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent, timeoutMs);
         // Expect a network capabilities update sans FOREGROUND.
         callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
@@ -3472,7 +3227,7 @@
         // Release the request. The network immediately goes into the background, since it was not
         // lingering.
         mCm.unregisterNetworkCallback(cellCallback);
-        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        fgCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         // Expect a network capabilities update sans FOREGROUND.
         callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
@@ -3480,8 +3235,8 @@
 
         // Disconnect wifi and check that cell is foreground again.
         mWiFiNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        fgCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
@@ -3618,7 +3373,7 @@
         testFactory.waitForNetworkRequests(1);
 
         // ...  and cell data to be torn down.
-        cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         assertLength(1, mCm.getAllNetworks());
 
         testFactory.unregister();
@@ -3709,7 +3464,7 @@
         mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedWifiCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Because avoid bad wifi is off, we don't switch to cellular.
         defaultCallback.assertNoCallback();
@@ -3753,7 +3508,7 @@
         mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedWifiCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Simulate the user selecting "switch" and checking the don't ask again checkbox.
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
@@ -3780,7 +3535,7 @@
 
         // If cell goes down, we switch to wifi.
         mCellNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedWifiCallback.assertNoCallback();
 
@@ -3845,7 +3600,7 @@
         networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false,
                 TEST_CALLBACK_TIMEOUT_MS);
         mWiFiNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
 
         // Validate that UNAVAILABLE is not called
         networkCallback.assertNoCallback();
@@ -3865,7 +3620,7 @@
         mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is called
-        networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
+        networkCallback.expectCallback(CallbackRecord.UNAVAILABLE, null);
 
         // create a network satisfying request - validate that request not triggered
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -3956,7 +3711,7 @@
             // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
             testFactory.triggerUnfulfillable(requests.get(newRequestId));
 
-            networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
+            networkCallback.expectCallback(CallbackRecord.UNAVAILABLE, null);
             testFactory.waitForRequests();
 
             // unregister network callback - a no-op (since already freed by the
@@ -4797,7 +4552,7 @@
 
         // Disconnect wifi aware network.
         wifiAware.disconnect();
-        callback.expectCallbackLike((info) -> info.state == CallbackState.LOST, TIMEOUT_MS);
+        callback.expectCallbackThat(TIMEOUT_MS, (info) -> info instanceof CallbackRecord.Lost);
         mCm.unregisterNetworkCallback(callback);
 
         verifyNoNetwork();
@@ -4846,14 +4601,15 @@
         // ConnectivityService.
         MockNetworkAgent networkAgent = new MockNetworkAgent(TRANSPORT_WIFI, lp);
         networkAgent.connect(true);
-        networkCallback.expectCallback(CallbackState.AVAILABLE, networkAgent);
-        networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent);
-        CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        networkCallback.expectCallback(CallbackRecord.AVAILABLE, networkAgent);
+        networkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, networkAgent);
+        CallbackRecord.LinkPropertiesChanged cbi =
+                networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 networkAgent);
-        networkCallback.expectCallback(CallbackState.BLOCKED_STATUS, networkAgent);
+        networkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, networkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent);
         networkCallback.assertNoCallback();
-        checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address),
+        checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address),
                 Arrays.asList(myIpv4DefaultRoute));
         checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()),
                 Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute));
@@ -4865,9 +4621,9 @@
         newLp.addLinkAddress(myIpv6Address1);
         newLp.addLinkAddress(myIpv6Address2);
         networkAgent.sendLinkProperties(newLp);
-        cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent);
+        cbi = networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, networkAgent);
         networkCallback.assertNoCallback();
-        checkDirectlyConnectedRoutes(cbi.arg,
+        checkDirectlyConnectedRoutes(cbi.getLp(),
                 Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
                 Arrays.asList(myIpv4DefaultRoute));
         mCm.unregisterNetworkCallback(networkCallback);
@@ -5071,15 +4827,15 @@
         assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers,
                 new String[] { "2001:db8::1", "192.0.2.1" }));
         reset(mMockDnsResolver);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
+        cellNetworkCallback.expectCallback(CallbackRecord.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED,
                 mCellNetworkAgent);
-        CallbackInfo cbi = cellNetworkCallback.expectCallback(
-                CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent);
+        CallbackRecord.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback(
+                CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
 
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
         verify(mMockDnsResolver, times(1)).setResolverConfiguration(
@@ -5107,11 +4863,11 @@
         setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com");
         // Can't test dns configuration for strict mode without properly mocking
         // out the DNS lookups, but can test that LinkProperties is updated.
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertEquals("strict.example.com", ((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertTrue(cbi.getLp().isPrivateDnsActive());
+        assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName());
     }
 
     @Test
@@ -5130,17 +4886,17 @@
         mCellNetworkAgent.sendLinkProperties(lp);
         mCellNetworkAgent.connect(false);
         waitForIdle();
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES,
+        cellNetworkCallback.expectCallback(CallbackRecord.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED,
                 mCellNetworkAgent);
-        CallbackInfo cbi = cellNetworkCallback.expectCallback(
-                CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent);
+        CallbackRecord.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback(
+                CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        cellNetworkCallback.expectCallback(CallbackRecord.BLOCKED_STATUS, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
         Set<InetAddress> dnsServers = new HashSet<>();
-        checkDnsServers(cbi.arg, dnsServers);
+        checkDnsServers(cbi.getLp(), dnsServers);
 
         // Send a validation event for a server that is not part of the current
         // resolver config. The validation event should be ignored.
@@ -5152,13 +4908,13 @@
         LinkProperties lp2 = new LinkProperties(lp);
         lp2.addDnsServer(InetAddress.getByName("145.100.185.16"));
         mCellNetworkAgent.sendLinkProperties(lp2);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
         dnsServers.add(InetAddress.getByName("145.100.185.16"));
-        checkDnsServers(cbi.arg, dnsServers);
+        checkDnsServers(cbi.getLp(), dnsServers);
 
         // Send a validation event containing a hostname that is not part of
         // the current resolver config. The validation event should be ignored.
@@ -5176,39 +4932,39 @@
         // private dns fields should be sent.
         mService.mNetdEventCallback.onPrivateDnsValidationEvent(
                 mCellNetworkAgent.getNetwork().netId, "145.100.185.16", "", true);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
-        checkDnsServers(cbi.arg, dnsServers);
+        assertTrue(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
+        checkDnsServers(cbi.getLp(), dnsServers);
 
         // The private dns fields in LinkProperties should be preserved when
         // the network agent sends unrelated changes.
         LinkProperties lp3 = new LinkProperties(lp2);
         lp3.setMtu(1300);
         mCellNetworkAgent.sendLinkProperties(lp3);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertTrue(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
-        checkDnsServers(cbi.arg, dnsServers);
-        assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
+        assertTrue(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
+        checkDnsServers(cbi.getLp(), dnsServers);
+        assertEquals(1300, cbi.getLp().getMtu());
 
         // Removing the only validated server should affect the private dns
         // fields in LinkProperties.
         LinkProperties lp4 = new LinkProperties(lp3);
         lp4.removeDnsServer(InetAddress.getByName("145.100.185.16"));
         mCellNetworkAgent.sendLinkProperties(lp4);
-        cbi = cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+        cbi = cellNetworkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED,
                 mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
-        assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive());
-        assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName());
+        assertFalse(cbi.getLp().isPrivateDnsActive());
+        assertNull(cbi.getLp().getPrivateDnsServerName());
         dnsServers.remove(InetAddress.getByName("145.100.185.16"));
-        checkDnsServers(cbi.arg, dnsServers);
-        assertEquals(1300, ((LinkProperties)cbi.arg).getMtu());
+        checkDnsServers(cbi.getLp(), dnsServers);
+        assertEquals(1300, cbi.getLp().getMtu());
     }
 
     private void checkDirectlyConnectedRoutes(Object callbackObj,
@@ -5290,19 +5046,19 @@
         defaultCallback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        genericNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
         genericNotVpnNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCapabilitiesLike(nc -> null == nc.getUids(), vpnNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent, nc -> null == nc.getUids());
+        defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         ranges.clear();
         vpnNetworkAgent.setUids(ranges);
 
-        genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
 
         // TODO : The default network callback should actually get a LOST call here (also see the
         // comment below for AVAILABLE). This is because ConnectivityService does not look at UID
@@ -5310,7 +5066,7 @@
         // can't currently update their UIDs without disconnecting, so this does not matter too
         // much, but that is the reason the test here has to check for an update to the
         // capabilities instead of the expected LOST then AVAILABLE.
-        defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
 
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setUids(ranges);
@@ -5322,23 +5078,23 @@
         vpnNetworkCallback.expectAvailableCallbacksValidated(vpnNetworkAgent);
         // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
         // happen outside of the test, ConnectivityService does not rematch callbacks.
-        defaultCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
 
         mWiFiNetworkAgent.disconnect();
 
-        genericNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        genericNotVpnNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        genericNotVpnNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         vpnNetworkCallback.assertNoCallback();
         defaultCallback.assertNoCallback();
 
         vpnNetworkAgent.disconnect();
 
-        genericNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        genericNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        vpnNetworkCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         assertEquals(null, mCm.getActiveNetwork());
 
         mCm.unregisterNetworkCallback(genericNetworkCallback);
@@ -5402,7 +5158,7 @@
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         vpnNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
 
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -5432,7 +5188,7 @@
         // Even though the VPN is unvalidated, it becomes the default network for our app.
         callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
         // TODO: this looks like a spurious callback.
-        callback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        callback.expectCallback(CallbackRecord.NETWORK_CAPS_UPDATED, vpnNetworkAgent);
         callback.assertNoCallback();
 
         assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
@@ -5456,7 +5212,7 @@
         callback.assertNoCallback();
 
         vpnNetworkAgent.disconnect();
-        callback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        callback.expectCallback(CallbackRecord.LOST, vpnNetworkAgent);
         callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
     }
 
@@ -5496,10 +5252,10 @@
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
@@ -5508,52 +5264,52 @@
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Don't disconnect, but note the VPN is not using wifi any more.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Use Wifi but not cell. Note the VPN is now unmetered.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Use both again.
         mService.setUnderlyingNetworksForVpn(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect cell. Receive update without even removing the dead network from the
         // underlying networks – it's dead anyway. Not metered any more.
         mCellNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect wifi too. No underlying networks means this is now metered.
         mWiFiNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         mMockVpn.disconnect();
     }
@@ -5592,20 +5348,20 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Connect to WiFi; WiFi is the new default.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         mWiFiNetworkAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect Cell. The default network did not change, so there shouldn't be any changes in
         // the capabilities.
@@ -5614,10 +5370,10 @@
         // Disconnect wifi too. Now we have no default network.
         mWiFiNetworkAgent.disconnect();
 
-        vpnNetworkCallback.expectCapabilitiesLike((caps) -> caps.hasTransport(TRANSPORT_VPN)
+        vpnNetworkCallback.expectCapabilitiesThat(vpnNetworkAgent,
+                (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED),
-                vpnNetworkAgent);
+                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         mMockVpn.disconnect();
     }
@@ -5893,7 +5649,7 @@
 
         // Switch to METERED network. Restrict the use of the network.
         mWiFiNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent);
 
         // Network becomes NOT_METERED.
@@ -5907,7 +5663,7 @@
         defaultCallback.assertNoCallback();
 
         mCellNetworkAgent.disconnect();
-        defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        defaultCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         defaultCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -5983,7 +5739,7 @@
         // the NAT64 prefix was removed because one was never discovered.
         cellLp.addLinkAddress(myIpv4);
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
 
@@ -5996,7 +5752,7 @@
         cellLp.removeLinkAddress(myIpv4);
         cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
 
         // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started.
@@ -6004,15 +5760,15 @@
         assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix());
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
                 kNat64PrefixString, 96);
-        LinkProperties lpBeforeClat = (LinkProperties) networkCallback.expectCallback(
-                CallbackState.LINK_PROPERTIES, mCellNetworkAgent).arg;
+        LinkProperties lpBeforeClat = networkCallback.expectCallback(
+                CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp();
         assertEquals(0, lpBeforeClat.getStackedLinks().size());
         assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix());
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
         // Clat iface comes up. Expect stacked link to be added.
         clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
                 .getStackedLinks();
         assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0));
@@ -6020,7 +5776,7 @@
         // Change trivial linkproperties and see if stacked link is preserved.
         cellLp.addDnsServer(InetAddress.getByName("8.8.8.8"));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
 
         List<LinkProperties> stackedLpsAfterChange =
                 mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks();
@@ -6038,12 +5794,12 @@
         cellLp.addLinkAddress(myIpv4);
         cellLp.addRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
         verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
 
         // As soon as stop is called, the linkproperties lose the stacked interface.
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
         LinkProperties expected = new LinkProperties(cellLp);
         expected.setNat64Prefix(kNat64Prefix);
@@ -6062,41 +5818,39 @@
         // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
                 kNat64PrefixString, 96);
-        networkCallback.expectLinkPropertiesLike((lp) -> lp.getNat64Prefix() == null,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getNat64Prefix() == null);
 
         // Remove IPv4 address and expect prefix discovery and clatd to be started again.
         cellLp.removeLinkAddress(myIpv4);
         cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
         cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8"));
         mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, true /* added */,
                 kNat64PrefixString, 96);
-        networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
 
         // Clat iface comes up. Expect stacked link to be added.
         clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
-        networkCallback.expectLinkPropertiesLike(
-                (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null);
 
         // NAT64 prefix is removed. Expect that clat is stopped.
         mService.mNetdEventCallback.onNat64PrefixEvent(cellNetId, false /* added */,
                 kNat64PrefixString, 96);
-        networkCallback.expectLinkPropertiesLike(
-                (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null);
         verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME);
-        networkCallback.expectLinkPropertiesLike((lp) -> lp.getStackedLinks().size() == 0,
-                mCellNetworkAgent);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                (lp) -> lp.getStackedLinks().size() == 0);
 
         // Clean up.
         mCellNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         networkCallback.assertNoCallback();
         mCm.unregisterNetworkCallback(networkCallback);
     }
@@ -6128,7 +5882,7 @@
         reset(mNetworkManagementService);
         mWiFiNetworkAgent.connect(true);
         networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         verify(mNetworkManagementService, times(1)).addIdleTimer(eq(WIFI_IFNAME), anyInt(),
                 eq(ConnectivityManager.TYPE_WIFI));
@@ -6137,7 +5891,7 @@
         // Disconnect wifi and switch back to cell
         reset(mNetworkManagementService);
         mWiFiNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mWiFiNetworkAgent);
         assertNoCallbacks(networkCallback);
         verify(mNetworkManagementService, times(1)).removeIdleTimer(eq(WIFI_IFNAME));
         verify(mNetworkManagementService, times(1)).addIdleTimer(eq(MOBILE_IFNAME), anyInt(),
@@ -6149,14 +5903,14 @@
         mWiFiNetworkAgent.sendLinkProperties(wifiLp);
         mWiFiNetworkAgent.connect(true);
         networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        networkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         // Disconnect cell
         reset(mNetworkManagementService);
         reset(mMockNetd);
         mCellNetworkAgent.disconnect();
-        networkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
+        networkCallback.expectCallback(CallbackRecord.LOST, mCellNetworkAgent);
         // LOST callback is triggered earlier than removing idle timer. Broadcast should also be
         // sent as network being switched. Ensure rule removal for cell will not be triggered
         // unexpectedly before network being removed.
diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/ResourcePathShortener.cpp
index 845262b..6b11de7 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.cpp
+++ b/tools/aapt2/optimize/ResourcePathShortener.cpp
@@ -23,6 +23,7 @@
 
 #include "ResourceTable.h"
 #include "ValueVisitor.h"
+#include "util/Util.h"
 
 
 static const std::string base64_chars =
@@ -95,8 +96,8 @@
     android::StringPiece res_subdir, actual_filename, extension;
     util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension);
 
-    // Android detects ColorStateLists via pathname, skip res/color/*
-    if (res_subdir == android::StringPiece("res/color/"))
+    // Android detects ColorStateLists via pathname, skip res/color*
+    if (util::StartsWith(res_subdir, "res/color"))
       continue;
 
     std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/ResourcePathShortener_test.cpp
index efbef8f..1f45694 100644
--- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp
+++ b/tools/aapt2/optimize/ResourcePathShortener_test.cpp
@@ -75,6 +75,9 @@
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
+          .AddFileReference("android:color/colorlist",
+                            "res/color-mdp-v21/colorlist.xml",
+                            test::ParseConfigOrDie("mdp-v21"))
           .Build();
 
   std::map<std::string, std::string> path_map;
@@ -82,6 +85,7 @@
 
   // Expect that the path map to not contain the ColorStateList
   ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
+  ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
 }
 
 TEST(ResourcePathShortenerTest, KeepExtensions) {