Merge "Preserve the controlling state of appearance and behavior"
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5b86e8d..4fb283a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -144,15 +144,13 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizePrimarySplitScreen(@NonNull android.graphics.Rect, @NonNull android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizeTask(int, android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect, boolean) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean setTaskWindowingModeSplitScreenPrimary(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSystemLockTaskMode(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode();
     method public static boolean supportsMultiWindow(android.content.Context);
     method public static boolean supportsSplitScreenMultiWindow(android.content.Context);
     field public static final int DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440; // 0x1b8
     field public static final int INVALID_STACK_ID = -1; // 0xffffffff
-    field public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1; // 0x1
-    field public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0; // 0x0
   }
 
   public class ActivityView extends android.view.ViewGroup {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a2b9157..f541e1a 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -17,7 +17,6 @@
 package android.app;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
@@ -256,13 +255,6 @@
             "android.activity.freezeRecentTasksReordering";
 
     /**
-     * Where the split-screen-primary stack should be positioned.
-     * @hide
-     */
-    private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
-            "android:activity.splitScreenCreateMode";
-
-    /**
      * Determines whether to disallow the outgoing activity from entering picture-in-picture as the
      * result of a new activity being launched.
      * @hide
@@ -373,7 +365,6 @@
     private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
     private int mLaunchTaskId = -1;
     private int mPendingIntentLaunchFlags;
-    private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     private boolean mLockTaskMode = false;
     private boolean mDisallowEnterPictureInPictureWhileLaunching;
     private boolean mApplyActivityFlagsForBubbles;
@@ -1049,8 +1040,6 @@
         mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
         mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false);
         mFreezeRecentTasksReordering = opts.getBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, false);
-        mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
-                SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
         mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
                 KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
         mApplyActivityFlagsForBubbles = opts.getBoolean(
@@ -1469,14 +1458,9 @@
     }
 
     /** @hide */
-    public int getSplitScreenCreateMode() {
-        return mSplitScreenCreateMode;
-    }
-
-    /** @hide */
     @UnsupportedAppUsage
     public void setSplitScreenCreateMode(int splitScreenCreateMode) {
-        mSplitScreenCreateMode = splitScreenCreateMode;
+        // Remove this method after @UnsupportedAppUsage can be removed.
     }
 
     /** @hide */
@@ -1709,9 +1693,6 @@
         if (mFreezeRecentTasksReordering) {
             b.putBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, mFreezeRecentTasksReordering);
         }
-        if (mSplitScreenCreateMode != SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT) {
-            b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
-        }
         if (mDisallowEnterPictureInPictureWhileLaunching) {
             b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
                     mDisallowEnterPictureInPictureWhileLaunching);
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 2060252..fbc3b0d 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
@@ -58,20 +60,6 @@
     public static final int INVALID_TASK_ID = -1;
 
     /**
-     * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which
-     * specifies the position of the created docked stack at the top half of the screen if
-     * in portrait mode or at the left half of the screen if in landscape mode.
-     */
-    public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
-
-    /**
-     * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which
-     * specifies the position of the created docked stack at the bottom half of the screen if
-     * in portrait mode or at the right half of the screen if in landscape mode.
-     */
-    public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
-
-    /**
      * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
      * that the resize doesn't need to preserve the window, and can be skipped if bounds
      * is unchanged. This mode is used by window manager in most cases.
@@ -199,28 +187,12 @@
     /**
      * Moves the input task to the primary-split-screen stack.
      * @param taskId Id of task to move.
-     * @param createMode The mode the primary split screen stack should be created in if it doesn't
-     *                   exist already. See
-     *                   {@link ActivityTaskManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
-     *                   and
-     *                   {@link android.app.ActivityManager
-     *                        #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
      * @param toTop If the task and stack should be moved to the top.
-     * @param animate Whether we should play an animation for the moving the task
-     * @param initialBounds If the primary stack gets created, it will use these bounds for the
-     *                      docked stack. Pass {@code null} to use default bounds.
-     * @param showRecents If the recents activity should be shown on the other side of the task
-     *                    going into split-screen mode.
      * @return Whether the task was successfully put into splitscreen.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
-    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
-            boolean animate, Rect initialBounds, boolean showRecents) throws SecurityException {
-        try {
-            return getService().setTaskWindowingModeSplitScreenPrimary(taskId, toTop);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, boolean toTop) {
+        return setTaskWindowingMode(taskId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, toTop);
     }
 
     /**
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 523c155..1d65711 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -218,7 +218,7 @@
      */
     boolean setTaskWindowingMode(int taskId, int windowingMode, boolean toTop);
     void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop);
-    boolean setTaskWindowingModeSplitScreenPrimary(int taskId, boolean toTop);
+
     /**
      * Removes root tasks in the input windowing modes from the system if they are of activity type
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index ddcfb92..b1ca12cd 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -18,6 +18,9 @@
 
 import android.annotation.IntDef;
 import android.annotation.TestApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -27,6 +30,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.util.Printer;
 
 import java.lang.annotation.Retention;
@@ -866,6 +870,47 @@
     };
 
     /**
+     * This change id forces the packages it is applied to to be resizable. We only allow resizing
+     * in fullscreen windowing mode, but not forcing the app into resizable multi-windowing mode.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    public static final long FORCE_RESIZE_APP = 174042936L; // number refers to buganizer id
+
+    /**
+     * Return value for {@link #supportsSizeChanges()} indicating that this activity does not
+     * support size changes.
+     * @hide
+     */
+    public static final int SIZE_CHANGES_UNSUPPORTED = 0;
+
+    /**
+     * Return value for {@link #supportsSizeChanges()} indicating that this activity supports size
+     * changes due to the android.supports_size_changes metadata flag being set either on
+     * application or on activity level.
+     * @hide
+     */
+    public static final int SIZE_CHANGES_SUPPORTED_METADATA = 1;
+
+    /**
+     * Return value for {@link #supportsSizeChanges()} indicating that this activity has been
+     * overridden to support size changes through the compat framework change id
+     * {@link #FORCE_RESIZE_APP}.
+     * @hide
+     */
+    public static final int SIZE_CHANGES_SUPPORTED_OVERRIDE = 2;
+
+    /** @hide */
+    @IntDef(prefix = { "SIZE_CHANGES_" }, value = {
+            SIZE_CHANGES_UNSUPPORTED,
+            SIZE_CHANGES_SUPPORTED_METADATA,
+            SIZE_CHANGES_SUPPORTED_OVERRIDE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SizeChangesSupportMode {}
+
+    /**
      * Convert Java change bits to native.
      *
      * @hide
@@ -1146,6 +1191,25 @@
         return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0;
     }
 
+    /**
+     * Returns whether the activity supports size changes.
+     * @hide
+     */
+    @SizeChangesSupportMode
+    public int supportsSizeChanges() {
+        if (supportsSizeChanges) {
+            return SIZE_CHANGES_SUPPORTED_METADATA;
+        }
+
+        if (CompatChanges.isChangeEnabled(FORCE_RESIZE_APP,
+                applicationInfo.packageName,
+                UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+            return SIZE_CHANGES_SUPPORTED_OVERRIDE;
+        }
+
+        return SIZE_CHANGES_UNSUPPORTED;
+    }
+
     /** @hide */
     @UnsupportedAppUsage
     public static boolean isResizeableMode(int mode) {
@@ -1186,6 +1250,20 @@
         }
     }
 
+    /** @hide */
+    public static String sizeChangesSupportModeToString(@SizeChangesSupportMode int mode) {
+        switch (mode) {
+            case SIZE_CHANGES_UNSUPPORTED:
+                return "SIZE_CHANGES_UNSUPPORTED";
+            case SIZE_CHANGES_SUPPORTED_METADATA:
+                return "SIZE_CHANGES_SUPPORTED_METADATA";
+            case SIZE_CHANGES_SUPPORTED_OVERRIDE:
+                return "SIZE_CHANGES_SUPPORTED_OVERRIDE";
+            default:
+                return "unknown=" + mode;
+        }
+    }
+
     public void dump(Printer pw, String prefix) {
         dump(pw, prefix, DUMP_FLAG_ALL);
     }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a89de01..b846142 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -985,6 +985,15 @@
          */
         public abstract void getDeferredJobsLineLocked(StringBuilder sb, int which);
 
+        /**
+         * Returns the measured energy in microjoules that the display consumed while the screen
+         * was on and uid active.
+         * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+         *
+         * {@hide}
+         */
+        public abstract long getScreenOnEnergy();
+
         public static abstract class Sensor {
 
             @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 58268e2..0fe8894 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -68,6 +68,8 @@
  */
 @SystemApi
 public class DynamicSystemClient {
+    private static final String TAG = "DynamicSystemClient";
+
     /** @hide */
     @IntDef(prefix = { "STATUS_" }, value = {
             STATUS_UNKNOWN,
@@ -92,8 +94,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface StatusChangedCause {}
 
-    private static final String TAG = "DynSystemClient";
-
     /** Listener for installation status updates. */
     public interface OnStatusChangedListener {
         /**
@@ -240,7 +240,7 @@
 
     private class DynSystemServiceConnection implements ServiceConnection {
         public void onServiceConnected(ComponentName className, IBinder service) {
-            Slog.v(TAG, "DynSystemService connected");
+            Slog.v(TAG, "onServiceConnected: " + className);
 
             mService = new Messenger(service);
 
@@ -262,7 +262,7 @@
         }
 
         public void onServiceDisconnected(ComponentName className) {
-            Slog.v(TAG, "DynSystemService disconnected");
+            Slog.v(TAG, "onServiceDisconnected: " + className);
             mService = null;
         }
     }
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 7f01cad..e8e4785 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -269,4 +269,16 @@
             throw new RuntimeException(e.toString());
         }
     }
+
+    /**
+     * Returns the suggested scratch partition size for overlayFS.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public long suggestScratchSize() {
+        try {
+            return mService.suggestScratchSize();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
 }
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index df0a69b..a5a40ad 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -125,4 +125,9 @@
      *                      valid VBMeta block to retrieve the AVB key from.
      */
     boolean getAvbPublicKey(out AvbPublicKey dst);
+
+    /**
+     * Returns the suggested scratch partition size for overlayFS.
+     */
+    long suggestScratchSize();
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0fa0df6..93dff9f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -8648,6 +8648,15 @@
             }
         }
 
+        @Override
+        public long getScreenOnEnergy() {
+            if (mUidMeasuredEnergyStats == null) {
+                return ENERGY_DATA_UNAVAILABLE;
+            }
+            return mUidMeasuredEnergyStats.getAccumulatedBucketEnergy(
+                    MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+        }
+
         void initNetworkActivityLocked() {
             detachIfNotNull(mNetworkByteActivityCounters);
             mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index 8086ff2..e0d159b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -171,6 +171,7 @@
         mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
                 context.getPackageManager(), requestedBatterySipper);
         long totalScreenMeasuredEnergyUJ = batteryStats.getScreenOnEnergy();
+        long uidScreenMeasuredEnergyUJ = requestedBatterySipper.uidObj.getScreenOnEnergy();
 
         addEntry("Total power", EntryType.POWER,
                 requestedBatterySipper.totalSmearedPowerMah, totalSmearedPowerMah);
@@ -180,11 +181,12 @@
                 requestedBatterySipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
         addEntry("Screen, smeared", EntryType.POWER,
                 requestedBatterySipper.screenPowerMah, totalScreenPower);
-        if (totalScreenMeasuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
-            final double measuredCharge = UJ_2_MAH * totalScreenMeasuredEnergyUJ;
-            final double ratio = measuredCharge / totalScreenPower;
-            addEntry("Screen, smeared (PowerStatsHal adjusted)", EntryType.POWER,
-                    requestedBatterySipper.screenPowerMah * ratio, measuredCharge);
+        if (uidScreenMeasuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE
+                && totalScreenMeasuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+            final double measuredCharge = UJ_2_MAH * uidScreenMeasuredEnergyUJ;
+            final double totalMeasuredCharge = UJ_2_MAH * totalScreenMeasuredEnergyUJ;
+            addEntry("Screen, measured", EntryType.POWER,
+                    measuredCharge, totalMeasuredCharge);
         }
         addEntry("Other, smeared", EntryType.POWER,
                 requestedBatterySipper.proportionalSmearMah, totalProportionalSmearMah);
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index a3a3e7c..5031ff9 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -2,3 +2,10 @@
 per-file *MotionEventTest.* = michaelwr@google.com, svv@google.com
 per-file *KeyEventTest.* = michaelwr@google.com, svv@google.com
 per-file VelocityTest.java = michaelwr@google.com, svv@google.com
+
+# WindowManager
+per-file *Display* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Focus* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index a9d4094..f708298 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -60,7 +60,6 @@
     byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem);
     List<String> getCaCertificateChainAliases(String rootAlias, boolean includeDeletedSystem);
     void setCredentialManagementApp(String packageName, in AppUriAuthenticationPolicy policy);
-    void updateCredentialManagementAppPolicy(in AppUriAuthenticationPolicy policy);
     boolean hasCredentialManagementApp();
     String getCredentialManagementAppPackageName();
     AppUriAuthenticationPolicy getCredentialManagementAppPolicy();
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index c6e72b0..2f444b3 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -31,6 +31,7 @@
 import android.content.ServiceConnection;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Process;
@@ -854,10 +855,26 @@
     @WorkerThread
     public static KeyChainConnection bindAsUser(@NonNull Context context, UserHandle user)
             throws InterruptedException {
+        return bindAsUser(context, null, user);
+    }
+
+    /**
+     * Bind to KeyChainService in the target user.
+     * Caller should call unbindService on the result when finished.
+     *
+     * @throws InterruptedException if interrupted during binding.
+     * @throws AssertionError if unable to bind to KeyChainService.
+     * @hide
+     */
+    public static KeyChainConnection bindAsUser(@NonNull Context context, @Nullable Handler handler,
+            UserHandle user) throws InterruptedException {
+
         if (context == null) {
             throw new NullPointerException("context == null");
         }
-        ensureNotOnMainThread(context);
+        if (handler == null) {
+            ensureNotOnMainThread(context);
+        }
         if (!UserManager.get(context).isUserUnlocked(user)) {
             throw new IllegalStateException("User must be unlocked");
         }
@@ -884,9 +901,19 @@
         };
         Intent intent = new Intent(IKeyChainService.class.getName());
         ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
+        if (comp == null) {
+            throw new AssertionError("could not resolve KeyChainService");
+        }
         intent.setComponent(comp);
-        if (comp == null || !context.bindServiceAsUser(
-                intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, user)) {
+        final boolean bindSucceed;
+        if (handler != null) {
+            bindSucceed = context.bindServiceAsUser(
+                    intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, handler, user);
+        } else {
+            bindSucceed = context.bindServiceAsUser(
+                    intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, user);
+        }
+        if (!bindSucceed) {
             throw new AssertionError("could not bind to KeyChainService");
         }
         countDownLatch.await();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index d88696d..6b79a36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -503,31 +503,31 @@
                     || isSplitActive()) {
                 return false;
             }
-
-            // Try fetching the top running task.
-            final List<RunningTaskInfo> runningTasks =
-                    ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
-            if (runningTasks == null || runningTasks.isEmpty()) {
-                return false;
-            }
-            // Note: The set of running tasks from the system is ordered by recency.
-            final RunningTaskInfo topRunningTask = runningTasks.get(0);
-            final int activityType = topRunningTask.getActivityType();
-            if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
-                return false;
-            }
-
-            if (!topRunningTask.supportsSplitScreenMultiWindow) {
-                Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
-                        Toast.LENGTH_SHORT).show();
-                return false;
-            }
-
-            return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary(
-                    topRunningTask.taskId, true /* onTop */);
         } catch (RemoteException e) {
             return false;
         }
+
+        // Try fetching the top running task.
+        final List<RunningTaskInfo> runningTasks =
+                ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
+        if (runningTasks == null || runningTasks.isEmpty()) {
+            return false;
+        }
+        // Note: The set of running tasks from the system is ordered by recency.
+        final RunningTaskInfo topRunningTask = runningTasks.get(0);
+        final int activityType = topRunningTask.getActivityType();
+        if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+            return false;
+        }
+
+        if (!topRunningTask.supportsSplitScreenMultiWindow) {
+            Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
+                    Toast.LENGTH_SHORT).show();
+            return false;
+        }
+
+        return ActivityTaskManager.getInstance().setTaskWindowingModeSplitScreenPrimary(
+                topRunningTask.taskId, true /* onTop */);
     }
 
     @Override
diff --git a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProvider.aidl b/location/java/com/android/internal/location/timezone/ILocationTimeZoneProvider.aidl
deleted file mode 100644
index 16aa848..0000000
--- a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProvider.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.location.timezone;
-
-import com.android.internal.location.timezone.ILocationTimeZoneProviderManager;
-import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
-
-/**
- * Binder interface for location time zone provider implementations. Do not implement this
- * directly, extend {@link com.android.location.timezone.provider.LocationTimeZoneProviderBase}
- * instead.
- * @hide
- */
-interface ILocationTimeZoneProvider {
-
-    oneway void setLocationTimeZoneProviderManager(in ILocationTimeZoneProviderManager manager);
-
-    oneway void setRequest(in LocationTimeZoneProviderRequest request);
-}
diff --git a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProviderManager.aidl b/location/java/com/android/internal/location/timezone/ILocationTimeZoneProviderManager.aidl
deleted file mode 100644
index b5450b7..0000000
--- a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProviderManager.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.location.timezone;
-
-import com.android.internal.location.timezone.LocationTimeZoneEvent;
-
-/**
- * Binder interface for the manager of location time zone provider implementations.
- * @hide
- */
-interface ILocationTimeZoneProviderManager {
-    void onLocationTimeZoneEvent(in LocationTimeZoneEvent locationTimeZoneEvent);
-}
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.aidl b/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.aidl
deleted file mode 100644
index 199e067..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2020, 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.internal.location.timezone;
-
-parcelable LocationTimeZoneEvent;
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.java b/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.java
deleted file mode 100644
index 31c27d1..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.location.timezone;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An event containing location time zone information.
- *
- * @hide
- */
-public final class LocationTimeZoneEvent implements Parcelable {
-
-    @IntDef({ EVENT_TYPE_UNKNOWN, EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS,
-            EVENT_TYPE_UNCERTAIN })
-    public @interface EventType {}
-
-    /** Uninitialized value for {@link #mEventType} - must not be used for real events. */
-    private static final int EVENT_TYPE_UNKNOWN = 0;
-
-    /**
-     * Indicates there was a permanent failure. This is not generally expected, and probably means a
-     * required backend service has been turned down, or the client is unreasonably old.
-     */
-    public static final int EVENT_TYPE_PERMANENT_FAILURE = 1;
-
-    /**
-     * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
-     * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
-     */
-    public static final int EVENT_TYPE_SUCCESS = 2;
-
-    /**
-     * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
-     * the provider is unable to detect location, or there was a problem when resolving the location
-     * to a time zone.
-     */
-    public static final int EVENT_TYPE_UNCERTAIN = 3;
-
-    private static final int EVENT_TYPE_MAX = EVENT_TYPE_UNCERTAIN;
-
-    @EventType
-    private final int mEventType;
-
-    @NonNull
-    private final List<String> mTimeZoneIds;
-
-    private final long mElapsedRealtimeMillis;
-
-    private LocationTimeZoneEvent(@EventType int eventType, @NonNull List<String> timeZoneIds,
-            long elapsedRealtimeMillis) {
-        mEventType = checkValidEventType(eventType);
-        mTimeZoneIds = immutableList(timeZoneIds);
-
-        boolean emptyTimeZoneIdListExpected = eventType != EVENT_TYPE_SUCCESS;
-        Preconditions.checkState(!emptyTimeZoneIdListExpected || timeZoneIds.isEmpty());
-
-        mElapsedRealtimeMillis = elapsedRealtimeMillis;
-    }
-
-    /**
-     * Returns the time of this fix, in elapsed real-time since system boot.
-     *
-     * <p>This value can be reliably compared to {@link
-     * android.os.SystemClock#elapsedRealtime()}, to calculate the age of a fix and to compare
-     * {@link LocationTimeZoneEvent} instances.
-     *
-     * @return elapsed real-time of fix, in milliseconds
-     */
-    public long getElapsedRealtimeMillis() {
-        return mElapsedRealtimeMillis;
-    }
-
-    /**
-     * Returns the event type.
-     */
-    @Nullable
-    public @EventType int getEventType() {
-        return mEventType;
-    }
-
-    /**
-     * Gets the time zone IDs of this event. Contains zero or more IDs for a successful lookup.
-     * The value is undefined for an unsuccessful lookup. See also {@link #getEventType()}.
-     */
-    @NonNull
-    public List<String> getTimeZoneIds() {
-        return mTimeZoneIds;
-    }
-
-    @Override
-    public String toString() {
-        return "LocationTimeZoneEvent{"
-                + "mEventType=" + mEventType
-                + ", mTimeZoneIds=" + mTimeZoneIds
-                + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
-                + "(" + Duration.ofMillis(mElapsedRealtimeMillis) + ")"
-                + '}';
-    }
-
-    public static final @NonNull Parcelable.Creator<LocationTimeZoneEvent> CREATOR =
-            new Parcelable.Creator<LocationTimeZoneEvent>() {
-                @Override
-                public LocationTimeZoneEvent createFromParcel(Parcel in) {
-                    int eventType = in.readInt();
-                    @SuppressWarnings("unchecked")
-                    ArrayList<String> timeZoneIds =
-                            (ArrayList<String>) in.readArrayList(null /* classLoader */);
-                    long elapsedRealtimeMillis = in.readLong();
-                    return new LocationTimeZoneEvent(eventType, timeZoneIds, elapsedRealtimeMillis);
-                }
-
-                @Override
-                public LocationTimeZoneEvent[] newArray(int size) {
-                    return new LocationTimeZoneEvent[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeInt(mEventType);
-        parcel.writeList(mTimeZoneIds);
-        parcel.writeLong(mElapsedRealtimeMillis);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        LocationTimeZoneEvent that = (LocationTimeZoneEvent) o;
-        return mEventType == that.mEventType
-                && mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
-                && mTimeZoneIds.equals(that.mTimeZoneIds);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mEventType, mTimeZoneIds, mElapsedRealtimeMillis);
-    }
-
-    /** @hide */
-    public static final class Builder {
-
-        private @EventType int mEventType = EVENT_TYPE_UNKNOWN;
-        private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
-        private long mElapsedRealtimeMillis;
-
-        public Builder() {
-        }
-
-        /**
-         * Sets the contents of this from the supplied instance.
-         */
-        public Builder(@NonNull LocationTimeZoneEvent ltz) {
-            mEventType = ltz.mEventType;
-            mTimeZoneIds = ltz.mTimeZoneIds;
-            mElapsedRealtimeMillis = ltz.mElapsedRealtimeMillis;
-        }
-
-        /**
-         * Set the time zone ID of this event.
-         */
-        public Builder setEventType(@EventType int eventType) {
-            checkValidEventType(eventType);
-            mEventType = eventType;
-            return this;
-        }
-
-        /**
-         * Sets the time zone IDs of this event.
-         */
-        public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
-            mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
-            return this;
-        }
-
-        /**
-         * Sets the time of this event, in elapsed real-time since system boot.
-         */
-        public Builder setElapsedRealtimeMillis(long time) {
-            mElapsedRealtimeMillis = time;
-            return this;
-        }
-
-        /**
-         * Builds a {@link LocationTimeZoneEvent} instance.
-         */
-        public LocationTimeZoneEvent build() {
-            return new LocationTimeZoneEvent(mEventType, mTimeZoneIds, mElapsedRealtimeMillis);
-        }
-    }
-
-    private static int checkValidEventType(int eventType) {
-        if (eventType <= EVENT_TYPE_UNKNOWN || eventType > EVENT_TYPE_MAX) {
-            throw new IllegalStateException("eventType " + eventType + " unknown");
-        }
-        return eventType;
-    }
-
-    @NonNull
-    private static List<String> immutableList(@NonNull List<String> list) {
-        Objects.requireNonNull(list);
-        if (list.isEmpty()) {
-            return Collections.emptyList();
-        } else {
-            return Collections.unmodifiableList(new ArrayList<>(list));
-        }
-    }
-}
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.aidl b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.aidl
deleted file mode 100644
index bb59457..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.location.timezone;
-
-parcelable LocationTimeZoneProviderRequest;
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
deleted file mode 100644
index 5c9d290..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.location.timezone;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * A request passed to a location time zone provider to configure it.
- *
- * @hide
- */
-public final class LocationTimeZoneProviderRequest implements Parcelable {
-
-    public static final LocationTimeZoneProviderRequest EMPTY_REQUEST =
-            new LocationTimeZoneProviderRequest(
-                    false /* reportLocationTimeZone */,
-                    0 /* initializationTimeoutMillis */);
-
-    public static final Creator<LocationTimeZoneProviderRequest> CREATOR =
-            new Creator<LocationTimeZoneProviderRequest>() {
-                @Override
-                public LocationTimeZoneProviderRequest createFromParcel(Parcel in) {
-                    return LocationTimeZoneProviderRequest.createFromParcel(in);
-                }
-
-                @Override
-                public LocationTimeZoneProviderRequest[] newArray(int size) {
-                    return new LocationTimeZoneProviderRequest[size];
-                }
-            };
-
-    private final boolean mReportLocationTimeZone;
-
-    private final long mInitializationTimeoutMillis;
-
-    private LocationTimeZoneProviderRequest(
-            boolean reportLocationTimeZone, long initializationTimeoutMillis) {
-        mReportLocationTimeZone = reportLocationTimeZone;
-        mInitializationTimeoutMillis = initializationTimeoutMillis;
-    }
-
-    /**
-     * Returns {@code true} if the provider should report events related to the device's current
-     * time zone, {@code false} otherwise.
-     */
-    public boolean getReportLocationTimeZone() {
-        return mReportLocationTimeZone;
-    }
-
-    // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to
-    //  be passed to the LocationTimeZoneProvider and remove if it is not useful.
-    /**
-     * Returns the maximum time that the provider is allowed to initialize before it is expected to
-     * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
-     * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
-     * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
-     */
-    public long getInitializationTimeoutMillis() {
-        return mInitializationTimeoutMillis;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeBoolean(mReportLocationTimeZone);
-        parcel.writeLong(mInitializationTimeoutMillis);
-    }
-
-    static LocationTimeZoneProviderRequest createFromParcel(Parcel in) {
-        boolean reportLocationTimeZone = in.readBoolean();
-        long initializationTimeoutMillis = in.readLong();
-        return new LocationTimeZoneProviderRequest(
-                reportLocationTimeZone, initializationTimeoutMillis);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        LocationTimeZoneProviderRequest that = (LocationTimeZoneProviderRequest) o;
-        return mReportLocationTimeZone == that.mReportLocationTimeZone
-            && mInitializationTimeoutMillis == that.mInitializationTimeoutMillis;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mReportLocationTimeZone, mInitializationTimeoutMillis);
-    }
-
-    @Override
-    public String toString() {
-        return "LocationTimeZoneProviderRequest{"
-                + "mReportLocationTimeZone=" + mReportLocationTimeZone
-                + ", mInitializationTimeoutMillis=" + mInitializationTimeoutMillis
-                + "}";
-    }
-
-    /** @hide */
-    public static final class Builder {
-
-        private boolean mReportLocationTimeZone;
-        private long mInitializationTimeoutMillis;
-
-        /**
-         * Sets the property that enables / disables the provider. This is set to {@code false} by
-         * default.
-         */
-        public Builder setReportLocationTimeZone(boolean reportLocationTimeZone) {
-            mReportLocationTimeZone = reportLocationTimeZone;
-            return this;
-        }
-
-        /**
-         * Sets the initialization timeout. See {@link
-         * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()} for details.
-         */
-        public Builder setInitializationTimeoutMillis(long timeoutMillis) {
-            mInitializationTimeoutMillis = timeoutMillis;
-            return this;
-        }
-
-        /** Builds the {@link LocationTimeZoneProviderRequest} instance. */
-        @NonNull
-        public LocationTimeZoneProviderRequest build() {
-            return new LocationTimeZoneProviderRequest(
-                    mReportLocationTimeZone, mInitializationTimeoutMillis);
-        }
-    }
-}
diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt
index d4f7558..4e13487 100644
--- a/location/lib/api/current.txt
+++ b/location/lib/api/current.txt
@@ -67,35 +67,3 @@
 
 }
 
-package com.android.location.timezone.provider {
-
-  public final class LocationTimeZoneEventUnbundled {
-    method public int getEventType();
-    method @NonNull public java.util.List<java.lang.String> getTimeZoneIds();
-    field public static final int EVENT_TYPE_PERMANENT_FAILURE = 1; // 0x1
-    field public static final int EVENT_TYPE_SUCCESS = 2; // 0x2
-    field public static final int EVENT_TYPE_UNCERTAIN = 3; // 0x3
-  }
-
-  public static final class LocationTimeZoneEventUnbundled.Builder {
-    ctor public LocationTimeZoneEventUnbundled.Builder();
-    method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled build();
-    method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled.Builder setEventType(int);
-    method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled.Builder setTimeZoneIds(@NonNull java.util.List<java.lang.String>);
-  }
-
-  public abstract class LocationTimeZoneProviderBase {
-    ctor public LocationTimeZoneProviderBase(android.content.Context, String);
-    method public final android.os.IBinder getBinder();
-    method protected final android.content.Context getContext();
-    method protected abstract void onSetRequest(@NonNull com.android.location.timezone.provider.LocationTimeZoneProviderRequestUnbundled);
-    method protected final void reportLocationTimeZoneEvent(@NonNull com.android.location.timezone.provider.LocationTimeZoneEventUnbundled);
-  }
-
-  public final class LocationTimeZoneProviderRequestUnbundled {
-    method @IntRange(from=0) public long getInitializationTimeoutMillis();
-    method public boolean getReportLocationTimeZone();
-  }
-
-}
-
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
deleted file mode 100644
index 55f5545..0000000
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2020 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.location.timezone.provider;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.os.SystemClock;
-
-import com.android.internal.location.timezone.LocationTimeZoneEvent;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An event from a {@link LocationTimeZoneProviderBase} sent while determining a device's time zone
- * using its location.
- */
-public final class LocationTimeZoneEventUnbundled {
-
-    @IntDef({ EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS, EVENT_TYPE_UNCERTAIN })
-    @interface EventType {}
-
-    /**
-     * Indicates there was a permanent failure. This is not generally expected, and probably means a
-     * required backend service has been turned down, or the client is unreasonably old.
-     */
-    public static final int EVENT_TYPE_PERMANENT_FAILURE =
-            LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
-
-    /**
-     * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
-     * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
-     */
-    public static final int EVENT_TYPE_SUCCESS = LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
-
-    /**
-     * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
-     * the provider is unable to detect location, or there was a problem when resolving the location
-     * to a time zone.
-     */
-    public static final int EVENT_TYPE_UNCERTAIN = LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
-
-    @NonNull
-    private final LocationTimeZoneEvent mDelegate;
-
-    private LocationTimeZoneEventUnbundled(@NonNull LocationTimeZoneEvent delegate) {
-        mDelegate = Objects.requireNonNull(delegate);
-    }
-
-    /**
-     * Returns the event type.
-     */
-    public @EventType int getEventType() {
-        return mDelegate.getEventType();
-    }
-
-    /**
-     * Gets the time zone IDs of this event. Contains zero or more IDs for a successful lookup.
-     * The value is undefined for an unsuccessful lookup. See also {@link #getEventType()}.
-     */
-    @NonNull
-    public List<String> getTimeZoneIds() {
-        return mDelegate.getTimeZoneIds();
-    }
-
-    /**
-     * Returns the information from this as a {@link LocationTimeZoneEvent}.
-     * @hide
-     */
-    @NonNull
-    public LocationTimeZoneEvent getInternalLocationTimeZoneEvent() {
-        return mDelegate;
-    }
-
-    @Override
-    public String toString() {
-        return "LocationTimeZoneEventUnbundled{"
-                + "mDelegate=" + mDelegate
-                + '}';
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        LocationTimeZoneEventUnbundled that = (LocationTimeZoneEventUnbundled) o;
-        return mDelegate.equals(that.mDelegate);
-    }
-
-    @Override
-    public int hashCode() {
-        return mDelegate.hashCode();
-    }
-
-    /**
-     * A builder of {@link LocationTimeZoneEventUnbundled} instances.
-     */
-    public static final class Builder {
-
-        private @EventType int mEventType;
-        private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
-
-        /**
-         * Set the time zone ID of this event.
-         */
-        @NonNull
-        public Builder setEventType(@EventType int eventType) {
-            checkValidEventType(eventType);
-            mEventType = eventType;
-            return this;
-        }
-
-        /**
-         * Sets the time zone IDs of this event.
-         */
-        @NonNull
-        public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
-            mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
-            return this;
-        }
-
-        /**
-         * Builds a {@link LocationTimeZoneEventUnbundled} instance.
-         */
-        @NonNull
-        public LocationTimeZoneEventUnbundled build() {
-            final int internalEventType = this.mEventType;
-            LocationTimeZoneEvent event = new LocationTimeZoneEvent.Builder()
-                    .setEventType(internalEventType)
-                    .setTimeZoneIds(mTimeZoneIds)
-                    .setElapsedRealtimeMillis(SystemClock.elapsedRealtime())
-                    .build();
-            return new LocationTimeZoneEventUnbundled(event);
-        }
-    }
-
-    private static int checkValidEventType(int eventType) {
-        if (eventType != EVENT_TYPE_SUCCESS
-                && eventType != EVENT_TYPE_UNCERTAIN
-                && eventType != EVENT_TYPE_PERMANENT_FAILURE) {
-            throw new IllegalStateException("eventType=" + eventType);
-        }
-        return eventType;
-    }
-}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
deleted file mode 100644
index 68ae722..0000000
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2020 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.location.timezone.provider;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.location.timezone.ILocationTimeZoneProvider;
-import com.android.internal.location.timezone.ILocationTimeZoneProviderManager;
-import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
-
-import java.util.Objects;
-
-/**
- * A base class for location time zone providers implemented as unbundled services.
- *
- * <p>Provider implementations are enabled / disabled via a call to {@link
- * #onSetRequest(LocationTimeZoneProviderRequestUnbundled)}.
- *
- * <p>Once enabled, providers are expected to detect the time zone if possible, and report the
- * result via {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)}  with a type of
- * either {@link LocationTimeZoneEventUnbundled#EVENT_TYPE_UNCERTAIN} or {@link
- * LocationTimeZoneEventUnbundled#EVENT_TYPE_SUCCESS}. Providers may also report that they have
- * permanently failed by sending an event of type {@link
- * LocationTimeZoneEventUnbundled#EVENT_TYPE_PERMANENT_FAILURE}. See the javadocs for each event
- * type for details.
- *
- * <p>Providers are expected to issue their first event within {@link
- * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()}.
- *
- * <p>Once disabled or have failed, providers are required to stop producing events.
- *
- * <p>Threading:
- *
- * <p>Calls to {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} can be made on
- * on any thread, but may be processed asynchronously by the system server. Similarly, calls to
- * {@link #onSetRequest(LocationTimeZoneProviderRequestUnbundled)} may occur on any thread.
- *
- * <p>IMPORTANT: This class is effectively a public API for unbundled applications, and must remain
- * API stable.
- */
-public abstract class LocationTimeZoneProviderBase {
-
-    private final Context mContext;
-    private final String mTag;
-    private final IBinder mBinder;
-
-    // write locked on mBinder, read lock is optional depending on atomicity requirements
-    @Nullable private volatile ILocationTimeZoneProviderManager mManager;
-
-    public LocationTimeZoneProviderBase(Context context, String tag) {
-        mContext = context;
-        mTag = tag;
-        mBinder = new Service();
-        mManager = null;
-    }
-
-    protected final Context getContext() {
-        return mContext;
-    }
-
-    public final IBinder getBinder() {
-        return mBinder;
-    }
-
-    /**
-     * Reports a new location time zone event from this provider.
-     */
-    protected final void reportLocationTimeZoneEvent(
-            @NonNull LocationTimeZoneEventUnbundled event) {
-        ILocationTimeZoneProviderManager manager = mManager;
-        if (manager != null) {
-            try {
-                manager.onLocationTimeZoneEvent(event.getInternalLocationTimeZoneEvent());
-            } catch (RemoteException | RuntimeException e) {
-                Log.w(mTag, e);
-            }
-        }
-    }
-
-    /**
-     * Set the {@link LocationTimeZoneProviderRequestUnbundled} requirements for this provider. Each
-     * call to this method overrides all previous requests. This method might trigger the provider
-     * to start returning location time zones, or to stop returning location time zones, depending
-     * on the parameters in the request.
-     */
-    protected abstract void onSetRequest(@NonNull LocationTimeZoneProviderRequestUnbundled request);
-
-    private final class Service extends ILocationTimeZoneProvider.Stub {
-
-        @Override
-        public void setLocationTimeZoneProviderManager(ILocationTimeZoneProviderManager manager) {
-            mManager = Objects.requireNonNull(manager);
-        }
-
-        @Override
-        public void setRequest(LocationTimeZoneProviderRequest request) {
-            onSetRequest(new LocationTimeZoneProviderRequestUnbundled(request));
-        }
-    }
-}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
deleted file mode 100644
index 10d1038..0000000
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2020 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.location.timezone.provider;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
-
-import java.util.Objects;
-
-/**
- * This class is an interface to LocationTimeZoneProviderRequest for provider implementations.
- *
- * <p>IMPORTANT: This class is effectively a public API for unbundled code, and must remain API
- * stable.
- */
-public final class LocationTimeZoneProviderRequestUnbundled {
-
-    private final LocationTimeZoneProviderRequest mRequest;
-
-    /** @hide */
-    public LocationTimeZoneProviderRequestUnbundled(
-            @NonNull LocationTimeZoneProviderRequest request) {
-        mRequest = Objects.requireNonNull(request);
-    }
-
-    /**
-     * Returns {@code true} if the provider should report events related to the device's current
-     * time zone, {@code false} otherwise.
-     */
-    public boolean getReportLocationTimeZone() {
-        return mRequest.getReportLocationTimeZone();
-    }
-
-    /**
-     * Returns the maximum time that the provider is allowed to initialize before it is expected to
-     * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
-     * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
-     * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
-     */
-    @IntRange(from = 0)
-    public long getInitializationTimeoutMillis() {
-        return mRequest.getInitializationTimeoutMillis();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        LocationTimeZoneProviderRequestUnbundled that =
-                (LocationTimeZoneProviderRequestUnbundled) o;
-        return mRequest.equals(that.mRequest);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mRequest);
-    }
-
-    @Override
-    public String toString() {
-        return mRequest.toString();
-    }
-}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index a4896cb2..7f19662 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -74,7 +74,7 @@
 public class DynamicSystemInstallationService extends Service
         implements InstallationAsyncTask.ProgressListener {
 
-    private static final String TAG = "DynSystemInstallationService";
+    private static final String TAG = "DynamicSystemInstallationService";
 
     // TODO (b/131866826): This is currently for test only. Will move this to System API.
     static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index ac73f35..4ef5e2b 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -20,6 +20,7 @@
 import android.gsi.AvbPublicKey;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.MemoryFile;
 import android.os.ParcelFileDescriptor;
 import android.os.image.DynamicSystemManager;
@@ -51,7 +52,8 @@
     private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27;
 
     private static final List<String> UNSUPPORTED_PARTITIONS =
-            Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
+            Arrays.asList(
+                    "vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other", "scratch");
 
     private class UnsupportedUrlException extends Exception {
         private UnsupportedUrlException(String message) {
@@ -196,6 +198,22 @@
                 return null;
             }
 
+            if (Build.IS_DEBUGGABLE) {
+                // If host is debuggable, then install a scratch partition so that we can do
+                // adb remount in the guest system.
+                try {
+                    installScratch();
+                } catch (IOException e) {
+                    // Failing to install overlayFS scratch shouldn't be fatal.
+                    // Just ignore the error and skip installing the scratch partition.
+                    Log.w(TAG, e.toString(), e);
+                }
+                if (isCancelled()) {
+                    mDynSystem.remove();
+                    return null;
+                }
+            }
+
             mDynSystem.finishInstallation();
         } catch (Exception e) {
             Log.e(TAG, e.toString(), e);
@@ -302,12 +320,53 @@
         }
     }
 
-    private void installUserdata() throws Exception {
+    private void installScratch() throws IOException, InterruptedException {
+        final long scratchSize = mDynSystem.suggestScratchSize();
+        Thread thread = new Thread() {
+            @Override
+            public void run() {
+                mInstallationSession =
+                        mDynSystem.createPartition("scratch", scratchSize, /* readOnly= */ false);
+            }
+        };
+
+        Log.d(TAG, "Creating partition: scratch, size = " + scratchSize);
+        thread.start();
+
+        Progress progress = new Progress("scratch", scratchSize, mNumInstalledPartitions++);
+
+        while (thread.isAlive()) {
+            if (isCancelled()) {
+                return;
+            }
+
+            final long installedSize = mDynSystem.getInstallationProgress().bytes_processed;
+
+            if (installedSize > progress.installedSize + MIN_PROGRESS_TO_PUBLISH) {
+                progress.installedSize = installedSize;
+                publishProgress(progress);
+            }
+
+            Thread.sleep(100);
+        }
+
+        if (mInstallationSession == null) {
+            throw new IOException(
+                    "Failed to start installation with requested size: " + scratchSize);
+        }
+        // Reset installation session and verify that installation completes successfully.
+        mInstallationSession = null;
+        if (!mDynSystem.closePartition()) {
+            throw new IOException("Failed to complete partition installation: scratch");
+        }
+    }
+
+    private void installUserdata() throws IOException, InterruptedException {
         Thread thread = new Thread(() -> {
             mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false);
         });
 
-        Log.d(TAG, "Creating partition: userdata");
+        Log.d(TAG, "Creating partition: userdata, size = " + mUserdataSize);
         thread.start();
 
         Progress progress = new Progress("userdata", mUserdataSize, mNumInstalledPartitions++);
@@ -324,7 +383,7 @@
                 publishProgress(progress);
             }
 
-            Thread.sleep(10);
+            Thread.sleep(100);
         }
 
         if (mInstallationSession == null) {
@@ -445,7 +504,7 @@
                 return;
             }
 
-            Thread.sleep(10);
+            Thread.sleep(100);
         }
 
         if (mInstallationSession == null) {
diff --git a/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml b/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml
new file mode 100644
index 0000000..6521bc9
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml
@@ -0,0 +1,29 @@
+<!--
+  Copyright (C) 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
index b4b4c63..5ff0dc7 100644
--- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
@@ -92,16 +92,32 @@
                 android:textAlignment="viewEnd"
                 android:textColor="?android:attr/textColorSecondary"
                 android:maxLines="1"
+                android:visibility="gone"
                 android:ellipsize="end"/>
         </LinearLayout>
-        <ProgressBar
-            android:id="@android:id/progress"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="4dp"
-            android:max="100"
-            android:visibility="gone"/>
     </LinearLayout>
 
+    <LinearLayout
+        android:id="@+id/radio_extra_widget_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:gravity="center_vertical">
+        <View
+            android:layout_width=".75dp"
+            android:layout_height="match_parent"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="16dp"
+            android:background="?android:attr/dividerVertical" />
+        <ImageView
+            android:id="@+id/radio_extra_widget"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:src="@drawable/ic_settings_accent"
+            android:contentDescription="@string/settings_label"
+            android:paddingStart="16dp"
+            android:paddingEnd="16dp"
+            android:layout_gravity="center"
+            android:background="?android:attr/selectableItemBackground" />
+    </LinearLayout>
 </LinearLayout>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml b/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml
new file mode 100644
index 0000000..ff3f90c
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2021 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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Content description for RadioButton with extra gear icon [CHAR LIMIT=NONE] -->
+    <string name="settings_label">Settings</string>
+
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java b/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
index 05e008c..f50127f 100644
--- a/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
+++ b/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
@@ -20,7 +20,7 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.TextView;
+import android.widget.ImageView;
 
 import androidx.preference.CheckBoxPreference;
 import androidx.preference.PreferenceViewHolder;
@@ -34,6 +34,9 @@
  * In other words, there's no "RadioButtonPreferenceGroup" in this
  * implementation. When you check one RadioButtonPreference, if you want to
  * uncheck all the other preferences, you should do that by code yourself.
+ *
+ * RadioButtonPreference can assign a extraWidgetListener to show a gear icon
+ * on the right side that can open another page.
  */
 public class RadioButtonPreference extends CheckBoxPreference {
 
@@ -53,6 +56,10 @@
     private View mAppendix;
     private int mAppendixVisibility = -1;
 
+    private View mExtraWidgetContainer;
+    private ImageView mExtraWidget;
+
+    private View.OnClickListener mExtraWidgetOnClickListener;
 
     /**
      * Perform inflation from XML and apply a class-specific base style.
@@ -69,7 +76,6 @@
         init();
     }
 
-
     /**
      * Perform inflation from XML and apply a class-specific base style.
      *
@@ -136,11 +142,10 @@
             }
         }
 
-        TextView title = (TextView) holder.findViewById(android.R.id.title);
-        if (title != null) {
-            title.setSingleLine(false);
-            title.setMaxLines(3);
-        }
+        mExtraWidget = (ImageView) holder.findViewById(R.id.radio_extra_widget);
+        mExtraWidgetContainer = holder.findViewById(R.id.radio_extra_widget_container);
+
+        setExtraWidgetOnClickListener(mExtraWidgetOnClickListener);
     }
 
     /**
@@ -155,6 +160,24 @@
         mAppendixVisibility = visibility;
     }
 
+    /**
+     * Sets the callback to be invoked when extra widget is clicked by the user.
+     *
+     * @param listener The callback to be invoked
+     */
+    public void setExtraWidgetOnClickListener(View.OnClickListener listener) {
+        mExtraWidgetOnClickListener = listener;
+
+        if (mExtraWidget == null || mExtraWidgetContainer == null) {
+            return;
+        }
+
+        mExtraWidget.setOnClickListener(mExtraWidgetOnClickListener);
+
+        mExtraWidgetContainer.setVisibility((mExtraWidgetOnClickListener != null)
+                ? View.VISIBLE : View.GONE);
+    }
+
     private void init() {
         setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
         setLayoutResource(R.layout.preference_radio);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
index d58e68a..a5028ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
@@ -40,10 +40,30 @@
     private Application mContext;
     private RadioButtonPreference mPreference;
 
+    private View mExtraWidgetContainer;
+    private View mExtraWidget;
+
+    private boolean mIsClickListenerCalled;
+    private View.OnClickListener mClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mIsClickListenerCalled = true;
+        }
+    };
+
     @Before
     public void setUp() {
         mContext = RuntimeEnvironment.application;
         mPreference = new RadioButtonPreference(mContext);
+
+        View view = LayoutInflater.from(mContext)
+                .inflate(R.layout.preference_radio, null /* root */);
+        PreferenceViewHolder preferenceViewHolder =
+                PreferenceViewHolder.createInstanceForTests(view);
+        mPreference.onBindViewHolder(preferenceViewHolder);
+
+        mExtraWidgetContainer = view.findViewById(R.id.radio_extra_widget_container);
+        mExtraWidget = view.findViewById(R.id.radio_extra_widget);
     }
 
     @Test
@@ -57,26 +77,30 @@
     }
 
     @Test
-    public void summary_containerShouldBeVisible() {
+    public void onBindViewHolder_withSummary_containerShouldBeVisible() {
         mPreference.setSummary("some summary");
         View summaryContainer = new View(mContext);
         View view = mock(View.class);
         when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
         PreferenceViewHolder preferenceViewHolder =
                 PreferenceViewHolder.createInstanceForTests(view);
+
         mPreference.onBindViewHolder(preferenceViewHolder);
+
         assertEquals(View.VISIBLE, summaryContainer.getVisibility());
     }
 
     @Test
-    public void emptySummary_containerShouldBeGone() {
+    public void onBindViewHolder_emptySummary_containerShouldBeGone() {
         mPreference.setSummary("");
         View summaryContainer = new View(mContext);
         View view = mock(View.class);
         when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
         PreferenceViewHolder preferenceViewHolder =
                 PreferenceViewHolder.createInstanceForTests(view);
+
         mPreference.onBindViewHolder(preferenceViewHolder);
+
         assertEquals(View.GONE, summaryContainer.getVisibility());
     }
 
@@ -93,11 +117,36 @@
     }
 
     @Test
-    public void hideAppendix_shouldBeGone() {
+    public void setAppendixVisibility_setGone_shouldBeGone() {
         mPreference.setAppendixVisibility(View.GONE);
-        View view = LayoutInflater.from(mContext).inflate(R.layout.preference_radio, null);
+
+        View view = LayoutInflater.from(mContext)
+                .inflate(R.layout.preference_radio, null /* root */);
         PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
         mPreference.onBindViewHolder(holder);
         assertThat(holder.findViewById(R.id.appendix).getVisibility()).isEqualTo(View.GONE);
     }
+
+    @Test
+    public void setExtraWidgetListener_setNull_extraWidgetShouldInvisible() {
+        mPreference.setExtraWidgetOnClickListener(null);
+
+        assertEquals(View.GONE, mExtraWidgetContainer.getVisibility());
+    }
+
+    @Test
+    public void setExtraWidgetListener_extraWidgetShouldVisible() {
+        mPreference.setExtraWidgetOnClickListener(mClickListener);
+
+        assertEquals(View.VISIBLE, mExtraWidgetContainer.getVisibility());
+    }
+
+    @Test
+    public void onClickListener_setExtraWidgetOnClickListener_ShouldCalled() {
+        mPreference.setExtraWidgetOnClickListener(mClickListener);
+
+        assertThat(mIsClickListenerCalled).isFalse();
+        mExtraWidget.callOnClick();
+        assertThat(mIsClickListenerCalled).isTrue();
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index 1a71f11..d672c2c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.shared.system;
 
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -48,9 +46,6 @@
         options.setLaunchWindowingMode(isPrimary
                 ? WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                 : WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        options.setSplitScreenCreateMode(dockTopLeft
-                ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
         return options;
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index d3066b4..b38270c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -83,6 +83,11 @@
     public static final int WINDOWING_MODE_FREEFORM = WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
     public static final int ITYPE_EXTRA_NAVIGATION_BAR = InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+    public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = InsetsState.ITYPE_LEFT_TAPPABLE_ELEMENT;
+    public static final int ITYPE_TOP_TAPPABLE_ELEMENT = InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+    public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = InsetsState.ITYPE_RIGHT_TAPPABLE_ELEMENT;
+    public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT =
+            InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 
     private static final WindowManagerWrapper sInstance = new WindowManagerWrapper();
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index d58b95c..d1494df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -23,10 +23,16 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.Gefingerpoken;
+
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A Base class for all Keyguard password/pattern/pin related inputs.
  */
 public abstract class KeyguardInputView extends LinearLayout {
+    private final List<Gefingerpoken> mMotionEventListener = new ArrayList<>();
 
     public KeyguardInputView(Context context) {
         super(context);
@@ -56,4 +62,25 @@
     boolean startDisappearAnimation(Runnable finishRunnable) {
         return false;
     }
+
+    void addMotionEventListener(Gefingerpoken listener) {
+        mMotionEventListener.add(listener);
+    }
+
+    void removeMotionEventListener(Gefingerpoken listener) {
+        mMotionEventListener.remove(listener);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return mMotionEventListener.stream().anyMatch(listener -> listener.onTouchEvent(event))
+                || super.onTouchEvent(event);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return mMotionEventListener.stream().anyMatch(
+                listener -> listener.onInterceptTouchEvent(event))
+                || super.onInterceptTouchEvent(event);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 6aa5e0d..1c691e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -26,6 +26,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -155,6 +156,7 @@
         private final Resources mResources;
         private LiftToActivateListener mLiftToActivateListener;
         private TelephonyManager mTelephonyManager;
+        private final FalsingCollector mFalsingCollector;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -163,7 +165,7 @@
                 KeyguardMessageAreaController.Factory messageAreaControllerFactory,
                 InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
                 @Main Resources resources, LiftToActivateListener liftToActivateListener,
-                TelephonyManager telephonyManager) {
+                TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -173,6 +175,7 @@
             mResources = resources;
             mLiftToActivateListener = liftToActivateListener;
             mTelephonyManager = telephonyManager;
+            mFalsingCollector = falsingCollector;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -191,17 +194,17 @@
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener);
+                        mLiftToActivateListener, mFalsingCollector);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager);
+                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager);
+                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 4d0ebff..f247948 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -25,12 +25,15 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
 
 public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
         extends KeyguardAbsKeyInputViewController<T> {
 
     private final LiftToActivateListener mLiftToActivateListener;
+    private final FalsingCollector mFalsingCollector;
     protected PasswordTextView mPasswordEntry;
 
     private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -40,13 +43,26 @@
         return false;
     };
 
-    private final OnTouchListener mOnTouchListener = (v, event) -> {
+    private final OnTouchListener mActionButtonTouchListener = (v, event) -> {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             mView.doHapticKeyClick();
         }
         return false;
     };
 
+    private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent ev) {
+            mFalsingCollector.avoidGesture();
+            return false;
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent ev) {
+            return false;
+        }
+    };
+
     protected KeyguardPinBasedInputViewController(T view,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             SecurityMode securityMode,
@@ -54,10 +70,12 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
-            LiftToActivateListener liftToActivateListener) {
+            LiftToActivateListener liftToActivateListener,
+            FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker);
         mLiftToActivateListener = liftToActivateListener;
+        mFalsingCollector = falsingCollector;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
     }
 
@@ -65,11 +83,13 @@
     protected void onViewAttached() {
         super.onViewAttached();
 
+        mView.addMotionEventListener(mGlobalTouchListener);
+
         mPasswordEntry.setOnKeyListener(mOnKeyListener);
         mPasswordEntry.setUserActivityListener(this::onUserInput);
 
         View deleteButton = mView.findViewById(R.id.delete_button);
-        deleteButton.setOnTouchListener(mOnTouchListener);
+        deleteButton.setOnTouchListener(mActionButtonTouchListener);
         deleteButton.setOnClickListener(v -> {
             // check for time-based lockouts
             if (mPasswordEntry.isEnabled()) {
@@ -87,7 +107,7 @@
 
         View okButton = mView.findViewById(R.id.key_enter);
         if (okButton != null) {
-            okButton.setOnTouchListener(mOnTouchListener);
+            okButton.setOnTouchListener(mActionButtonTouchListener);
             okButton.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
@@ -101,6 +121,12 @@
     }
 
     @Override
+    protected void onViewDetached() {
+        super.onViewDetached();
+        mView.removeMotionEventListener(mGlobalTouchListener);
+    }
+
+    @Override
     public void onResume(int reason) {
         super.onResume(reason);
         mPasswordEntry.requestFocus();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index fb0d6be..c0aa2af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -22,6 +22,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
 
 public class KeyguardPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardPINView> {
@@ -32,10 +33,11 @@
             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker,
-            LiftToActivateListener liftToActivateListener) {
+            LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+                messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+                falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 5b4a7ff..b218141 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -38,6 +38,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
 
 public class KeyguardSimPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -76,11 +77,11 @@
             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker,
-            LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager) {
+            LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+                messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+                falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index eafb33f..890a17c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -39,6 +39,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
 
 public class KeyguardSimPukViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -83,11 +84,11 @@
             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker,
-            LiftToActivateListener liftToActivateListener,
-            TelephonyManager telephonyManager) {
+            LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+                messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+                falsingCollector);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e78057f..6572ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS;
 import static com.android.systemui.classifier.FalsingModule.BRIGHT_LINE_GESTURE_CLASSIFERS;
+import static com.android.systemui.classifier.FalsingModule.DOUBLE_TAP_TIMEOUT_MS;
 
 import android.net.Uri;
 import android.os.Build;
@@ -28,23 +29,26 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.TestHarness;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.sensors.ThresholdSensor;
-import com.android.systemui.util.time.SystemClock;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Queue;
 import java.util.Set;
 import java.util.StringJoiner;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -65,7 +69,8 @@
     private final SingleTapClassifier mSingleTapClassifier;
     private final DoubleTapClassifier mDoubleTapClassifier;
     private final HistoryTracker mHistoryTracker;
-    private final SystemClock mSystemClock;
+    private final DelayableExecutor mDelayableExecutor;
+    private final long mDoubleTapTimeMs;
     private final boolean mTestHarness;
     private final MetricsLogger mMetricsLogger;
     private int mIsFalseTouchCalls;
@@ -90,23 +95,44 @@
 
     private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener =
             new FalsingDataProvider.GestureCompleteListener() {
-        @Override
-        public void onGestureComplete() {
-            mHistoryTracker.addResults(
-                    mClassifiers.stream().map(FalsingClassifier::classifyGesture)
-                            .collect(Collectors.toCollection(ArrayList::new)),
-                    mSystemClock.uptimeMillis());
+                @Override
+        public void onGestureComplete(long completionTimeMs) {
+            if (mPriorResults != null) {
+                // Single taps that may become double taps don't get added right away.
+                if (mClassifyAsSingleTap) {
+                    Collection<FalsingClassifier.Result> singleTapResults = mPriorResults;
+                    mSingleTapHistoryCanceller = mDelayableExecutor.executeDelayed(
+                            () -> {
+                                mSingleTapHistoryCanceller = null;
+                                mHistoryTracker.addResults(singleTapResults, completionTimeMs);
+                            },
+                            mDoubleTapTimeMs);
+                    mClassifyAsSingleTap = false;  // Don't treat things as single taps by default.
+                } else {
+                    mHistoryTracker.addResults(mPriorResults, completionTimeMs);
+                }
+                mPriorResults = null;
+            } else {
+                // Gestures that were not classified get treated as a false.
+                mHistoryTracker.addResults(
+                        Collections.singleton(
+                                FalsingClassifier.Result.falsed(.8, "unclassified")),
+                        completionTimeMs);
+            }
         }
     };
 
-    private boolean mPreviousResult = false;
+    private Collection<FalsingClassifier.Result> mPriorResults;
+    private boolean mClassifyAsSingleTap;
+    private Runnable mSingleTapHistoryCanceller;
 
     @Inject
     public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
             DockManager dockManager, MetricsLogger metricsLogger,
             @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
             SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
-            HistoryTracker historyTracker, SystemClock systemClock,
+            HistoryTracker historyTracker, @Main DelayableExecutor delayableExecutor,
+            @Named(DOUBLE_TAP_TIMEOUT_MS) long doubleTapTimeMs,
             @TestHarness boolean testHarness) {
         mDataProvider = falsingDataProvider;
         mDockManager = dockManager;
@@ -115,7 +141,8 @@
         mSingleTapClassifier = singleTapClassifier;
         mDoubleTapClassifier = doubleTapClassifier;
         mHistoryTracker = historyTracker;
-        mSystemClock = systemClock;
+        mDelayableExecutor = delayableExecutor;
+        mDoubleTapTimeMs = doubleTapTimeMs;
         mTestHarness = testHarness;
 
         mDataProvider.addSessionListener(mSessionListener);
@@ -129,38 +156,51 @@
 
     @Override
     public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+        boolean result;
+
+        mClassifyAsSingleTap = false;
         mDataProvider.setInteractionType(interactionType);
-        if (!mDataProvider.isDirty()) {
-            return mPreviousResult;
+
+        if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) {
+            Stream<FalsingClassifier.Result> results =
+                    mClassifiers.stream().map(falsingClassifier -> {
+                        FalsingClassifier.Result classifierResult =
+                                falsingClassifier.classifyGesture(
+                                        mHistoryTracker.falsePenalty(),
+                                        mHistoryTracker.falseConfidence());
+                        if (classifierResult.isFalse()) {
+                            logInfo(String.format(
+                                    (Locale) null,
+                                    "{classifier=%s, interactionType=%d}",
+                                    falsingClassifier.getClass().getName(),
+                                    mDataProvider.getInteractionType()));
+                            String reason = classifierResult.getReason();
+                            if (reason != null) {
+                                logInfo(reason);
+                            }
+                        } else {
+                            logDebug(falsingClassifier.getClass().getName() + ": false");
+                        }
+                        return classifierResult;
+                    });
+            mPriorResults = new ArrayList<>();
+            final boolean[] localResult = {false};
+            results.forEach(classifierResult -> {
+                localResult[0] |= classifierResult.isFalse();
+                mPriorResults.add(classifierResult);
+            });
+            result = localResult[0];
+        } else {
+            result = false;
+            mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1));
         }
 
-        mPreviousResult = !mTestHarness
-                && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()
-                && mClassifiers.stream().anyMatch(falsingClassifier -> {
-                    FalsingClassifier.Result result = falsingClassifier.classifyGesture(
-                            mHistoryTracker.falsePenalty(), mHistoryTracker.falseConfidence());
-                    if (result.isFalse()) {
-                        logInfo(String.format(
-                                (Locale) null,
-                                "{classifier=%s, interactionType=%d}",
-                                falsingClassifier.getClass().getName(),
-                                mDataProvider.getInteractionType()));
-                        String reason = result.getReason();
-                        if (reason != null) {
-                            logInfo(reason);
-                        }
-                    } else {
-                        logDebug(falsingClassifier.getClass().getName() + ": false");
-                    }
-                    return result.isFalse();
-                });
-
-        logDebug("Is false touch? " + mPreviousResult);
+        logDebug("Is false touch? " + result);
 
         if (Build.IS_ENG || Build.IS_USERDEBUG) {
             // Copy motion events, as the passed in list gets emptied out elsewhere in the code.
             RECENT_SWIPES.add(new DebugSwipeRecord(
-                    mPreviousResult,
+                    result,
                     mDataProvider.getInteractionType(),
                     mDataProvider.getRecentMotionEvents().stream().map(
                             motionEvent -> new XYDt(
@@ -173,13 +213,16 @@
             }
         }
 
-        return mPreviousResult;
+        return result;
     }
 
     @Override
     public boolean isFalseTap(boolean robustCheck) {
+        mClassifyAsSingleTap = true;
+
         FalsingClassifier.Result singleTapResult =
                 mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents());
+        mPriorResults = Collections.singleton(singleTapResult);
         if (singleTapResult.isFalse()) {
             logInfo(String.format(
                     (Locale) null, "{classifier=%s}", mSingleTapClassifier.getClass().getName()));
@@ -192,7 +235,12 @@
 
         // TODO(b/172655679): More heuristics to come. For now, allow touches through if face-authed
         if (robustCheck) {
-            return !mDataProvider.isJustUnlockedWithFace();
+            boolean result = !mDataProvider.isJustUnlockedWithFace();
+            mPriorResults = Collections.singleton(
+                    result ? FalsingClassifier.Result.falsed(0.1, "no face detected")
+                            : FalsingClassifier.Result.passed(1));
+
+            return result;
         }
 
         return false;
@@ -200,7 +248,9 @@
 
     @Override
     public boolean isFalseDoubleTap() {
+        mClassifyAsSingleTap = false;
         FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture();
+        mPriorResults = Collections.singleton(result);
         if (result.isFalse()) {
             logInfo(String.format(
                     (Locale) null, "{classifier=%s}", mDoubleTapClassifier.getClass().getName()));
@@ -208,6 +258,12 @@
             if (reason != null) {
                 logInfo(reason);
             }
+        } else {
+            // A valid double tap prevents an invalid single tap from going into history.
+            if (mSingleTapHistoryCanceller != null) {
+                mSingleTapHistoryCanceller.run();
+                mSingleTapHistoryCanceller = null;
+            }
         }
         return result.isFalse();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index fe47162..b0bbab3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -116,6 +116,9 @@
     void onTouchEvent(MotionEvent ev);
 
     /** */
+    void avoidGesture();
+
+    /** */
     void cleanup();
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index fd05989..12a0604 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -147,6 +147,10 @@
     }
 
     @Override
+    public void avoidGesture() {
+    }
+
+    @Override
     public void cleanup() {
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 4c11ecf..e08b43b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -49,6 +49,8 @@
     private boolean mShowingAod;
     private boolean mScreenOn;
     private boolean mSessionStarted;
+    private MotionEvent mPendingDownEvent;
+    private boolean mAvoidGesture;
 
     private final ThresholdSensor.Listener mSensorEventListener = this::onProximityEvent;
 
@@ -245,7 +247,32 @@
 
     @Override
     public void onTouchEvent(MotionEvent ev) {
-        mFalsingDataProvider.onMotionEvent(ev);
+        // We delay processing down events to see if another component wants to process them.
+        // If #avoidGesture is called after a MotionEvent.ACTION_DOWN, all following motion events
+        // will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
+        // avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before
+        // any other events are processed, otherwise the whole gesture will be recorded.
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            // Make a copy of ev, since it will be recycled after we exit this method.
+            mPendingDownEvent = MotionEvent.obtain(ev);
+            mAvoidGesture = false;
+        } else if (!mAvoidGesture) {
+            if (mPendingDownEvent != null) {
+                mFalsingDataProvider.onMotionEvent(mPendingDownEvent);
+                mPendingDownEvent.recycle();
+                mPendingDownEvent = null;
+            }
+            mFalsingDataProvider.onMotionEvent(ev);
+        }
+    }
+
+    @Override
+    public void avoidGesture() {
+        if (mPendingDownEvent != null) {
+            mAvoidGesture = true;
+            mPendingDownEvent.recycle();
+            mPendingDownEvent = null;
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index deb9e6d..4bacc15 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -90,10 +90,8 @@
         }
 
         if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            if (!mRecentMotionEvents.isEmpty()) {
-                mExtendedMotionEvents.addFirst(mRecentMotionEvents);
-                mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
-            }
+            completePriorGesture();
+            mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
         }
         mRecentMotionEvents.addAll(motionEvents);
 
@@ -101,9 +99,25 @@
 
         mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent));
 
+        // We explicitly do not complete a gesture on UP or CANCEL events.
+        // We wait for the next gesture to start before marking the prior gesture as complete.  This
+        // has multiple benefits. First, it makes it trivial to track the "current" or "recent"
+        // gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly,
+        // it ensures that the current gesture doesn't get added to this HistoryTracker before it
+        // is analyzed.
+
         mDirty = true;
     }
 
+    private void completePriorGesture() {
+        if (!mRecentMotionEvents.isEmpty()) {
+            mGestuerCompleteListeners.forEach(listener -> listener.onGestureComplete(
+                    mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
+
+            mExtendedMotionEvents.addFirst(mRecentMotionEvents);
+        }
+    }
+
     /** Returns screen width in pixels. */
     public int getWidthPixels() {
         return mWidthPixels;
@@ -146,13 +160,6 @@
         }
     }
 
-    /**
-     * Returns true if new data has been supplied since the last time this class has been accessed.
-     */
-    public boolean isDirty() {
-        return mDirty;
-    }
-
     /** Return the interaction type that is being compared against for falsing. */
     public  final int getInteractionType() {
         return mInteractionType;
@@ -387,6 +394,6 @@
     /** Callback to be alerted when the current gesture ends. */
     public interface GestureCompleteListener {
         /** */
-        void onGestureComplete();
+        void onGestureComplete(long completionTimeMs);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
new file mode 100644
index 0000000..2719d3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2020 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.screenshot;
+
+import static android.os.FileUtils.closeQuietly;
+
+import android.annotation.IntRange;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.exifinterface.media.ExifInterface;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+class ImageExporter {
+    private static final String TAG = LogConfig.logTag(ImageExporter.class);
+
+    static final Duration PENDING_ENTRY_TTL = Duration.ofHours(24);
+
+    // ex: 'Screenshot_20201215-090626.png'
+    private static final String FILENAME_PATTERN = "Screenshot_%1$tY%<tm%<td-%<tH%<tM%<tS.%2$s";
+    private static final String SCREENSHOTS_PATH = Environment.DIRECTORY_PICTURES
+            + File.separator + Environment.DIRECTORY_SCREENSHOTS;
+
+    private static final String RESOLVER_INSERT_RETURNED_NULL =
+            "ContentResolver#insert returned null.";
+    private static final String RESOLVER_OPEN_FILE_RETURNED_NULL =
+            "ContentResolver#openFile returned null.";
+    private static final String RESOLVER_OPEN_FILE_EXCEPTION =
+            "ContentResolver#openFile threw an exception.";
+    private static final String OPEN_OUTPUT_STREAM_EXCEPTION =
+            "ContentResolver#openOutputStream threw an exception.";
+    private static final String EXIF_READ_EXCEPTION =
+            "ExifInterface threw an exception reading from the file descriptor.";
+    private static final String EXIF_WRITE_EXCEPTION =
+            "ExifInterface threw an exception writing to the file descriptor.";
+    private static final String RESOLVER_UPDATE_ZERO_ROWS =
+            "Failed to publishEntry. ContentResolver#update reported no rows updated.";
+    private static final String IMAGE_COMPRESS_RETURNED_FALSE =
+            "Bitmap.compress returned false. (Failure unknown)";
+
+    private final ContentResolver mResolver;
+    private CompressFormat mCompressFormat = CompressFormat.PNG;
+    private int mQuality = 100;
+
+    @Inject
+    ImageExporter(ContentResolver resolver) {
+        mResolver = resolver;
+    }
+
+    /**
+     * Adjusts the output image format. This also determines extension of the filename created. The
+     * default is {@link CompressFormat#PNG PNG}.
+     *
+     * @see CompressFormat
+     *
+     * @param format the image format for export
+     */
+    void setFormat(CompressFormat format) {
+        mCompressFormat = format;
+    }
+
+    /**
+     * Sets the quality format. The exact meaning is dependent on the {@link CompressFormat} used.
+     *
+     * @param quality the 'quality' level between 0 and 100
+     */
+    void setQuality(@IntRange(from = 0, to = 100) int quality) {
+        mQuality = quality;
+    }
+
+    /**
+     * Export the image using the given executor.
+     *
+     * @param executor the thread for execution
+     * @param bitmap the bitmap to export
+     *
+     * @return a listenable future result
+     */
+    ListenableFuture<Uri> export(Executor executor, Bitmap bitmap) {
+        return export(executor, bitmap, ZonedDateTime.now());
+    }
+
+    /**
+     * Export the image using the given executor.
+     *
+     * @param executor the thread for execution
+     * @param bitmap the bitmap to export
+     *
+     * @return a listenable future result
+     */
+    ListenableFuture<Uri> export(Executor executor, Bitmap bitmap, ZonedDateTime captureTime) {
+        final Task task = new Task(mResolver, bitmap, captureTime, mCompressFormat, mQuality);
+        return CallbackToFutureAdapter.getFuture(
+                (completer) -> {
+                    executor.execute(() -> {
+                        try {
+                            completer.set(task.execute());
+                        } catch (ImageExportException | InterruptedException e) {
+                            completer.setException(e);
+                        }
+                    });
+                    return task;
+                }
+        );
+    }
+
+    private static class Task {
+        private final ContentResolver mResolver;
+        private final ZonedDateTime mCaptureTime;
+        private final CompressFormat mFormat;
+        private final int mQuality;
+        private final Bitmap mBitmap;
+
+        Task(ContentResolver resolver, Bitmap bitmap, ZonedDateTime captureTime,
+                CompressFormat format, int quality) {
+            mResolver = resolver;
+            mBitmap = bitmap;
+            mCaptureTime = captureTime;
+            mFormat = format;
+            mQuality = quality;
+        }
+
+        public Uri execute() throws ImageExportException, InterruptedException {
+            Trace.beginSection("ImageExporter_execute");
+            Uri uri = null;
+            Instant start = null;
+            try {
+                if (LogConfig.DEBUG_STORAGE) {
+                    Log.d(TAG, "image export started");
+                    start = Instant.now();
+                }
+                uri = createEntry(mFormat, mCaptureTime);
+                throwIfInterrupted();
+
+                writeImage(mBitmap, mFormat, mQuality, uri);
+                throwIfInterrupted();
+
+                writeExif(uri, mBitmap.getWidth(), mBitmap.getHeight(), mCaptureTime);
+                throwIfInterrupted();
+
+                publishEntry(uri);
+
+                if (LogConfig.DEBUG_STORAGE) {
+                    Log.d(TAG, "image export completed: "
+                            + Duration.between(start, Instant.now()).toMillis() + " ms");
+                }
+            } catch (ImageExportException e) {
+                if (uri != null) {
+                    mResolver.delete(uri, null);
+                }
+                throw e;
+            } finally {
+                Trace.endSection();
+            }
+            return uri;
+        }
+
+        Uri createEntry(CompressFormat format, ZonedDateTime time) throws ImageExportException {
+            Trace.beginSection("ImageExporter_createEntry");
+            try {
+                final ContentValues values = createMetadata(time, format);
+                Uri uri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+                if (uri == null) {
+                    throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
+                }
+                return uri;
+            } finally {
+                Trace.endSection();
+            }
+        }
+
+        void writeImage(Bitmap bitmap, CompressFormat format, int quality,
+                Uri contentUri) throws ImageExportException {
+            Trace.beginSection("ImageExporter_writeImage");
+            try (OutputStream out = mResolver.openOutputStream(contentUri)) {
+                long start = SystemClock.elapsedRealtime();
+                if (!bitmap.compress(format, quality, out)) {
+                    throw new ImageExportException(IMAGE_COMPRESS_RETURNED_FALSE);
+                } else if (LogConfig.DEBUG_STORAGE) {
+                    Log.d(TAG, "Bitmap.compress took "
+                            + (SystemClock.elapsedRealtime() - start) + " ms");
+                }
+            } catch (IOException ex) {
+                throw new ImageExportException(OPEN_OUTPUT_STREAM_EXCEPTION, ex);
+            } finally {
+                Trace.endSection();
+            }
+        }
+
+        void writeExif(Uri uri, int width, int height, ZonedDateTime captureTime)
+                throws ImageExportException {
+            Trace.beginSection("ImageExporter_writeExif");
+            ParcelFileDescriptor pfd = null;
+            try {
+                pfd = mResolver.openFile(uri, "rw", null);
+                if (pfd == null) {
+                    throw new ImageExportException(RESOLVER_OPEN_FILE_RETURNED_NULL);
+                }
+                ExifInterface exif;
+                try {
+                    exif = new ExifInterface(pfd.getFileDescriptor());
+                } catch (IOException e) {
+                    throw new ImageExportException(EXIF_READ_EXCEPTION, e);
+                }
+
+                updateExifAttributes(exif, width, height, captureTime);
+                try {
+                    exif.saveAttributes();
+                } catch (IOException e) {
+                    throw new ImageExportException(EXIF_WRITE_EXCEPTION, e);
+                }
+            } catch (FileNotFoundException e) {
+                throw new ImageExportException(RESOLVER_OPEN_FILE_EXCEPTION, e);
+            } finally {
+                closeQuietly(pfd);
+                Trace.endSection();
+            }
+        }
+
+        void publishEntry(Uri uri) throws ImageExportException {
+            Trace.beginSection("ImageExporter_publishEntry");
+            try {
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.MediaColumns.IS_PENDING, 0);
+                values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
+                final int rowsUpdated = mResolver.update(uri, values, /* extras */ null);
+                if (rowsUpdated < 1) {
+                    throw new ImageExportException(RESOLVER_UPDATE_ZERO_ROWS);
+                }
+            } finally {
+                Trace.endSection();
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "compress [" + mBitmap + "] to [" + mFormat + "] at quality " + mQuality;
+        }
+    }
+
+    static String createFilename(ZonedDateTime time, CompressFormat format) {
+        return String.format(FILENAME_PATTERN, time, fileExtension(format));
+    }
+
+    static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format) {
+        ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.RELATIVE_PATH, SCREENSHOTS_PATH);
+        values.put(MediaStore.MediaColumns.DISPLAY_NAME, createFilename(captureTime, format));
+        values.put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(format));
+        values.put(MediaStore.MediaColumns.DATE_ADDED, captureTime.toEpochSecond());
+        values.put(MediaStore.MediaColumns.DATE_MODIFIED, captureTime.toEpochSecond());
+        values.put(MediaStore.MediaColumns.DATE_EXPIRES,
+                captureTime.plus(PENDING_ENTRY_TTL).toEpochSecond());
+        values.put(MediaStore.MediaColumns.IS_PENDING, 1);
+        return values;
+    }
+
+    static void updateExifAttributes(ExifInterface exif, int width, int height,
+            ZonedDateTime captureTime) {
+        exif.setAttribute(ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY);
+        exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, Integer.toString(width));
+        exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, Integer.toString(height));
+
+        String dateTime = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(captureTime);
+        String subSec = DateTimeFormatter.ofPattern("SSS").format(captureTime);
+        String timeZone = DateTimeFormatter.ofPattern("xxx").format(captureTime);
+
+        exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTime);
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, subSec);
+        exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, timeZone);
+
+        exif.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, dateTime);
+        exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, subSec);
+        exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED, timeZone);
+    }
+
+    static String getMimeType(CompressFormat format) {
+        switch (format) {
+            case JPEG:
+                return "image/jpeg";
+            case PNG:
+                return "image/png";
+            case WEBP:
+            case WEBP_LOSSLESS:
+            case WEBP_LOSSY:
+                return "image/webp";
+            default:
+                throw new IllegalArgumentException("Unknown CompressFormat!");
+        }
+    }
+
+    static String fileExtension(CompressFormat format) {
+        switch (format) {
+            case JPEG:
+                return "jpg";
+            case PNG:
+                return "png";
+            case WEBP:
+            case WEBP_LOSSY:
+            case WEBP_LOSSLESS:
+                return "webp";
+            default:
+                throw new IllegalArgumentException("Unknown CompressFormat!");
+        }
+    }
+
+    private static void throwIfInterrupted() throws InterruptedException {
+        if (Thread.currentThread().isInterrupted()) {
+            throw new InterruptedException();
+        }
+    }
+
+    static final class ImageExportException extends IOException {
+        ImageExportException(String message) {
+            super(message);
+        }
+
+        ImageExportException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+}
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 a991d3615..f9c7799 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -34,7 +34,6 @@
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
-import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_INVALID;
 import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -176,7 +175,6 @@
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CommandQueue;
@@ -1582,11 +1580,6 @@
             return true;
         }
 
-        final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
-        if (navbarPos == NAV_BAR_POS_INVALID) {
-            return false;
-        }
-
         if (legacySplitScreen.splitPrimaryTask()) {
             if (metricsDockAction != -1) {
                 mMetricsLogger.action(metricsDockAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 08e70a9..6ae5e90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -105,6 +105,9 @@
 
         default void onExtremeBatterySaverChanged(boolean isExtreme) {
         }
+
+        default void onWirelessChargingChanged(boolean isWirlessCharging) {
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 8c67072..da9aa97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -77,7 +77,7 @@
     private boolean mCharged;
     protected boolean mPowerSave;
     private boolean mAodPowerSave;
-    protected boolean mWirelessCharging;
+    private boolean mWirelessCharging;
     private boolean mTestMode = false;
     @VisibleForTesting
     boolean mHasReceivedBattery = false;
@@ -155,6 +155,7 @@
         cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
         cb.onPowerSaveChanged(mPowerSave);
         cb.onBatteryUnknownStateChanged(mStateUnknown);
+        cb.onWirelessChargingChanged(mWirelessCharging);
     }
 
     @Override
@@ -179,8 +180,12 @@
                     BatteryManager.BATTERY_STATUS_UNKNOWN);
             mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
             mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
-            mWirelessCharging = mCharging && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
-                    == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+            if (mWirelessCharging != (mCharging
+                    && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
+                    == BatteryManager.BATTERY_PLUGGED_WIRELESS)) {
+                mWirelessCharging = !mWirelessCharging;
+                fireWirelessChargingChanged();
+            }
 
             boolean present = intent.getBooleanExtra(EXTRA_PRESENT, true);
             boolean unknown = !present;
@@ -227,6 +232,13 @@
         }
     }
 
+    private void fireWirelessChargingChanged() {
+        synchronized (mChangeCallbacks) {
+            mChangeCallbacks.forEach(batteryStateChangeCallback ->
+                    batteryStateChangeCallback.onWirelessChargingChanged(mWirelessCharging));
+        }
+    }
+
     @Override
     public boolean isPluggedIn() {
         return mPluggedIn;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
index 477424c..c9bc7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
@@ -91,6 +91,7 @@
 
     private void openInternalNotificationPanel(String action) {
         Intent intent = new Intent(mContext, TvNotificationPanelActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
         intent.setAction(action);
         mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
     }
@@ -113,6 +114,7 @@
         if (ri != null && ri.activityInfo != null) {
             if (ri.activityInfo.permission != null && ri.activityInfo.permission.equals(
                     Manifest.permission.STATUS_BAR_SERVICE)) {
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
             } else {
                 Log.e(TAG,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4944284..31cc7bb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -31,6 +31,8 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.classifier.FalsingCollectorFake;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,6 +67,7 @@
     private LatencyTracker mLatencyTracker;
     @Mock
     private LiftToActivateListener mLiftToactivateListener;
+    private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
     @Mock
     private View mDeleteButton;
     @Mock
@@ -88,7 +91,8 @@
                 .thenReturn(mOkButton);
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) {
+                mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
+                mFalsingCollector) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 8c547b1..5709ce30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -16,12 +16,15 @@
 
 package com.android.systemui.classifier;
 
-import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.any;
-
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
 import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -35,6 +38,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
 import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -45,7 +49,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -53,6 +56,8 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class BrightLineClassifierTest extends SysuiTestCase {
+    private static final long DOUBLE_TAP_TIMEOUT_MS = 1000;
+
     private BrightLineFalsingManager mBrightLineFalsingManager;
     @Mock
     private FalsingDataProvider mFalsingDataProvider;
@@ -69,24 +74,35 @@
     private FalsingClassifier mClassifierB;
     private final List<MotionEvent> mMotionEventList = new ArrayList<>();
     @Mock
-    private HistoryTracker mHistoryTracker;
-    private FakeSystemClock mSystemClock = new FakeSystemClock();
+    private HistoryTracker mHistoryTracker;;
+    private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
 
     private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, "");
     private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
+    private GestureCompleteListener mGestureCompleteListener;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
         when(mClassifierB.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
+        when(mSingleTapClassfier.isTap(any(List.class))).thenReturn(mPassedResult);
+        when(mDoubleTapClassifier.classifyGesture()).thenReturn(mPassedResult);
         mClassifiers.add(mClassifierA);
         mClassifiers.add(mClassifierB);
-        when(mFalsingDataProvider.isDirty()).thenReturn(true);
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mDockManager,
                 mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
-                mHistoryTracker, mSystemClock, false);
+                mHistoryTracker, mFakeExecutor, DOUBLE_TAP_TIMEOUT_MS, false);
+
+
+        ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
+                ArgumentCaptor.forClass(GestureCompleteListener.class);
+
+        verify(mFalsingDataProvider).addGestureCompleteListener(
+                gestureCompleteListenerCaptor.capture());
+
+        mGestureCompleteListener = gestureCompleteListenerCaptor.getValue();
     }
 
     @Test
@@ -179,15 +195,57 @@
 
     @Test
     public void testHistory() {
-        ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
-                ArgumentCaptor.forClass(GestureCompleteListener.class);
+        mGestureCompleteListener.onGestureComplete(1000);
 
-        verify(mFalsingDataProvider).addGestureCompleteListener(
-                gestureCompleteListenerCaptor.capture());
+        verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
+    }
 
-        GestureCompleteListener gestureCompleteListener = gestureCompleteListenerCaptor.getValue();
-        gestureCompleteListener.onGestureComplete();
+    @Test
+    public void testHistory_singleTap() {
+        // When trying to classify single taps, we don't immediately add results to history.
+        mBrightLineFalsingManager.isFalseTap(false);
+        mGestureCompleteListener.onGestureComplete(1000);
 
-        verify(mHistoryTracker).addResults(any(Collection.class), eq(mSystemClock.uptimeMillis()));
+        verify(mHistoryTracker, never()).addResults(any(), anyLong());
+
+        mFakeExecutor.advanceClockToNext();
+        mFakeExecutor.runAllReady();
+
+        verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
+    }
+
+    @Test
+    public void testHistory_multipleSingleTaps() {
+        // When trying to classify single taps, we don't immediately add results to history.
+        mBrightLineFalsingManager.isFalseTap(false);
+        mGestureCompleteListener.onGestureComplete(1000);
+        mBrightLineFalsingManager.isFalseTap(false);
+        mGestureCompleteListener.onGestureComplete(2000);
+
+        verify(mHistoryTracker, never()).addResults(any(), anyLong());
+
+        mFakeExecutor.advanceClockToNext();
+        mFakeExecutor.runNextReady();
+        verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
+        reset(mHistoryTracker);
+        mFakeExecutor.advanceClockToNext();
+        mFakeExecutor.runNextReady();
+        verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
+    }
+
+    @Test
+    public void testHistory_doubleTap() {
+        // When trying to classify single taps, we don't immediately add results to history.
+        mBrightLineFalsingManager.isFalseTap(false);
+        mGestureCompleteListener.onGestureComplete(1000);
+        // Before checking for double tap, we may check for single-tap on the second gesture.
+        mBrightLineFalsingManager.isFalseTap(false);
+        mBrightLineFalsingManager.isFalseDoubleTap();
+        mGestureCompleteListener.onGestureComplete(2000);
+
+        // Double tap is immediately added to history. Single tap is never added.
+        verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
+
+        assertThat(mFakeExecutor.numPending()).isEqualTo(0);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index af5e789..23ef865 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -17,12 +17,15 @@
 package com.android.systemui.classifier;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
 
 import androidx.test.filters.SmallTest;
 
@@ -38,6 +41,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -66,7 +70,6 @@
                 mKeyguardUpdateMonitor, mProximitySensor, mStatusBarStateController);
     }
 
-
     @Test
     public void testRegisterSensor() {
         mFalsingCollector.onScreenTurningOn();
@@ -110,7 +113,6 @@
 
     @Test
     public void testUnregisterSensor_StateTransition() {
-
         ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
                 ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
         verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
@@ -120,4 +122,37 @@
         stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
         verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class));
     }
+
+    @Test
+    public void testPassThroughGesture() {
+        MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+
+        // Nothing passed initially
+        mFalsingCollector.onTouchEvent(down);
+        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+        // Up event flushes the down event.
+        mFalsingCollector.onTouchEvent(up);
+        InOrder orderedCalls = inOrder(mFalsingDataProvider);
+        // We can't simply use "eq" or similar because the collector makes a copy of "down".
+        orderedCalls.verify(mFalsingDataProvider).onMotionEvent(
+                argThat(argument -> argument.getActionMasked() == MotionEvent.ACTION_DOWN));
+        orderedCalls.verify(mFalsingDataProvider).onMotionEvent(up);
+    }
+
+    @Test
+    public void testAvoidGesture() {
+        MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+
+        // Nothing passed initially
+        mFalsingCollector.onTouchEvent(down);
+        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+        mFalsingCollector.avoidGesture();
+        // Up event would flush, but we were told to avoid.
+        mFalsingCollector.onTouchEvent(up);
+        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
new file mode 100644
index 0000000..f566880
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 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.screenshot;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.testing.AndroidTestingRunner;
+
+import androidx.exifinterface.media.ExifInterface;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.systemui.SysuiTestCase;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidTestingRunner.class)
+@MediumTest // file I/O
+public class ImageExporterTest extends SysuiTestCase {
+
+    /** Executes directly in the caller's thread */
+    private static final Executor DIRECT_EXECUTOR = Runnable::run;
+    private static final byte[] EXIF_FILE_TAG = "Exif\u0000\u0000".getBytes(US_ASCII);
+
+    private static final ZonedDateTime CAPTURE_TIME =
+            ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST"));
+
+    @Test
+    public void testImageFilename() {
+        assertEquals("image file name", "Screenshot_20201215-131500.png",
+                ImageExporter.createFilename(CAPTURE_TIME, CompressFormat.PNG));
+    }
+
+    @Test
+    public void testUpdateExifAttributes_timeZoneUTC() throws IOException {
+        ExifInterface exifInterface = new ExifInterface(new ByteArrayInputStream(EXIF_FILE_TAG),
+                ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY);
+
+        ImageExporter.updateExifAttributes(exifInterface, 100, 100,
+                ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 18, 15), ZoneId.of("UTC")));
+
+        assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00",
+                exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL));
+        assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00",
+                exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED));
+    }
+
+    @Test
+    public void testImageExport() throws ExecutionException, InterruptedException, IOException {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        ContentResolver contentResolver = context.getContentResolver();
+        ImageExporter exporter = new ImageExporter(contentResolver);
+
+        Bitmap original = createCheckerBitmap(10, 10, 10);
+
+        ListenableFuture<Uri> direct = exporter.export(DIRECT_EXECUTOR, original, CAPTURE_TIME);
+        assertTrue("future should be done", direct.isDone());
+        assertFalse("future should not be canceled", direct.isCancelled());
+        Uri result = direct.get();
+
+        assertNotNull("Uri should not be null", result);
+        Bitmap decoded = null;
+        try (InputStream in = contentResolver.openInputStream(result)) {
+            decoded = BitmapFactory.decodeStream(in);
+            assertNotNull("decoded image should not be null", decoded);
+            assertTrue("original and decoded image should be identical", original.sameAs(decoded));
+
+            try (ParcelFileDescriptor pfd = contentResolver.openFile(result, "r", null)) {
+                assertNotNull(pfd);
+                ExifInterface exifInterface = new ExifInterface(pfd.getFileDescriptor());
+
+                assertEquals("Exif " + ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY,
+                        exifInterface.getAttribute(ExifInterface.TAG_SOFTWARE));
+
+                assertEquals("Exif " + ExifInterface.TAG_IMAGE_WIDTH, 100,
+                        exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0));
+                assertEquals("Exif " + ExifInterface.TAG_IMAGE_LENGTH, 100,
+                        exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0));
+
+                assertEquals("Exif " + ExifInterface.TAG_DATETIME_ORIGINAL, "2020:12:15 13:15:00",
+                        exifInterface.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
+                assertEquals("Exif " + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, "000",
+                        exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL));
+                assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "-05:00",
+                        exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL));
+
+                assertEquals("Exif " + ExifInterface.TAG_DATETIME_DIGITIZED, "2020:12:15 13:15:00",
+                        exifInterface.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED));
+                assertEquals("Exif " + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, "000",
+                        exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED));
+                assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, "-05:00",
+                        exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED));
+            }
+        } finally {
+            if (decoded != null) {
+                decoded.recycle();
+            }
+            contentResolver.delete(result, null);
+        }
+    }
+
+    @Test
+    public void testMediaStoreMetadata() {
+        ContentValues values = ImageExporter.createMetadata(CAPTURE_TIME, CompressFormat.PNG);
+        assertEquals("Pictures/Screenshots",
+                values.getAsString(MediaStore.MediaColumns.RELATIVE_PATH));
+        assertEquals("Screenshot_20201215-131500.png",
+                values.getAsString(MediaStore.MediaColumns.DISPLAY_NAME));
+        assertEquals("image/png", values.getAsString(MediaStore.MediaColumns.MIME_TYPE));
+        assertEquals(Long.valueOf(1608056100L),
+                values.getAsLong(MediaStore.MediaColumns.DATE_ADDED));
+        assertEquals(Long.valueOf(1608056100L),
+                values.getAsLong(MediaStore.MediaColumns.DATE_MODIFIED));
+        assertEquals(Integer.valueOf(1), values.getAsInteger(MediaStore.MediaColumns.IS_PENDING));
+        assertEquals(Long.valueOf(1608056100L + 86400L), // +1 day
+                values.getAsLong(MediaStore.MediaColumns.DATE_EXPIRES));
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    private Bitmap createCheckerBitmap(int tileSize, int w, int h) {
+        Bitmap bitmap = Bitmap.createBitmap(w * tileSize, h * tileSize, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setStyle(Paint.Style.FILL);
+
+        for (int i = 0; i < h; i++) {
+            int top = i * tileSize;
+            for (int j = 0; j < w; j++) {
+                int left = j * tileSize;
+                paint.setColor(paint.getColor() == Color.WHITE ? Color.BLACK : Color.WHITE);
+                c.drawRect(left, top, left + tileSize, top + tileSize, paint);
+            }
+        }
+        return bitmap;
+    }
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4e75a9e..1378776 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3597,8 +3597,8 @@
     private boolean isNetworkPotentialSatisfier(
             @NonNull final NetworkAgentInfo candidate, @NonNull final NetworkRequestInfo nri) {
         // listen requests won't keep up a network satisfying it. If this is not a multilayer
-        // request, we can return immediately. For multilayer requests, we have to check to see if
-        // any of the multilayer requests may have a potential satisfier.
+        // request, return immediately. For multilayer requests, check to see if any of the
+        // multilayer requests may have a potential satisfier.
         if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) {
             return false;
         }
@@ -8234,6 +8234,13 @@
         final IBinder iCb = cb.asBinder();
         final NetworkRequestInfo nri = cbInfo.mRequestInfo;
 
+        // Connectivity Diagnostics are meant to be used with a single network request. It would be
+        // confusing for these networks to change when an NRI is satisfied in another layer.
+        if (nri.isMultilayerRequest()) {
+            throw new IllegalArgumentException("Connectivity Diagnostics do not support multilayer "
+                + "network requests.");
+        }
+
         // This means that the client registered the same callback multiple times. Do
         // not override the previous entry, and exit silently.
         if (mConnectivityDiagnosticsCallbacks.containsKey(iCb)) {
@@ -8260,7 +8267,8 @@
         synchronized (mNetworkForNetId) {
             for (int i = 0; i < mNetworkForNetId.size(); i++) {
                 final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
-                if (nai.satisfies(nri.request)) {
+                // Connectivity Diagnostics rejects multilayer requests at registration hence get(0)
+                if (nai.satisfies(nri.mRequests.get(0))) {
                     matchingNetworks.add(nai);
                 }
             }
@@ -8388,7 +8396,8 @@
                 mConnectivityDiagnosticsCallbacks.entrySet()) {
             final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue();
             final NetworkRequestInfo nri = cbInfo.mRequestInfo;
-            if (nai.satisfies(nri.request)) {
+            // Connectivity Diagnostics rejects multilayer requests at registration hence get(0).
+            if (nai.satisfies(nri.mRequests.get(0))) {
                 if (checkConnectivityDiagnosticsPermissions(
                         nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
                     results.add(entry.getValue().mCb);
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 500e768..f2b63a6 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -236,4 +236,9 @@
             throw new RuntimeException(e.toString());
         }
     }
+
+    @Override
+    public long suggestScratchSize() throws RemoteException {
+        return getGsiService().suggestScratchSize();
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1841d67..8cb9b0a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14052,18 +14052,25 @@
                 callerApp = mPidsSelfLocked.get(pid);
             }
         }
-        // Check if the instrumentation of the process has the permission. This covers the usual
-        // test started from the shell (which has the permission) case. This is needed for apps
-        // targeting SDK level < S but we are also allowing for targetSdk S+ as a convenience to
-        // avoid breaking a bunch of existing tests and asking them to adopt shell permissions to do
-        // this.
+
         if (callerApp != null) {
+            // Check if the instrumentation of the process has the permission. This covers the usual
+            // test started from the shell (which has the permission) case. This is needed for apps
+            // targeting SDK level < S but we are also allowing for targetSdk S+ as a convenience to
+            // avoid breaking a bunch of existing tests and asking them to adopt shell permissions
+            // to do this.
             ActiveInstrumentation instrumentation = callerApp.getActiveInstrumentation();
             if (instrumentation != null && checkPermission(
                     permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, -1, instrumentation.mSourceUid)
                     == PERMISSION_GRANTED) {
                 return true;
             }
+            // This is the notification trampoline use-case for example, where apps use Intent.ACSD
+            // to close the shade prior to starting an activity.
+            WindowProcessController wmApp = callerApp.getWindowProcessController();
+            if (wmApp.canCloseSystemDialogsByToken()) {
+                return true;
+            }
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index e129561..d9c83da 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -749,9 +749,15 @@
                         // There are other callbacks in the queue, let's just update the originating
                         // token
                         if (mIsAllowedBgActivityStartsByStart) {
-                            mAppForAllowingBgActivityStartsByStart
-                                    .addOrUpdateAllowBackgroundActivityStartsToken(
-                                            this, getExclusiveOriginatingToken());
+                            // mAppForAllowingBgActivityStartsByStart can be null here for example
+                            // if get 2 calls to allowBgActivityStartsOnServiceStart() without a
+                            // process attached to this ServiceRecord, so we need to perform a null
+                            // check here.
+                            if (mAppForAllowingBgActivityStartsByStart != null) {
+                                mAppForAllowingBgActivityStartsByStart
+                                        .addOrUpdateAllowBackgroundActivityStartsToken(
+                                                this, getExclusiveOriginatingToken());
+                            }
                         } else {
                             Slog.wtf(TAG,
                                     "Service callback to revoke bg activity starts by service "
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
index b9c23b7..0881cd2 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -162,11 +162,6 @@
     }
 
     @Override
-    void logWarn(String msg) {
-        Slog.w(TAG, msg);
-    }
-
-    @Override
     public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
         synchronized (mSharedLock) {
             ipw.println("{BinderLocationTimeZoneProvider}");
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index ab64f97..a23b9d7 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -285,8 +285,12 @@
     }
 
     static void warnLog(String msg) {
+        warnLog(msg, null);
+    }
+
+    static void warnLog(String msg, @Nullable Throwable t) {
         if (Log.isLoggable(TAG, Log.WARN)) {
-            Slog.w(TAG, msg);
+            Slog.w(TAG, msg, t);
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index 8b51ab4..e55d1cc 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -17,6 +17,7 @@
 package com.android.server.location.timezone;
 
 import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
+import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
 import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
@@ -345,12 +346,21 @@
             }
             mProviderListener = Objects.requireNonNull(providerListener);
             ProviderState currentState = ProviderState.createStartingState(this);
-            ProviderState newState = currentState.newState(
+            currentState = currentState.newState(
                     PROVIDER_STATE_STOPPED, null, null,
                     "initialize() called");
-            setCurrentState(newState, false);
+            setCurrentState(currentState, false);
 
-            onInitialize();
+            // Guard against uncaught exceptions due to initialization problems.
+            try {
+                onInitialize();
+            } catch (RuntimeException e) {
+                warnLog("Unable to initialize the provider", e);
+                currentState = currentState
+                        .newState(PROVIDER_STATE_PERM_FAILED, null, null,
+                                "Provider failed to initialize");
+                setCurrentState(currentState, true);
+            }
         }
     }
 
@@ -498,7 +508,7 @@
                 case PROVIDER_STATE_PERM_FAILED: {
                     // After entering perm failed, there is nothing to do. The remote peer is
                     // supposed to stop sending events after it has reported perm failure.
-                    logWarn("handleTimeZoneProviderEvent: Event=" + timeZoneProviderEvent
+                    warnLog("handleTimeZoneProviderEvent: Event=" + timeZoneProviderEvent
                             + " received for provider=" + this + " when in failed state");
                     return;
                 }
@@ -509,7 +519,7 @@
                                     + " Failure event=" + timeZoneProviderEvent
                                     + " received for stopped provider=" + this
                                     + ", entering permanently failed state";
-                            logWarn(msg);
+                            warnLog(msg);
                             ProviderState newState = currentState.newState(
                                     PROVIDER_STATE_PERM_FAILED, null, null, msg);
                             setCurrentState(newState, true);
@@ -522,7 +532,7 @@
                         case EVENT_TYPE_UNCERTAIN: {
                             // Any geolocation-related events received for a stopped provider are
                             // ignored: they should not happen.
-                            logWarn("handleTimeZoneProviderEvent:"
+                            warnLog("handleTimeZoneProviderEvent:"
                                     + " event=" + timeZoneProviderEvent
                                     + " received for stopped provider=" + this
                                     + ", ignoring");
@@ -544,7 +554,7 @@
                                     + " Failure event=" + timeZoneProviderEvent
                                     + " received for provider=" + this
                                     + ", entering permanently failed state";
-                            logWarn(msg);
+                            warnLog(msg);
                             ProviderState newState = currentState.newState(
                                     PROVIDER_STATE_PERM_FAILED, null, null, msg);
                             setCurrentState(newState, true);
@@ -584,11 +594,6 @@
         }
     }
 
-    /**
-     * Implemented by subclasses.
-     */
-    abstract void logWarn(String msg);
-
     @GuardedBy("mSharedLock")
     private void assertIsStarted() {
         ProviderState currentState = mCurrentState.get();
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
index 91b52f1..16f9e97 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
@@ -79,8 +79,8 @@
                 throw new IllegalStateException("listener already set");
             }
             this.mListener = listener;
+            onInitialize();
         }
-        onInitialize();
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
index e11a7ce..4b321e6 100644
--- a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.IndentingPrintWriter;
-import android.util.Slog;
 
 import java.time.Duration;
 
@@ -74,11 +73,6 @@
     }
 
     @Override
-    void logWarn(String msg) {
-        Slog.w(TAG, msg);
-    }
-
-    @Override
     public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
         synchronized (mSharedLock) {
             ipw.println("{Stubbed LocationTimeZoneProvider}");
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a146c8c..c106558 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
@@ -291,7 +292,6 @@
 
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
@@ -308,7 +308,7 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -10416,12 +10416,15 @@
         private final Set<String> mPackagesShown = new ArraySet<>();
 
         @Override
-        public IBinder getToken() {
-            return ALLOWLIST_TOKEN;
-        }
-
-        @Override
-        public boolean isActivityStartAllowed(int uid, String packageName) {
+        public boolean isActivityStartAllowed(Collection<IBinder> tokens, int uid,
+                String packageName) {
+            checkArgument(!tokens.isEmpty());
+            for (IBinder token : tokens) {
+                if (token != ALLOWLIST_TOKEN) {
+                    // We only block or warn if the start is exclusively due to notification
+                    return true;
+                }
+            }
             String toastMessage = "Indirect activity start from " + packageName;
             String logcatMessage =
                     "Indirect notification activity start (trampoline) from " + packageName;
@@ -10439,6 +10442,15 @@
             }
         }
 
+        @Override
+        public boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid) {
+            // If the start is allowed via notification, we allow the app to close system dialogs
+            // only if their targetSdk < S, otherwise they have no valid reason to do this since
+            // trampolines are blocked.
+            return tokens.contains(ALLOWLIST_TOKEN)
+                    && !CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
+        }
+
         private void toast(String message) {
             mUiHandler.post(() ->
                     Toast.makeText(getUiContext(), message + "\nSee go/s-trampolines.",
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 5cd22e0..de77372 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -88,7 +88,6 @@
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -106,6 +105,9 @@
     // The amount of time rules instances can exist without their owning app being installed.
     private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
 
+    // pkg|userId => uid
+    protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
+
     private final Context mContext;
     private final H mHandler;
     private final SettingsObserver mSettingsObserver;
@@ -145,7 +147,7 @@
         mHandler = new H(looper);
         addCallback(mMetrics);
         mAppOps = context.getSystemService(AppOpsManager.class);
-        mNotificationManager =  context.getSystemService(NotificationManager.class);
+        mNotificationManager = context.getSystemService(NotificationManager.class);
 
         mDefaultConfig = readDefaultConfig(mContext.getResources());
         updateDefaultAutomaticRuleNames();
@@ -384,17 +386,25 @@
         synchronized (mConfig) {
             if (mConfig == null) return false;
             newConfig = mConfig.copy();
-            ZenRule rule = newConfig.automaticRules.get(id);
-            if (rule == null) return false;
-            if (canManageAutomaticZenRule(rule)) {
+            ZenRule ruleToRemove = newConfig.automaticRules.get(id);
+            if (ruleToRemove == null) return false;
+            if (canManageAutomaticZenRule(ruleToRemove)) {
                 newConfig.automaticRules.remove(id);
+                if (ruleToRemove.pkg != null && !"android".equals(ruleToRemove.pkg)) {
+                    for (ZenRule currRule : newConfig.automaticRules.values()) {
+                        if (currRule.pkg != null && currRule.pkg.equals(ruleToRemove.pkg)) {
+                            break; // no need to remove from cache
+                        }
+                    }
+                    mRulesUidCache.remove(getPackageUserKey(ruleToRemove.pkg, newConfig.user));
+                }
                 if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + id + " reason=" + reason);
             } else {
                 throw new SecurityException(
                         "Cannot delete rules not owned by your condition provider");
             }
             dispatchOnAutomaticRuleStatusChanged(
-                    mConfig.user, rule.pkg, id, AUTOMATIC_RULE_STATUS_REMOVED);
+                    mConfig.user, ruleToRemove.pkg, id, AUTOMATIC_RULE_STATUS_REMOVED);
             return setConfigLocked(newConfig, reason, null, true);
         }
     }
@@ -1192,7 +1202,6 @@
     public void pullRules(List<StatsEvent> events) {
         synchronized (mConfig) {
             final int numConfigs = mConfigs.size();
-            int id = 0;
             for (int i = 0; i < numConfigs; i++) {
                 final int user = mConfigs.keyAt(i);
                 final ZenModeConfig config = mConfigs.valueAt(i);
@@ -1208,16 +1217,16 @@
                         .writeByteArray(config.toZenPolicy().toProto());
                 events.add(data.build());
                 if (config.manualRule != null && config.manualRule.enabler != null) {
-                    ruleToProto(user, config.manualRule, events);
+                    ruleToProtoLocked(user, config.manualRule, events);
                 }
                 for (ZenRule rule : config.automaticRules.values()) {
-                    ruleToProto(user, rule, events);
+                    ruleToProtoLocked(user, rule, events);
                 }
             }
         }
     }
 
-    private void ruleToProto(int user, ZenRule rule, List<StatsEvent> events) {
+    private void ruleToProtoLocked(int user, ZenRule rule, List<StatsEvent> events) {
         // Make the ID safe.
         String id = rule.id == null ? "" : rule.id;
         if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) {
@@ -1231,9 +1240,6 @@
             id = ZenModeConfig.MANUAL_RULE_ID;
         }
 
-        // TODO: fetch the uid from the package manager
-        int uid = "android".equals(pkg) ? Process.SYSTEM_UID : 0;
-
         SysUiStatsEvent.Builder data;
         data = mStatsEventBuilderFactory.newBuilder()
                 .setAtomId(DND_MODE_RULE)
@@ -1242,7 +1248,7 @@
                 .writeBoolean(false) // channels_bypassing unused for rules
                 .writeInt(rule.zenMode)
                 .writeString(id)
-                .writeInt(uid)
+                .writeInt(getPackageUid(pkg, user))
                 .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
         byte[] policyProto = new byte[]{};
         if (rule.zenPolicy != null) {
@@ -1252,6 +1258,24 @@
         events.add(data.build());
     }
 
+    private int getPackageUid(String pkg, int user) {
+        if ("android".equals(pkg)) {
+            return Process.SYSTEM_UID;
+        }
+        final String key = getPackageUserKey(pkg, user);
+        if (mRulesUidCache.get(key) == null) {
+            try {
+                mRulesUidCache.put(key, mPm.getPackageUidAsUser(pkg, user));
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+        return mRulesUidCache.getOrDefault(key, -1);
+    }
+
+    private static String getPackageUserKey(String pkg, int user) {
+        return pkg + "|" + user;
+    }
+
     @VisibleForTesting
     protected final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1653896..c9d8eef 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1018,9 +1018,8 @@
             if (info.minAspectRatio != 0) {
                 pw.println(prefix + "minAspectRatio=" + info.minAspectRatio);
             }
-            if (info.supportsSizeChanges) {
-                pw.println(prefix + "supportsSizeChanges=true");
-            }
+            pw.println(prefix + "supportsSizeChanges="
+                    + ActivityInfo.sizeChangesSupportModeToString(info.supportsSizeChanges()));
             if (info.configChanges != 0) {
                 pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
             }
@@ -6555,7 +6554,7 @@
      *         aspect ratio.
      */
     boolean shouldUseSizeCompatMode() {
-        if (info.supportsSizeChanges) {
+        if (info.supportsSizeChanges() != ActivityInfo.SIZE_CHANGES_UNSUPPORTED) {
             return false;
         }
         if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ea04c64..cd5ea46 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -34,7 +34,6 @@
 import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1902,9 +1901,6 @@
 
     @Override
     public boolean setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            return setTaskWindowingModeSplitScreenPrimary(taskId, toTop);
-        }
         enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "setTaskWindowingMode()");
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
@@ -2235,28 +2231,6 @@
     }
 
     /**
-     * Moves the specified task to the primary-split-screen stack.
-     *
-     * @param taskId Id of task to move.
-     * @param toTop  If the task and stack should be moved to the top.
-     * @return Whether the task was successfully put into splitscreen.
-     */
-    @Override
-    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, boolean toTop) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
-                "setTaskWindowingModeSplitScreenPrimary()");
-        synchronized (mGlobalLock) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                return setTaskWindowingModeSplitScreen(taskId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
-                        toTop);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    /**
      * Moves the specified task into a split-screen tile.
      */
     private boolean setTaskWindowingModeSplitScreen(int taskId, int windowingMode, boolean toTop) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
index af6c255..200f207 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
@@ -18,25 +18,29 @@
 
 import android.os.IBinder;
 
+import java.util.Collection;
+
 /**
- * Callback to be called when a background activity start is allowed exclusively because of the
- * token provided in {@link #getToken()}.
+ * Callback to decide activity starts and related operations based on originating tokens.
  */
 public interface BackgroundActivityStartCallback {
     /**
-     * The token for which this callback is responsible for deciding whether the app can start
-     * background activities or not.
+     * Returns true if the background activity start originating from {@code tokens} should be
+     * allowed or not.
      *
-     * Ideally this should just return a final variable, don't do anything costly here (don't hold
-     * any locks).
-     */
-    IBinder getToken();
-
-    /**
-     * Returns true if the background activity start due to originating token in {@link #getToken()}
-     * should be allowed or not.
+     * Note that if the start was allowed due to a mechanism other than tokens (eg. permission),
+     * this won't be called.
      *
      * This will be called holding the WM lock, don't do anything costly here.
      */
-    boolean isActivityStartAllowed(int uid, String packageName);
+    boolean isActivityStartAllowed(Collection<IBinder> tokens, int uid, String packageName);
+
+    /**
+     * Returns whether {@code uid} can send {@link android.content.Intent
+     * #ACTION_CLOSE_SYSTEM_DIALOGS}, presumably to start activities, based on the originating
+     * tokens {@code tokens} currently associated with potential activity starts.
+     *
+     * This will be called holding the AM and WM lock, don't do anything costly here.
+     */
+    boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 447e66e..292d7ea 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -27,7 +27,6 @@
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
@@ -689,9 +688,6 @@
 
     final BLASTSyncEngine mSyncEngine;
 
-    int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-    Rect mDockedStackCreateBounds;
-
     boolean mIsPc;
     /**
      * Flag that indicates that desktop mode is forced for public secondary screens.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 9d64af7..90949cb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -585,9 +585,8 @@
     }
 
     /**
-     * If there are no tokens, we don't allow *by token*. If there are tokens, we need to check if
-     * the callback handles all the tokens, if so we ask the callback if the activity should be
-     * started, otherwise we allow.
+     * If there are no tokens, we don't allow *by token*. If there are tokens, we ask the callback
+     * if the start is allowed for these tokens, otherwise if there is no callback we allow.
      */
     private boolean isBackgroundStartAllowedByToken() {
         if (mBackgroundActivityStartTokens.isEmpty()) {
@@ -597,16 +596,22 @@
             // We have tokens but no callback to decide => allow
             return true;
         }
-        IBinder callbackToken = mBackgroundActivityStartCallback.getToken();
-        for (IBinder token : mBackgroundActivityStartTokens.values()) {
-            if (token != callbackToken) {
-                // The callback doesn't handle all the tokens => allow
-                return true;
-            }
+        // The callback will decide
+        return mBackgroundActivityStartCallback.isActivityStartAllowed(
+                mBackgroundActivityStartTokens.values(), mInfo.uid, mInfo.packageName);
+    }
+
+    /**
+     * Returns whether this process is allowed to close system dialogs via a background activity
+     * start token that allows the close system dialogs operation (eg. notification).
+     */
+    public boolean canCloseSystemDialogsByToken() {
+        synchronized (mAtm.mGlobalLock) {
+            return !mBackgroundActivityStartTokens.isEmpty()
+                    && mBackgroundActivityStartCallback != null
+                    && mBackgroundActivityStartCallback.canCloseSystemDialogs(
+                            mBackgroundActivityStartTokens.values(), mInfo.uid);
         }
-        // The callback handles all the tokens => callback decides
-        return mBackgroundActivityStartCallback.isActivityStartAllowed(mInfo.uid,
-                mInfo.packageName);
     }
 
     private boolean isBoundByForegroundUid() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5f6d76be..7b4c1be 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7620,10 +7620,9 @@
                         + " as profile owner on user " + currentForegroundUser);
                 // Sets profile owner on current foreground user since
                 // the human user will complete the DO setup workflow from there.
-                mInjector.binderWithCleanCallingIdentity(() ->
-                        manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
-                                /* managedUser= */ currentForegroundUser,
-                                /* adminExtras= */ null));
+                manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
+                            /* managedUser= */ currentForegroundUser,
+                            /* adminExtras= */ null);
             }
             return true;
         }
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneEventTest.java b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneEventTest.java
deleted file mode 100644
index 84b886e..0000000
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneEventTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 2020 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.internal.location.timezone;
-
-import static com.android.internal.location.timezone.ParcelableTestSupport.assertRoundTripParcelable;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-import static java.util.Collections.singletonList;
-
-import org.junit.Test;
-
-import java.util.List;
-
-public class LocationTimeZoneEventTest {
-
-    private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 9999;
-
-    private static final List<String> ARBITRARY_TIME_ZONE_IDS = singletonList("Europe/London");
-
-    @Test(expected = RuntimeException.class)
-    public void testSetInvalidEventType() {
-        new LocationTimeZoneEvent.Builder().setEventType(Integer.MAX_VALUE);
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testBuildUnsetEventType() {
-        new LocationTimeZoneEvent.Builder()
-                .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS)
-                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
-                .build();
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testInvalidTimeZoneIds() {
-        new LocationTimeZoneEvent.Builder()
-                .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
-                .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS)
-                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
-                .build();
-    }
-
-    @Test
-    public void testEquals() {
-        LocationTimeZoneEvent.Builder builder1 = new LocationTimeZoneEvent.Builder()
-                .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
-                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            assertEquals(one, one);
-        }
-
-        LocationTimeZoneEvent.Builder builder2 = new LocationTimeZoneEvent.Builder()
-                .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
-                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            LocationTimeZoneEvent two = builder2.build();
-            assertEquals(one, two);
-            assertEquals(two, one);
-        }
-
-        builder1.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS + 1);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            LocationTimeZoneEvent two = builder2.build();
-            assertNotEquals(one, two);
-            assertNotEquals(two, one);
-        }
-
-        builder2.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS + 1);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            LocationTimeZoneEvent two = builder2.build();
-            assertEquals(one, two);
-            assertEquals(two, one);
-        }
-
-        builder2.setEventType(LocationTimeZoneEvent.EVENT_TYPE_SUCCESS);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            LocationTimeZoneEvent two = builder2.build();
-            assertNotEquals(one, two);
-            assertNotEquals(two, one);
-        }
-
-        builder1.setEventType(LocationTimeZoneEvent.EVENT_TYPE_SUCCESS);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            LocationTimeZoneEvent two = builder2.build();
-            assertEquals(one, two);
-            assertEquals(two, one);
-        }
-
-        builder2.setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            LocationTimeZoneEvent two = builder2.build();
-            assertNotEquals(one, two);
-            assertNotEquals(two, one);
-        }
-
-        builder1.setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS);
-        {
-            LocationTimeZoneEvent one = builder1.build();
-            LocationTimeZoneEvent two = builder2.build();
-            assertEquals(one, two);
-            assertEquals(two, one);
-        }
-    }
-
-    @Test
-    public void testParcelable() {
-        LocationTimeZoneEvent.Builder builder = new LocationTimeZoneEvent.Builder()
-                .setEventType(LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE)
-                .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
-        assertRoundTripParcelable(builder.build());
-
-        builder.setEventType(LocationTimeZoneEvent.EVENT_TYPE_SUCCESS)
-                .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS);
-        assertRoundTripParcelable(builder.build());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
deleted file mode 100644
index 95daa36..0000000
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2020 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.internal.location.timezone;
-
-import static com.android.internal.location.timezone.ParcelableTestSupport.assertRoundTripParcelable;
-
-import org.junit.Test;
-
-import java.time.Duration;
-
-public class LocationTimeZoneProviderRequestTest {
-
-    @Test
-    public void testParcelable() {
-        LocationTimeZoneProviderRequest.Builder builder =
-                new LocationTimeZoneProviderRequest.Builder()
-                        .setReportLocationTimeZone(false);
-        assertRoundTripParcelable(builder.build());
-
-        builder.setReportLocationTimeZone(true)
-                .setInitializationTimeoutMillis(Duration.ofMinutes(5).toMillis());
-
-        assertRoundTripParcelable(builder.build());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/ParcelableTestSupport.java b/services/tests/servicestests/src/com/android/internal/location/timezone/ParcelableTestSupport.java
deleted file mode 100644
index ece5d00..0000000
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/ParcelableTestSupport.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 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.internal.location.timezone;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.reflect.Field;
-
-/** Utility methods related to {@link Parcelable} objects used in several tests. */
-final class ParcelableTestSupport {
-
-    private ParcelableTestSupport() {}
-
-    /** Returns the result of parceling and unparceling the argument. */
-    @SuppressWarnings("unchecked")
-    public static <T extends Parcelable> T roundTripParcelable(T parcelable) {
-        Parcel parcel = Parcel.obtain();
-        parcel.writeTypedObject(parcelable, 0);
-        parcel.setDataPosition(0);
-
-        Parcelable.Creator<T> creator;
-        try {
-            Field creatorField = parcelable.getClass().getField("CREATOR");
-            creator = (Parcelable.Creator<T>) creatorField.get(null);
-        } catch (NoSuchFieldException | IllegalAccessException e) {
-            throw new AssertionError(e);
-        }
-        T toReturn = parcel.readTypedObject(creator);
-        parcel.recycle();
-        return toReturn;
-    }
-
-    public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
-        assertEquals(instance, roundTripParcelable(instance));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index dc81237..4de4d95 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -1053,11 +1053,6 @@
         }
 
         @Override
-        void logWarn(String msg) {
-            System.out.println(msg);
-        }
-
-        @Override
         public void dump(IndentingPrintWriter pw, String[] args) {
             // Nothing needed for tests.
         }
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS
new file mode 100644
index 0000000..28aff18
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
+nfuller@google.com
+include /core/java/android/app/timedetector/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index cfdd246..57d5323 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -58,6 +58,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -101,7 +102,6 @@
 
 import com.android.internal.R;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.util.FastXmlSerializer;
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.ManagedServices.UserProfiles;
 
@@ -110,9 +110,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -134,9 +132,13 @@
     private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
     private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
     private static final int ZEN_MODE_FOR_TESTING = 99;
+    private static final String CUSTOM_PKG_NAME = "not.android";
+    private static final int CUSTOM_PKG_UID = 1;
+    private static final String CUSTOM_RULE_ID = "custom_rule";
 
     ConditionProviders mConditionProviders;
     @Mock NotificationManager mNotificationManager;
+    @Mock PackageManager mPackageManager;
     private Resources mResources;
     private TestableLooper mTestableLooper;
     private ZenModeHelper mZenModeHelperSpy;
@@ -146,7 +148,7 @@
     private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
 
     @Before
-    public void setUp() {
+    public void setUp() throws PackageManager.NameNotFoundException {
         MockitoAnnotations.initMocks(this);
 
         mTestableLooper = TestableLooper.get(this);
@@ -169,6 +171,10 @@
         mConditionProviders.addSystemProvider(new CountdownConditionProvider());
         mZenModeHelperSpy = spy(new ZenModeHelper(mContext, mTestableLooper.getLooper(),
                 mConditionProviders, mStatsEventBuilderFactory));
+
+        when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt()))
+                .thenReturn(CUSTOM_PKG_UID);
+        mZenModeHelperSpy.mPm = mPackageManager;
     }
 
     private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
@@ -238,19 +244,24 @@
 
     private ArrayMap<String, ZenModeConfig.ZenRule> getCustomAutomaticRules(int zenMode) {
         ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
+        ZenModeConfig.ZenRule rule = createCustomAutomaticRule(zenMode, CUSTOM_RULE_ID);
+        automaticRules.put(rule.id, rule);
+        return automaticRules;
+    }
+
+    private ZenModeConfig.ZenRule createCustomAutomaticRule(int zenMode, String id) {
         ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
         final ScheduleInfo customRuleInfo = new ScheduleInfo();
         customRule.enabled = true;
         customRule.creationTime = 0;
-        customRule.id = "customRule";
-        customRule.name = "Custom Rule";
+        customRule.id = id;
+        customRule.name = "Custom Rule with id=" + id;
         customRule.zenMode = zenMode;
         customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
         customRule.configurationActivity =
-                new ComponentName("not.android", "ScheduleConditionProvider");
+                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider");
         customRule.pkg = customRule.configurationActivity.getPackageName();
-        automaticRules.put("customRule", customRule);
-        return automaticRules;
+        return customRule;
     }
 
     @Test
@@ -893,7 +904,7 @@
             if (builder.getAtomId() == DND_MODE_RULE) {
                 if (ZEN_MODE_FOR_TESTING == builder.getInt(ZEN_MODE_FIELD_NUMBER)) {
                     foundCustomEvent = true;
-                    assertEquals(0, builder.getInt(UID_FIELD_NUMBER));
+                    assertEquals(CUSTOM_PKG_UID, builder.getInt(UID_FIELD_NUMBER));
                     assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
                 }
             } else {
@@ -904,6 +915,46 @@
     }
 
     @Test
+    public void ruleUidsCached() throws Exception {
+        setupZenConfig();
+        // one enabled automatic rule
+        mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules();
+        List<StatsEvent> events = new LinkedList<>();
+        // first time retrieving uid:
+        mZenModeHelperSpy.pullRules(events);
+        verify(mPackageManager, atLeastOnce()).getPackageUidAsUser(anyString(), anyInt());
+
+        // second time retrieving uid:
+        reset(mPackageManager);
+        mZenModeHelperSpy.pullRules(events);
+        verify(mPackageManager, never()).getPackageUidAsUser(anyString(), anyInt());
+
+        // new rule from same package + user added
+        reset(mPackageManager);
+        ZenModeConfig.ZenRule rule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                CUSTOM_RULE_ID + "2");
+        mZenModeHelperSpy.mConfig.automaticRules.put(rule.id, rule);
+        mZenModeHelperSpy.pullRules(events);
+        verify(mPackageManager, never()).getPackageUidAsUser(anyString(), anyInt());
+    }
+
+    @Test
+    public void ruleUidAutomaticZenRuleRemovedUpdatesCache() throws Exception {
+        when(mContext.checkCallingPermission(anyString()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        setupZenConfig();
+        // one enabled automatic rule
+        mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules();
+        List<StatsEvent> events = new LinkedList<>();
+
+        mZenModeHelperSpy.pullRules(events);
+        mZenModeHelperSpy.removeAutomaticZenRule(CUSTOM_RULE_ID, "test");
+        assertTrue(-1
+                == mZenModeHelperSpy.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
+    }
+
+    @Test
     public void testProtoRedactsIds() throws Exception {
         setupZenConfig();
         // one enabled automatic rule
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 1ecf850..cf977b4 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -47,6 +47,7 @@
         "testables",
         "ub-uiautomator",
         "hamcrest-library",
+        "platform-compat-test-rules",
     ],
 
     libs: [
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 2acb647..8983b4e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
@@ -53,7 +52,6 @@
         opts.setRotationAnimationHint(ROTATION_ANIMATION_ROTATE);
         opts.setTaskAlwaysOnTop(true);
         opts.setTaskOverlay(true, true);
-        opts.setSplitScreenCreateMode(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
         Bundle optsBundle = opts.toBundle();
 
         // Try and merge the constructed options with a new set of options
@@ -71,7 +69,5 @@
         assertTrue(restoredOpts.getTaskAlwaysOnTop());
         assertTrue(restoredOpts.getTaskOverlay());
         assertTrue(restoredOpts.canTaskOverlayResume());
-        assertEquals(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
-                restoredOpts.getSplitScreenCreateMode());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 4892ef3..93ef126b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1006,8 +1006,6 @@
         assertNotRestoreTask(
                 () -> mAtm.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN,
                         false/* toTop */));
-        assertNotRestoreTask(
-                () -> mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, false /* toTop */));
     }
 
     @Test
@@ -1144,8 +1142,6 @@
         assertSecurityException(expectCallable,
                 () -> mAtm.moveTaskToRootTask(0, INVALID_STACK_ID, true));
         assertSecurityException(expectCallable,
-                () -> mAtm.setTaskWindowingModeSplitScreenPrimary(0, true));
-        assertSecurityException(expectCallable,
                 () -> mAtm.moveTopActivityToPinnedRootTask(INVALID_STACK_ID, new Rect()));
         assertSecurityException(expectCallable, () -> mAtm.getAllRootTaskInfos());
         assertSecurityException(expectCallable,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index c7175a0c..e190248 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -49,6 +49,8 @@
 import android.app.ActivityManagerInternal;
 import android.app.TaskStackListener;
 import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -58,7 +60,11 @@
 
 import androidx.test.filters.MediumTest;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
@@ -73,6 +79,9 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class SizeCompatTests extends WindowTestsBase {
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     private Task mTask;
     private ActivityRecord mActivity;
 
@@ -535,6 +544,26 @@
     }
 
     @Test
+    @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+    public void testNoSizeCompatWhenPerAppOverrideSet() {
+        setUpDisplaySizeWithApp(1000, 2500);
+
+        // Make the task root resizable.
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+        // Create a size compat activity on the same task.
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
+                .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+        assertFalse(activity.shouldUseSizeCompatMode());
+    }
+
+    @Test
     public void testLaunchWithFixedRotationTransform() {
         final int dw = 1000;
         final int dh = 2500;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index d81c24d..ba7770d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -107,7 +107,7 @@
                                 configuration.endRotation)
                             navBarLayerIsAlwaysVisible(enabled = false)
                             statusBarLayerIsAlwaysVisible(enabled = false)
-                            visibleLayersShownMoreThanOneConsecutiveEntry(bugId = 174541970)
+                            visibleLayersShownMoreThanOneConsecutiveEntry()
 
                             appLayerReplacesWallpaperLayer(testApp.`package`)
                         }