Merge "Expose get/set notification listeners as a @SystemAPI"
diff --git a/Android.bp b/Android.bp
index afdd832..1f17932 100644
--- a/Android.bp
+++ b/Android.bp
@@ -482,6 +482,7 @@
         "android.hardware.thermal-V2.0-java",
         "android.hardware.tv.input-V1.0-java-constants",
         "android.hardware.tv.tuner-V1.0-java-constants",
+        "android.hardware.tv.tuner-V1.1-java-constants",
         "android.hardware.usb-V1.0-java-constants",
         "android.hardware.usb-V1.1-java-constants",
         "android.hardware.usb-V1.2-java-constants",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 05bf7b6..8ac9842 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -70,6 +70,7 @@
         "android.hardware.thermal-V2.0-java",
         "android.hardware.tv.input-V1.0-java-constants",
         "android.hardware.tv.tuner-V1.0-java-constants",
+        "android.hardware.tv.tuner-V1.1-java-constants",
         "android.hardware.usb-V1.0-java-constants",
         "android.hardware.usb-V1.1-java-constants",
         "android.hardware.usb.gadget-V1.0-java",
@@ -121,7 +122,6 @@
 droidstubs {
     name: "api-stubs-docs",
     defaults: ["metalava-full-api-stubs-default"],
-    removed_dex_api_filename: "removed-dex.txt",
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
@@ -142,12 +142,6 @@
             baseline_file: "api/lint-baseline.txt",
         },
     },
-    dist: {
-        targets: ["sdk", "win_sdk"],
-        dir: "apistubs/android/public/api",
-        dest: "android.txt",
-    },
-    jdiff_enabled: true,
 }
 
 droidstubs {
@@ -188,7 +182,6 @@
 droidstubs {
     name: "system-api-stubs-docs",
     defaults: ["metalava-full-api-stubs-default"],
-    removed_dex_api_filename: "system-removed-dex.txt",
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
@@ -209,12 +202,6 @@
             baseline_file: "api/system-lint-baseline.txt",
         },
     },
-    dist: {
-        targets: ["sdk", "win_sdk"],
-        dir: "apistubs/android/system/api",
-        dest: "android.txt",
-    },
-    jdiff_enabled: true,
 }
 
 droidstubs {
@@ -246,7 +233,11 @@
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
-    args: metalava_framework_docs_args + " --show-annotation android.annotation.TestApi",
+    args: metalava_framework_docs_args
+        + " --show-annotation android.annotation.TestApi"
+        + " --show-for-stub-purposes-annotation android.annotation.SystemApi\\("
+        +     "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS"
+        + "\\)",
     check_api: {
         current: {
             api_file: "api/test-current.txt",
@@ -294,11 +285,6 @@
             baseline_file: "api/module-lib-lint-baseline.txt",
         },
     },
-    dist: {
-        targets: ["sdk", "win_sdk"],
-        dir: "apistubs/android/module-lib/api",
-        dest: "android.txt",
-    },
 }
 
 droidstubs {
@@ -367,6 +353,7 @@
     srcs: [ ":api-stubs-docs-non-updatable" ],
     static_libs: [
         "conscrypt.module.public.api.stubs",
+        "framework-appsearch.stubs",
         "framework-graphics.stubs",
         "framework-media.stubs",
         "framework-mediaprovider.stubs",
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index f672e4b..45ea233 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -16,10 +16,13 @@
 
 package com.android.server.alarm;
 
+import static android.app.AlarmManager.ELAPSED_REALTIME;
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
 
+import static com.android.server.alarm.AlarmManagerService.clampPositive;
+
 import android.app.AlarmManager;
 import android.app.IAlarmListener;
 import android.app.PendingIntent;
@@ -32,8 +35,28 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
+/**
+ * Class to describe an alarm that is used to the set the kernel timer that returns when the timer
+ * expires. The timer will wake up the device if the alarm is a "wakeup" alarm.
+ */
 class Alarm {
+    private static final int NUM_POLICIES = 2;
+    /**
+     * Index used to store the time the alarm was requested to expire. To be used with
+     * {@link #setPolicyElapsed(int, long)}
+     */
+    public static final int REQUESTER_POLICY_INDEX = 0;
+    /**
+     * Index used to store the earliest time the alarm can expire based on app-standby policy.
+     * To be used with {@link #setPolicyElapsed(int, long)}
+     */
+    public static final int APP_STANDBY_POLICY_INDEX = 1;
+
     public final int type;
+    /**
+     * The original trigger time supplied by the caller. This can be in the elapsed or rtc time base
+     * depending on the type of this alarm
+     */
     public final long origWhen;
     public final boolean wakeup;
     public final PendingIntent operation;
@@ -47,42 +70,40 @@
     public final int creatorUid;
     public final String packageName;
     public final String sourcePackage;
+    public final long windowLength;
+    public final long repeatInterval;
     public int count;
-    public long when;
-    public long windowLength;
-    public long whenElapsed;    // 'when' in the elapsed time base
-    public long maxWhenElapsed; // also in the elapsed time base
-    // Expected alarm expiry time before app standby deferring is applied.
-    public long expectedWhenElapsed;
-    public long expectedMaxWhenElapsed;
-    public long repeatInterval;
+    /** The earliest time this alarm is eligible to fire according to each policy */
+    private long[] mPolicyWhenElapsed;
+    /** The ultimate delivery time to be used for this alarm */
+    private long mWhenElapsed;
+    private long mMaxWhenElapsed;
     public AlarmManagerService.PriorityClass priorityClass;
 
-    Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
-            long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag,
-            WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info,
-            int _uid, String _pkgName) {
-        type = _type;
-        origWhen = _when;
-        wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
-                || _type == AlarmManager.RTC_WAKEUP;
-        when = _when;
-        whenElapsed = _whenElapsed;
-        expectedWhenElapsed = _whenElapsed;
-        windowLength = _windowLength;
-        maxWhenElapsed = expectedMaxWhenElapsed = AlarmManagerService.clampPositive(_maxWhen);
-        repeatInterval = _interval;
-        operation = _op;
-        listener = _rec;
-        listenerTag = _listenerTag;
-        statsTag = makeTag(_op, _listenerTag, _type);
-        workSource = _ws;
-        flags = _flags;
-        alarmClock = _info;
-        uid = _uid;
-        packageName = _pkgName;
+    Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
+            PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
+            AlarmManager.AlarmClockInfo info, int uid, String pkgName) {
+        this.type = type;
+        origWhen = when;
+        wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
+                || type == AlarmManager.RTC_WAKEUP;
+        mPolicyWhenElapsed = new long[NUM_POLICIES];
+        mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] = requestedWhenElapsed;
+        mWhenElapsed = requestedWhenElapsed;
+        this.windowLength = windowLength;
+        mMaxWhenElapsed = clampPositive(requestedWhenElapsed + windowLength);
+        repeatInterval = interval;
+        operation = op;
+        listener = rec;
+        this.listenerTag = listenerTag;
+        statsTag = makeTag(op, listenerTag, type);
+        workSource = ws;
+        this.flags = flags;
+        alarmClock = info;
+        this.uid = uid;
+        packageName = pkgName;
         sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
-        creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
+        creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
     }
 
     public static String makeTag(PendingIntent pi, String tag, int type) {
@@ -91,13 +112,6 @@
         return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
     }
 
-    public AlarmManagerService.WakeupEvent makeWakeupEvent(long nowRTC) {
-        return new AlarmManagerService.WakeupEvent(nowRTC, creatorUid,
-                (operation != null)
-                    ? operation.getIntent().getAction()
-                    : ("<listener>:" + listenerTag));
-    }
-
     // Returns true if either matches
     public boolean matches(PendingIntent pi, IAlarmListener rec) {
         return (operation != null)
@@ -109,6 +123,65 @@
         return packageName.equals(sourcePackage);
     }
 
+    /**
+     * Get the earliest time this alarm is allowed to expire based on the given policy.
+     *
+     * @param policyIndex The index of the policy. One of [{@link #REQUESTER_POLICY_INDEX},
+     *                    {@link #APP_STANDBY_POLICY_INDEX}].
+     */
+    public long getPolicyElapsed(int policyIndex) {
+        return mPolicyWhenElapsed[policyIndex];
+    }
+
+    /**
+     * Get the earliest time that this alarm should be delivered to the requesting app.
+     */
+    public long getWhenElapsed() {
+        return mWhenElapsed;
+    }
+
+    /**
+     * Get the latest time that this alarm should be delivered to the requesting app. Will be equal
+     * to {@link #getWhenElapsed()} in case this is an exact alarm.
+     */
+    public long getMaxWhenElapsed() {
+        return mMaxWhenElapsed;
+    }
+
+    /**
+     * Set the earliest time this alarm can expire based on the passed policy index.
+     *
+     * @return {@code true} if this change resulted in a change in the ultimate delivery time (or
+     * time window in the case of inexact alarms) of this alarm.
+     * @see #getWhenElapsed()
+     * @see #getMaxWhenElapsed()
+     * @see #getPolicyElapsed(int)
+     */
+    public boolean setPolicyElapsed(int policyIndex, long policyElapsed) {
+        mPolicyWhenElapsed[policyIndex] = policyElapsed;
+        return updateWhenElapsed();
+    }
+
+    /**
+     * @return {@code true} if either {@link #mWhenElapsed} or {@link #mMaxWhenElapsed} changes
+     * due to this call.
+     */
+    private boolean updateWhenElapsed() {
+        final long oldWhenElapsed = mWhenElapsed;
+        mWhenElapsed = 0;
+        for (int i = 0; i < NUM_POLICIES; i++) {
+            mWhenElapsed = Math.max(mWhenElapsed, mPolicyWhenElapsed[i]);
+        }
+
+        final long oldMaxWhenElapsed = mMaxWhenElapsed;
+        // windowLength should always be >= 0 here.
+        final long maxRequestedElapsed = clampPositive(
+                mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] + windowLength);
+        mMaxWhenElapsed = Math.max(maxRequestedElapsed, mWhenElapsed);
+
+        return (oldWhenElapsed != mWhenElapsed) || (oldMaxWhenElapsed != mMaxWhenElapsed);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
@@ -116,11 +189,11 @@
         sb.append(Integer.toHexString(System.identityHashCode(this)));
         sb.append(" type ");
         sb.append(type);
-        sb.append(" when ");
-        sb.append(when);
+        sb.append(" origWhen ");
+        sb.append(origWhen);
         sb.append(" ");
         sb.append(" whenElapsed ");
-        sb.append(whenElapsed);
+        sb.append(getWhenElapsed());
         sb.append(" ");
         sb.append(sourcePackage);
         sb.append('}');
@@ -136,30 +209,46 @@
         dump(ipw, nowELAPSED, sdf);
     }
 
+    private static String policyIndexToString(int index) {
+        switch (index) {
+            case REQUESTER_POLICY_INDEX:
+                return "requester";
+            case APP_STANDBY_POLICY_INDEX:
+                return "app_standby";
+            default:
+                return "unknown";
+        }
+    }
+
+    public static String typeToString(int type) {
+        switch (type) {
+            case RTC:
+                return "RTC";
+            case RTC_WAKEUP:
+                return "RTC_WAKEUP";
+            case ELAPSED_REALTIME:
+                return "ELAPSED";
+            case ELAPSED_REALTIME_WAKEUP:
+                return "ELAPSED_WAKEUP";
+            default:
+                return "--unknown--";
+        }
+    }
+
     public void dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf) {
         final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
         ipw.print("tag=");
         ipw.println(statsTag);
 
         ipw.print("type=");
-        ipw.print(type);
-        ipw.print(" expectedWhenElapsed=");
-        TimeUtils.formatDuration(expectedWhenElapsed, nowELAPSED, ipw);
-        ipw.print(" expectedMaxWhenElapsed=");
-        TimeUtils.formatDuration(expectedMaxWhenElapsed, nowELAPSED, ipw);
-        ipw.print(" whenElapsed=");
-        TimeUtils.formatDuration(whenElapsed, nowELAPSED, ipw);
-        ipw.print(" maxWhenElapsed=");
-        TimeUtils.formatDuration(maxWhenElapsed, nowELAPSED, ipw);
-        ipw.print(" when=");
+        ipw.print(typeToString(type));
+        ipw.print(" origWhen=");
         if (isRtc) {
-            ipw.print(sdf.format(new Date(when)));
+            ipw.print(sdf.format(new Date(origWhen)));
         } else {
-            TimeUtils.formatDuration(when, nowELAPSED, ipw);
+            TimeUtils.formatDuration(origWhen, nowELAPSED, ipw);
         }
-        ipw.println();
-
-        ipw.print("window=");
+        ipw.print(" window=");
         TimeUtils.formatDuration(windowLength, ipw);
         ipw.print(" repeatInterval=");
         ipw.print(repeatInterval);
@@ -168,6 +257,19 @@
         ipw.print(" flags=0x");
         ipw.println(Integer.toHexString(flags));
 
+        ipw.print("policyWhenElapsed:");
+        for (int i = 0; i < NUM_POLICIES; i++) {
+            ipw.print(" " + policyIndexToString(i) + "=");
+            TimeUtils.formatDuration(mPolicyWhenElapsed[i], nowELAPSED, ipw);
+        }
+        ipw.println();
+
+        ipw.print("whenElapsed=");
+        TimeUtils.formatDuration(getWhenElapsed(), nowELAPSED, ipw);
+        ipw.print(" maxWhenElapsed=");
+        TimeUtils.formatDuration(mMaxWhenElapsed, nowELAPSED, ipw);
+        ipw.println();
+
         if (alarmClock != null) {
             ipw.println("Alarm clock:");
 
@@ -177,9 +279,10 @@
             ipw.print("  showIntent=");
             ipw.println(alarmClock.getShowIntent());
         }
-        ipw.print("operation=");
-        ipw.println(operation);
-
+        if (operation != null) {
+            ipw.print("operation=");
+            ipw.println(operation);
+        }
         if (listener != null) {
             ipw.print("listener=");
             ipw.println(listener.asBinder());
@@ -191,7 +294,7 @@
 
         proto.write(AlarmProto.TAG, statsTag);
         proto.write(AlarmProto.TYPE, type);
-        proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, whenElapsed - nowElapsed);
+        proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, getWhenElapsed() - nowElapsed);
         proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
         proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
         proto.write(AlarmProto.COUNT, count);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 05910a5..82819da 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -26,6 +26,9 @@
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.os.UserHandle.USER_SYSTEM;
 
+import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
+import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
+
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -727,9 +730,9 @@
             }
 
             // within each class, sort by nominal delivery time
-            if (lhs.whenElapsed < rhs.whenElapsed) {
+            if (lhs.getWhenElapsed() < rhs.getWhenElapsed()) {
                 return -1;
-            } else if (lhs.whenElapsed > rhs.whenElapsed) {
+            } else if (lhs.getWhenElapsed() > rhs.getWhenElapsed()) {
                 return 1;
             }
 
@@ -798,9 +801,12 @@
         this(context, new Injector(context));
     }
 
+    private static boolean isRtc(int type) {
+        return (type == RTC || type == RTC_WAKEUP);
+    }
+
     private long convertToElapsed(long when, int type) {
-        final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
-        if (isRtc) {
+        if (isRtc(type)) {
             when -= mInjector.getCurrentTimeMillis() - mInjector.getElapsedRealtime();
         }
         return when;
@@ -823,13 +829,29 @@
     }
 
     // The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries.
-    void reevaluateRtcAlarms(final long nowElapsed) {
+    void reevaluateRtcAlarms() {
         synchronized (mLock) {
-            final ArrayList<Alarm> rtcAlarms = mAlarmStore.remove(a -> (a.type == RTC
-                    || a.type == RTC_WAKEUP));
-            for (final Alarm a : rtcAlarms) {
-                restoreAlarmLocked(a, nowElapsed);
-                setImplLocked(a);
+            boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
+                if (!isRtc(a.type)) {
+                    return false;
+                }
+                return restoreRequestedTime(a);
+            });
+
+            if (mNextWakeFromIdle != null && isRtc(mNextWakeFromIdle.type)) {
+                // The next wake from idle got updated due to the rtc time change, implying we need
+                // to update the time we have to come out of idle too.
+                changed |= mAlarmStore.updateAlarmDeliveries(a -> {
+                    if (a != mPendingIdleUntil) {
+                        return false;
+                    }
+                    return adjustIdleUntilTime(a);
+                });
+            }
+
+            if (changed) {
+                rescheduleKernelAlarmsLocked();
+                // Only time shifted, so the next alarm clock will not change
             }
         }
     }
@@ -844,7 +866,7 @@
     boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) {
         final long start = mStatLogger.getTime();
 
-        final boolean changed = mAlarmStore.recalculateAlarmDeliveries(a -> {
+        final boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
             final Pair<String, Integer> packageUser =
                     Pair.create(a.sourcePackage, UserHandle.getUserId(a.creatorUid));
             if (targetPackages != null && !targetPackages.contains(packageUser)) {
@@ -857,23 +879,8 @@
         return changed;
     }
 
-    private void restoreAlarmLocked(Alarm a, long nowElapsed) {
-        a.when = a.origWhen;
-        long whenElapsed = convertToElapsed(a.when, a.type);
-        final long maxElapsed;
-        if (a.windowLength == AlarmManager.WINDOW_EXACT) {
-            // Exact
-            maxElapsed = whenElapsed;
-        } else {
-            // Not exact.  Preserve any explicit window, otherwise recalculate
-            // the window based on the alarm's new futurity.  Note that this
-            // reflects a policy of preferring timely to deferred delivery.
-            maxElapsed = (a.windowLength > 0)
-                    ? clampPositive(whenElapsed + a.windowLength)
-                    : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
-        }
-        a.expectedWhenElapsed = a.whenElapsed = whenElapsed;
-        a.expectedMaxWhenElapsed = a.maxWhenElapsed = maxElapsed;
+    private boolean restoreRequestedTime(Alarm a) {
+        return a.setPolicyElapsed(REQUESTER_POLICY_INDEX, convertToElapsed(a.origWhen, a.type));
     }
 
     static long clampPositive(long val) {
@@ -973,14 +980,17 @@
             // Recurring alarms may have passed several alarm intervals while the
             // alarm was kept pending. Send the appropriate trigger count.
             if (alarm.repeatInterval > 0) {
-                alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
+                alarm.count += (nowELAPSED - alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX))
+                        / alarm.repeatInterval;
                 // Also schedule its next recurrence
                 final long delta = alarm.count * alarm.repeatInterval;
-                final long nextElapsed = alarm.expectedWhenElapsed + delta;
-                setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
-                        maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
-                        alarm.repeatInterval, alarm.operation, null, null, alarm.flags,
-                        alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
+                final long nextElapsed = alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX) + delta;
+                final long nextMaxElapsed = maxTriggerTime(nowELAPSED, nextElapsed,
+                        alarm.repeatInterval);
+                setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
+                        nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
+                        null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
+                        alarm.packageName);
                 // Kernel alarms will be rescheduled as needed in setImplLocked
             }
         }
@@ -1026,18 +1036,10 @@
         if (mPendingWhileIdleAlarms.size() > 0) {
             ArrayList<Alarm> alarms = mPendingWhileIdleAlarms;
             mPendingWhileIdleAlarms = new ArrayList<>();
-            final long nowElapsed = mInjector.getElapsedRealtime();
             for (int i = alarms.size() - 1; i >= 0; i--) {
-                Alarm a = alarms.get(i);
-                restoreAlarmLocked(a, nowElapsed);
-                setImplLocked(a);
+                setImplLocked(alarms.get(i));
             }
         }
-
-        // Reschedule everything.
-        rescheduleKernelAlarmsLocked();
-        updateNextAlarmClockLocked();
-
     }
 
     static final class InFlight {
@@ -1449,6 +1451,11 @@
             }
         }
 
+        if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+            // Do not support windows for idle-until alarms.
+            windowLength = AlarmManager.WINDOW_EXACT;
+        }
+
         // Sanity check the window length.  This will catch people mistakenly
         // trying to pass an end-of-window timestamp rather than a duration.
         if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
@@ -1515,17 +1522,17 @@
                 Slog.w(TAG, errorMsg);
                 throw new IllegalStateException(errorMsg);
             }
-            setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
-                    interval, operation, directReceiver, listenerTag, flags, workSource,
-                    alarmClock, callingUid, callingPackage);
+            setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
+                    directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
+                    callingPackage);
         }
     }
 
     private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
-            long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
+            long interval, PendingIntent operation, IAlarmListener directReceiver,
             String listenerTag, int flags, WorkSource workSource,
             AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
-        Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
+        final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
                 operation, directReceiver, listenerTag, workSource, flags, alarmClock,
                 callingUid, callingPackage);
         if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
@@ -1560,72 +1567,55 @@
     }
 
     /**
-     * Adjusts the idle-until alarm delivery time based on the upcoming wake-from-idle alarm.
+     * An alarm with {@link AlarmManager#FLAG_IDLE_UNTIL} is a special alarm that will put the
+     * system into idle until it goes off. We need to pull it earlier if there are existing alarms
+     * that have requested to bring us out of idle at an earlier time.
      *
      * @param alarm The alarm to adjust
      * @return true if the alarm delivery time was updated.
      */
     private boolean adjustIdleUntilTime(Alarm alarm) {
-        if ((alarm.flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+        if ((alarm.flags & AlarmManager.FLAG_IDLE_UNTIL) == 0) {
             return false;
         }
-        // This is a special alarm that will put the system into idle until it goes off.
-        // The caller has given the time they want this to happen at, however we need
-        // to pull that earlier if there are existing alarms that have requested to
-        // bring us out of idle at an earlier time.
-        if (mNextWakeFromIdle != null && alarm.whenElapsed > mNextWakeFromIdle.whenElapsed) {
-            alarm.when = alarm.whenElapsed = alarm.maxWhenElapsed = mNextWakeFromIdle.whenElapsed;
+        restoreRequestedTime(alarm);
+        long triggerBeforeFuzz = alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX);
+        if (mNextWakeFromIdle != null && triggerBeforeFuzz > mNextWakeFromIdle.getWhenElapsed()) {
+            triggerBeforeFuzz = mNextWakeFromIdle.getWhenElapsed();
         }
         // Add fuzz to make the alarm go off some time before the actual desired time.
-        final long nowElapsed = mInjector.getElapsedRealtime();
-        final int fuzz = fuzzForDuration(alarm.whenElapsed - nowElapsed);
+        final int fuzz = fuzzForDuration(alarm.getWhenElapsed() - mInjector.getElapsedRealtime());
+        final int delta;
         if (fuzz > 0) {
             if (mRandom == null) {
                 mRandom = new Random();
             }
-            final int delta = mRandom.nextInt(fuzz);
-            alarm.whenElapsed -= delta;
-            if (false) {
-                Slog.d(TAG, "Alarm when: " + alarm.whenElapsed);
-                Slog.d(TAG, "Delta until alarm: " + (alarm.whenElapsed - nowElapsed));
-                Slog.d(TAG, "Applied fuzz: " + fuzz);
-                Slog.d(TAG, "Final delta: " + delta);
-                Slog.d(TAG, "Final when: " + alarm.whenElapsed);
-            }
-            alarm.when = alarm.maxWhenElapsed = alarm.whenElapsed;
+            delta = mRandom.nextInt(fuzz);
+        } else {
+            delta = 0;
         }
+        alarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, triggerBeforeFuzz - delta);
         return true;
     }
 
     /**
-     * Adjusts the alarm delivery time based on the current app standby bucket.
+     * Adjusts the alarm's policy time for app_standby.
      *
-     * @param alarm The alarm to adjust
-     * @return true if the alarm delivery time was updated.
+     * @param alarm The alarm to update.
+     * @return {@code true} if the actual delivery time of the given alarm was updated due to
+     *         adjustments made in this call.
      */
     private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
-        if (isExemptFromAppStandby(alarm)) {
-            return false;
+        final long nowElapsed = mInjector.getElapsedRealtime();
+        if (isExemptFromAppStandby(alarm) || mAppStandbyParole) {
+            return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
         }
-        if (mAppStandbyParole) {
-            if (alarm.whenElapsed > alarm.expectedWhenElapsed) {
-                // We did defer this alarm earlier, restore original requirements
-                alarm.whenElapsed = alarm.expectedWhenElapsed;
-                alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
-                return true;
-            }
-            return false;
-        }
-        final long oldWhenElapsed = alarm.whenElapsed;
-        final long oldMaxWhenElapsed = alarm.maxWhenElapsed;
 
         final String sourcePackage = alarm.sourcePackage;
         final int sourceUserId = UserHandle.getUserId(alarm.creatorUid);
         final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
-                sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
+                sourcePackage, sourceUserId, nowElapsed);
 
-        // Quota deferring implementation:
-        boolean deferred = false;
         final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage,
                 sourceUserId);
         if (standbyBucket == UsageStatsManager.STANDBY_BUCKET_RESTRICTED) {
@@ -1635,14 +1625,9 @@
             if (wakeupsInWindow > 0) {
                 final long lastWakeupTime = mAppWakeupHistory.getNthLastWakeupForPackage(
                         sourcePackage, sourceUserId, mConstants.APP_STANDBY_RESTRICTED_QUOTA);
-                if (mInjector.getElapsedRealtime() - lastWakeupTime
-                        < mConstants.APP_STANDBY_RESTRICTED_WINDOW) {
-                    final long minElapsed =
-                            lastWakeupTime + mConstants.APP_STANDBY_RESTRICTED_WINDOW;
-                    if (alarm.expectedWhenElapsed < minElapsed) {
-                        alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
-                        deferred = true;
-                    }
+                if ((nowElapsed - lastWakeupTime) < mConstants.APP_STANDBY_RESTRICTED_WINDOW) {
+                    return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX,
+                            lastWakeupTime + mConstants.APP_STANDBY_RESTRICTED_WINDOW);
                 }
             }
         } else {
@@ -1651,7 +1636,7 @@
                 final long minElapsed;
                 if (quotaForBucket <= 0) {
                     // Just keep deferring for a day till the quota changes
-                    minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY;
+                    minElapsed = nowElapsed + MILLIS_IN_DAY;
                 } else {
                     // Suppose the quota for window was q, and the qth last delivery time for this
                     // package was t(q) then the next delivery must be after t(q) + <window_size>
@@ -1659,19 +1644,11 @@
                             sourcePackage, sourceUserId, quotaForBucket);
                     minElapsed = t + mConstants.APP_STANDBY_WINDOW;
                 }
-                if (alarm.expectedWhenElapsed < minElapsed) {
-                    alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
-                    deferred = true;
-                }
+                return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, minElapsed);
             }
         }
-        if (!deferred) {
-            // Restore original requirements in case they were changed earlier.
-            alarm.whenElapsed = alarm.expectedWhenElapsed;
-            alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
-        }
-
-        return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
+        // wakeupsInWindow are less than the permitted quota, hence no deferring is needed.
+        return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
     }
 
     private static boolean isAllowedWhileIdle(Alarm a) {
@@ -1691,7 +1668,7 @@
                 ent.tag = a.operation.getTag("");
                 ent.op = "SET";
                 ent.elapsedRealtime = mInjector.getElapsedRealtime();
-                ent.argRealtime = a.whenElapsed;
+                ent.argRealtime = a.getWhenElapsed();
                 mAllowWhileIdleDispatches.add(ent);
                 if (mPendingIdleUntil == null) {
                     IdleDispatchEntry ent2 = new IdleDispatchEntry();
@@ -1704,6 +1681,7 @@
             if ((mPendingIdleUntil != a) && (mPendingIdleUntil != null)) {
                 Slog.wtfStack(TAG, "setImplLocked: idle until changed from " + mPendingIdleUntil
                         + " to " + a);
+                mAlarmStore.remove(mPendingIdleUntil::equals);
             }
             mPendingIdleUntil = a;
             final ArrayList<Alarm> notAllowedWhileIdleAlarms = mAlarmStore.remove(
@@ -1718,18 +1696,16 @@
             }
         }
         if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
-            if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) {
+            if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed()
+                    > a.getWhenElapsed()) {
                 mNextWakeFromIdle = a;
                 // If this wake from idle is earlier than whatever was previously scheduled,
-                // and we are currently idling, then we need to rebatch alarms in case the idle
-                // until time needs to be updated.
+                // and we are currently idling, then the idle-until time needs to be updated.
                 if (mPendingIdleUntil != null) {
-                    final long nowElapsed = mInjector.getElapsedRealtime();
-                    mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+                    mAlarmStore.updateAlarmDeliveries(alarm -> {
                         if (alarm != mPendingIdleUntil) {
                             return false;
                         }
-                        restoreAlarmLocked(alarm, nowElapsed);
                         return adjustIdleUntilTime(alarm);
                     });
                 }
@@ -2563,7 +2539,7 @@
 
     long getNextWakeFromIdleTimeImpl() {
         synchronized (mLock) {
-            return mNextWakeFromIdle != null ? mNextWakeFromIdle.whenElapsed : Long.MAX_VALUE;
+            return mNextWakeFromIdle != null ? mNextWakeFromIdle.getWhenElapsed() : Long.MAX_VALUE;
         }
     }
 
@@ -2784,12 +2760,11 @@
                 restorePending = true;
             }
             if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {
-                mNextWakeFromIdle = null;
-                mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+                mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+                mAlarmStore.updateAlarmDeliveries(alarm -> {
                     if (alarm != mPendingIdleUntil) {
                         return false;
                     }
-                    restoreAlarmLocked(alarm, mInjector.getElapsedRealtime());
                     return adjustIdleUntilTime(alarm);
                 });
             }
@@ -2834,15 +2809,14 @@
                 mPendingBackgroundAlarms.removeAt(i);
             }
         }
-        // If we're currently keying off of this app's alarms for doze transitions,
-        // make sure to reset to other triggers.
+        // If we're currently using this app's alarms to come out of doze,
+        // make sure to reset to any remaining WAKE_FROM_IDLE alarms.
         if (mNextWakeFromIdle != null && mNextWakeFromIdle.uid == uid) {
-            mNextWakeFromIdle = null;
-            mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+            mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+            mAlarmStore.updateAlarmDeliveries(alarm -> {
                 if (alarm != mPendingIdleUntil) {
                     return false;
                 }
-                restoreAlarmLocked(alarm, mInjector.getElapsedRealtime());
                 return adjustIdleUntilTime(alarm);
             });
         }
@@ -2906,15 +2880,14 @@
                 mPendingBackgroundAlarms.removeAt(i);
             }
         }
-        // If we're currently keying off of this app's alarms for doze transitions,
-        // make sure to reset to other triggers.
+        // If we're currently using this app's alarms to come out of doze,
+        // make sure to reset to any remaining WAKE_FROM_IDLE alarms.
         if (removedNextWakeFromIdle.value) {
-            mNextWakeFromIdle = null;
-            mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+            mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+            mAlarmStore.updateAlarmDeliveries(alarm -> {
                 if (alarm != mPendingIdleUntil) {
                     return false;
                 }
-                restoreAlarmLocked(alarm, mInjector.getElapsedRealtime());
                 return adjustIdleUntilTime(alarm);
             });
         }
@@ -3071,20 +3044,6 @@
         }
     }
 
-    private static final String labelForType(int type) {
-        switch (type) {
-            case RTC:
-                return "RTC";
-            case RTC_WAKEUP:
-                return "RTC_WAKEUP";
-            case ELAPSED_REALTIME:
-                return "ELAPSED";
-            case ELAPSED_REALTIME_WAKEUP:
-                return "ELAPSED_WAKEUP";
-        }
-        return "--unknown--";
-    }
-
     private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
             String prefix, long nowELAPSED, SimpleDateFormat sdf) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, prefix, prefix);
@@ -3095,7 +3054,7 @@
             long nowELAPSED, SimpleDateFormat sdf) {
         for (int i = list.size() - 1; i >= 0; i--) {
             final Alarm a = list.get(i);
-            final String label = labelForType(a.type);
+            final String label = Alarm.typeToString(a.type);
             ipw.print(label);
             ipw.print(" #");
             ipw.print(i);
@@ -3125,6 +3084,9 @@
         }
         final String sourcePackage = alarm.sourcePackage;
         final int sourceUid = alarm.creatorUid;
+        if (UserHandle.isCore(sourceUid)) {
+            return false;
+        }
         return (mAppStateTracker != null) &&
                 mAppStateTracker.areAlarmsRestricted(sourceUid, sourcePackage,
                         exemptOnBatterySaver);
@@ -3169,11 +3131,7 @@
                     // Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
                     // alarm went off for this app.  Reschedule the alarm to be in the
                     // correct time period.
-                    alarm.expectedWhenElapsed = alarm.whenElapsed = minTime;
-                    if (alarm.maxWhenElapsed < minTime) {
-                        alarm.maxWhenElapsed = minTime;
-                    }
-                    alarm.expectedMaxWhenElapsed = alarm.maxWhenElapsed;
+                    alarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, minTime);
                     if (RECORD_DEVICE_IDLE_ALARMS) {
                         IdleDispatchEntry ent = new IdleDispatchEntry();
                         ent.uid = alarm.uid;
@@ -3213,12 +3171,11 @@
                 restorePendingWhileIdleAlarmsLocked();
             }
             if (mNextWakeFromIdle == alarm) {
-                mNextWakeFromIdle = null;
-                mAlarmStore.recalculateAlarmDeliveries(a -> {
+                mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+                mAlarmStore.updateAlarmDeliveries(a -> {
                     if (a != mPendingIdleUntil) {
                         return false;
                     }
-                    restoreAlarmLocked(a, nowELAPSED);
                     return adjustIdleUntilTime(a);
                 });
             }
@@ -3228,14 +3185,17 @@
             if (alarm.repeatInterval > 0) {
                 // this adjustment will be zero if we're late by
                 // less than one full repeat interval
-                alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
+                alarm.count += (nowELAPSED - alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX))
+                        / alarm.repeatInterval;
                 // Also schedule its next recurrence
                 final long delta = alarm.count * alarm.repeatInterval;
-                final long nextElapsed = alarm.expectedWhenElapsed + delta;
-                setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
-                        maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
-                        alarm.repeatInterval, alarm.operation, null, null, alarm.flags,
-                        alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
+                final long nextElapsed = alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX) + delta;
+                final long nextMaxElapsed = maxTriggerTime(nowELAPSED, nextElapsed,
+                        alarm.repeatInterval);
+                setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
+                        nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
+                        null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
+                        alarm.packageName);
             }
 
             if (alarm.wakeup) {
@@ -3277,7 +3237,7 @@
         }
     }
 
-    static int fuzzForDuration(long duration) {
+    int fuzzForDuration(long duration) {
         if (duration < 15 * 60 * 1000) {
             // If the duration until the time is less than 15 minutes, the maximum fuzz
             // is the duration.
@@ -3480,7 +3440,7 @@
                         FrameworkStatsLog.write(FrameworkStatsLog.WALL_CLOCK_TIME_SHIFTED, nowRTC);
                         removeImpl(null, mTimeTickTrigger);
                         removeImpl(mDateChangeSender, null);
-                        reevaluateRtcAlarms(nowELAPSED);
+                        reevaluateRtcAlarms();
                         mClockReceiver.scheduleTimeTickEvent();
                         mClockReceiver.scheduleDateChangedEvent();
                         synchronized (mLock) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
index 9fdbb8b..7a846b9 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
@@ -48,6 +48,15 @@
     ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms);
 
     /**
+     * Gets the earliest alarm with the flag {@link android.app.AlarmManager#FLAG_WAKE_FROM_IDLE}
+     * based on {@link Alarm#getWhenElapsed()}.
+     *
+     * @return An alarm object matching the description above or {@code null} if no such alarm was
+     *         found.
+     */
+    Alarm getNextWakeFromIdleAlarm();
+
+    /**
      * Returns the total number of alarms in this store.
      */
     int size();
@@ -71,7 +80,7 @@
     /**
      * Removes all alarms that are pending delivery at the given time.
      *
-     * @param nowElapsed    The time at which delivery eligibility is evaluated.
+     * @param nowElapsed The time at which delivery eligibility is evaluated.
      * @return The list of alarms pending at the given time.
      */
     ArrayList<Alarm> removePendingAlarms(long nowElapsed);
@@ -82,7 +91,7 @@
      *
      * @return {@code true} if any of the alarm deliveries changed due to this call.
      */
-    boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator);
+    boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator);
 
     /**
      * Returns all the alarms in the form of a list.
@@ -97,6 +106,7 @@
      * Primary useful for debugging. Can be called from the
      * {@link android.os.Binder#dump(FileDescriptor PrintWriter, String[]) dump} method of the
      * caller.
+     *
      * @param ipw        The {@link IndentingPrintWriter} to write to.
      * @param nowElapsed the time when the dump is requested in the
      *                   {@link SystemClock#elapsedRealtime()
@@ -112,7 +122,7 @@
 
     /**
      * A functional interface used to update the alarm. Used to describe the update in
-     * {@link #recalculateAlarmDeliveries(AlarmDeliveryCalculator)}
+     * {@link #updateAlarmDeliveries(AlarmDeliveryCalculator)}
      */
     @FunctionalInterface
     interface AlarmDeliveryCalculator {
@@ -125,3 +135,4 @@
         boolean updateAlarmDelivery(Alarm a);
     }
 }
+
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
index 91c0c05..cbfe80b 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
@@ -66,8 +66,8 @@
     };
 
     private static final Comparator<Alarm> sIncreasingTimeOrder = (a1, a2) -> {
-        long when1 = a1.whenElapsed;
-        long when2 = a2.whenElapsed;
+        long when1 = a1.getWhenElapsed();
+        long when2 = a2.getWhenElapsed();
         if (when1 > when2) {
             return 1;
         }
@@ -99,11 +99,28 @@
         }
         if (!removed.isEmpty()) {
             mSize -= removed.size();
+            // Not needed if only whole batches were removed, but keeping existing behavior.
             rebatchAllAlarms();
         }
         return removed;
     }
 
+    @Override
+    public Alarm getNextWakeFromIdleAlarm() {
+        for (final Batch batch : mAlarmBatches) {
+            if ((batch.mFlags & AlarmManager.FLAG_WAKE_FROM_IDLE) == 0) {
+                continue;
+            }
+            for (int i = 0; i < batch.size(); i++) {
+                final Alarm a = batch.get(i);
+                if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+                    return a;
+                }
+            }
+        }
+        return null;
+    }
+
     private void rebatchAllAlarms() {
         final long start = mStatLogger.getTime();
         final ArrayList<Batch> oldBatches = (ArrayList<Batch>) mAlarmBatches.clone();
@@ -157,7 +174,7 @@
     }
 
     @Override
-    public boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
+    public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
         boolean changed = false;
         for (final Batch b : mAlarmBatches) {
             for (int i = 0; i < b.size(); i++) {
@@ -204,7 +221,7 @@
 
     private void insertAndBatchAlarm(Alarm alarm) {
         final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
-                : attemptCoalesce(alarm.whenElapsed, alarm.maxWhenElapsed);
+                : attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed());
 
         if (whichBatch < 0) {
             addBatch(mAlarmBatches, new Batch(alarm));
@@ -247,8 +264,8 @@
         final ArrayList<Alarm> mAlarms = new ArrayList<>();
 
         Batch(Alarm seed) {
-            mStart = seed.whenElapsed;
-            mEnd = clampPositive(seed.maxWhenElapsed);
+            mStart = seed.getWhenElapsed();
+            mEnd = clampPositive(seed.getMaxWhenElapsed());
             mFlags = seed.flags;
             mAlarms.add(seed);
         }
@@ -276,12 +293,12 @@
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "Adding " + alarm + " to " + this);
             }
-            if (alarm.whenElapsed > mStart) {
-                mStart = alarm.whenElapsed;
+            if (alarm.getWhenElapsed() > mStart) {
+                mStart = alarm.getWhenElapsed();
                 newStart = true;
             }
-            if (alarm.maxWhenElapsed < mEnd) {
-                mEnd = alarm.maxWhenElapsed;
+            if (alarm.getMaxWhenElapsed() < mEnd) {
+                mEnd = alarm.getMaxWhenElapsed();
             }
             mFlags |= alarm.flags;
 
@@ -309,11 +326,11 @@
                         Slog.wtf(TAG, "Removed TIME_TICK alarm");
                     }
                 } else {
-                    if (alarm.whenElapsed > newStart) {
-                        newStart = alarm.whenElapsed;
+                    if (alarm.getWhenElapsed() > newStart) {
+                        newStart = alarm.getWhenElapsed();
                     }
-                    if (alarm.maxWhenElapsed < newEnd) {
-                        newEnd = alarm.maxWhenElapsed;
+                    if (alarm.getMaxWhenElapsed() < newEnd) {
+                        newEnd = alarm.getMaxWhenElapsed();
                     }
                     newFlags |= alarm.flags;
                     i++;
diff --git a/api/Android.bp b/api/Android.bp
index f0218b8..388bb68 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -48,6 +48,7 @@
     name: "frameworks-base-api-current-merged.txt",
     srcs: [
         ":conscrypt.module.public.api{.public.api.txt}",
+        ":framework-appsearch{.public.api.txt}",
         ":framework-graphics{.public.api.txt}",
         ":framework-media{.public.api.txt}",
         ":framework-mediaprovider{.public.api.txt}",
@@ -61,12 +62,19 @@
     out: ["current.txt"],
     tools: ["metalava"],
     cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+    dist: {
+        targets: ["sdk", "win_sdk"],
+        dir: "apistubs/android/public/api",
+        dest: "android.txt",
+    },
 }
 
 genrule {
     name: "frameworks-base-api-removed-merged.txt",
     srcs: [
         ":conscrypt.module.public.api{.public.removed-api.txt}",
+        ":framework-appsearch{.public.removed-api.txt}",
+        ":framework-graphics{.public.removed-api.txt}",
         ":framework-media{.public.removed-api.txt}",
         ":framework-mediaprovider{.public.removed-api.txt}",
         ":framework-permission{.public.removed-api.txt}",
@@ -84,6 +92,7 @@
 genrule {
     name: "frameworks-base-api-system-current-merged.txt",
     srcs: [
+        ":framework-appsearch{.system.api.txt}",
         ":framework-graphics{.system.api.txt}",
         ":framework-media{.system.api.txt}",
         ":framework-mediaprovider{.system.api.txt}",
@@ -97,11 +106,18 @@
     out: ["system-current.txt"],
     tools: ["metalava"],
     cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+    dist: {
+        targets: ["sdk", "win_sdk"],
+        dir: "apistubs/android/system/api",
+        dest: "android.txt",
+    },
 }
 
 genrule {
     name: "frameworks-base-api-system-removed-merged.txt",
     srcs: [
+        ":framework-appsearch{.system.removed-api.txt}",
+        ":framework-graphics{.system.removed-api.txt}",
         ":framework-media{.system.removed-api.txt}",
         ":framework-mediaprovider{.system.removed-api.txt}",
         ":framework-permission{.system.removed-api.txt}",
@@ -119,6 +135,7 @@
 genrule {
     name: "frameworks-base-api-module-lib-current-merged.txt",
     srcs: [
+        ":framework-appsearch{.module-lib.api.txt}",
         ":framework-graphics{.module-lib.api.txt}",
         ":framework-media{.module-lib.api.txt}",
         ":framework-mediaprovider{.module-lib.api.txt}",
@@ -132,11 +149,18 @@
     out: ["module-lib-current.txt"],
     tools: ["metalava"],
     cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+    dist: {
+        targets: ["sdk", "win_sdk"],
+        dir: "apistubs/android/module-lib/api",
+        dest: "android.txt",
+    },
 }
 
 genrule {
     name: "frameworks-base-api-module-lib-removed-merged.txt",
     srcs: [
+        ":framework-appsearch{.module-lib.removed-api.txt}",
+        ":framework-graphics{.module-lib.removed-api.txt}",
         ":framework-media{.module-lib.removed-api.txt}",
         ":framework-mediaprovider{.module-lib.removed-api.txt}",
         ":framework-permission{.module-lib.removed-api.txt}",
diff --git a/api/current.txt b/api/current.txt
index 83df1d9..ac6970a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2889,6 +2889,7 @@
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
+    field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b
     field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
     field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
     field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
@@ -2897,11 +2898,13 @@
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
     field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
+    field public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44; // 0x2c
     field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
     field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
     field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20
     field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
     field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
+    field public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45; // 0x2d
     field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26
     field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a
     field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
@@ -10664,12 +10667,15 @@
     field public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
     field public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
     field public static final String ACTION_PACKAGE_FIRST_LAUNCH = "android.intent.action.PACKAGE_FIRST_LAUNCH";
+    field public static final String ACTION_PACKAGE_FULLY_LOADED = "android.intent.action.PACKAGE_FULLY_LOADED";
     field public static final String ACTION_PACKAGE_FULLY_REMOVED = "android.intent.action.PACKAGE_FULLY_REMOVED";
     field @Deprecated public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL";
     field public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION";
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+    field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
+    field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -10834,6 +10840,7 @@
     field public static final String EXTRA_TIMEZONE = "time-zone";
     field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
     field public static final String EXTRA_UID = "android.intent.extra.UID";
+    field public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
     field public static final int FILL_IN_ACTION = 1; // 0x1
     field public static final int FILL_IN_CATEGORIES = 4; // 0x4
@@ -12293,6 +12300,9 @@
     field public static final int SYNCHRONOUS = 2; // 0x2
     field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
     field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
+    field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
+    field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
+    field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
@@ -25456,6 +25466,7 @@
 
   public static final class MediaCodec.CryptoInfo {
     ctor public MediaCodec.CryptoInfo();
+    method @NonNull public android.media.MediaCodec.CryptoInfo.Pattern getPattern();
     method public void set(int, @NonNull int[], @NonNull int[], @NonNull byte[], @NonNull byte[], int);
     method public void setPattern(android.media.MediaCodec.CryptoInfo.Pattern);
     field public byte[] iv;
@@ -26408,7 +26419,7 @@
     method public boolean containsKey(String);
     method public int describeContents();
     method public android.graphics.Bitmap getBitmap(String);
-    method @IntRange(from=0) public int getBitmapDimensionLimit();
+    method @IntRange(from=1) public int getBitmapDimensionLimit();
     method @NonNull public android.media.MediaDescription getDescription();
     method public long getLong(String);
     method public android.media.Rating getRating(String);
@@ -26458,7 +26469,7 @@
     method public android.media.MediaMetadata.Builder putRating(String, android.media.Rating);
     method public android.media.MediaMetadata.Builder putString(String, String);
     method public android.media.MediaMetadata.Builder putText(String, CharSequence);
-    method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(int);
+    method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(@IntRange(from=1) int);
   }
 
   @Deprecated public abstract class MediaMetadataEditor {
@@ -44100,11 +44111,13 @@
     method public long getLastAudiblyAlertedMillis();
     method public String getOverrideGroupKey();
     method public int getRank();
+    method @Nullable public android.content.pm.ShortcutInfo getShortcutInfo();
     method @NonNull public java.util.List<android.app.Notification.Action> getSmartActions();
     method @NonNull public java.util.List<java.lang.CharSequence> getSmartReplies();
     method public int getSuppressedVisualEffects();
     method public int getUserSentiment();
     method public boolean isAmbient();
+    method public boolean isConversation();
     method public boolean isSuspended();
     method public boolean matchesInterruptionFilter();
     field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
@@ -48440,6 +48453,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String[] getForbiddenPlmns();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getGroupIdLevel1();
     method public String getIccAuthentication(int, int, String);
@@ -48464,7 +48478,7 @@
     method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
-    method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
     method @Nullable public android.telephony.SignalStrength getSignalStrength();
     method public int getSimCarrierId();
     method @Nullable public CharSequence getSimCarrierIdName();
@@ -48487,7 +48501,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType();
-    method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
+    method @Nullable public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
     method public boolean hasCarrierPrivileges();
     method public boolean hasIccCard();
     method @Deprecated public boolean iccCloseLogicalChannel(int);
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 2d24d5b..b8b6687 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -1,10 +1,20 @@
 // Signature format: 2.0
 package android.app {
 
+  public class ActivityManager {
+    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
+    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
+  }
+
   public class AppOpsManager {
     field public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
   }
 
+  public abstract class HomeVisibilityListener {
+    ctor public HomeVisibilityListener();
+    method public abstract void onHomeVisibilityChanged(boolean);
+  }
+
   public class NotificationManager {
     method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
     field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
@@ -53,6 +63,10 @@
     field public static final int FLAG_FROM_KEY = 4096; // 0x1000
   }
 
+  public class MediaMetadataRetriever implements java.lang.AutoCloseable {
+    field public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40; // 0x28
+  }
+
   @Deprecated public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable {
     ctor @Deprecated public MediaParceledListSlice(@NonNull java.util.List<T>);
     method @Deprecated public int describeContents();
diff --git a/api/system-current.txt b/api/system-current.txt
index 5fa0f93..daf712c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5039,6 +5039,7 @@
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+    method public int linkFrontendToCiCam(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
     method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
     method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
@@ -5052,9 +5053,13 @@
     method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
     method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
     method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
+    method public int unlinkFrontendToCiCam(int);
     method public void updateResourcePriority(int, int);
     field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
     field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
+    field public static final long INVALID_FILTER_ID_64BIT = -1L; // 0xffffffffffffffffL
+    field public static final int INVALID_LTS_ID = -1; // 0xffffffff
+    field public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM = -1; // 0xffffffff
     field public static final int INVALID_STREAM_ID = 65535; // 0xffff
     field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
     field public static final int INVALID_TS_PID = 65535; // 0xffff
@@ -5074,6 +5079,13 @@
     method public void onResourceLost(@NonNull android.media.tv.tuner.Tuner);
   }
 
+  public final class TunerVersionChecker {
+    method public static int getTunerVersion();
+    field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
+    field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
+    field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
+  }
+
 }
 
 package android.media.tv.tuner.dvr {
@@ -5207,6 +5219,7 @@
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
     method public int flush();
     method public int getId();
+    method public long getId64Bit();
     method public int read(@NonNull byte[], long, long);
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
     method public int start();
@@ -5258,16 +5271,19 @@
     method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
     method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
     method public int getDstPort();
+    method public int getIpFilterContextId();
     method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
     method public int getSrcPort();
     method public int getType();
     method public boolean isPassthrough();
+    field public static final int INVALID_IP_FILTER_CONTEXT_ID = -1; // 0xffffffff
   }
 
   public static final class IpFilterConfiguration.Builder {
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration build();
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstIpAddress(@NonNull byte[]);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstPort(int);
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setIpFilterContextId(int);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setPassthrough(boolean);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSettings(@Nullable android.media.tv.tuner.filter.Settings);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSrcIpAddress(@NonNull byte[]);
@@ -5307,6 +5323,8 @@
 
   public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
+    method public int getMpuSequenceNumber();
+    method public long getPts();
     method public int getScHevcIndexMask();
   }
 
@@ -5469,6 +5487,7 @@
   public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
     method public int getPacketId();
+    method public long getPts();
     method public int getScIndexMask();
     method public int getTsIndexMask();
   }
@@ -11030,6 +11049,25 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
@@ -11523,7 +11561,6 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDeviceSoftwareVersion(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 7195144..773ecd03 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -159,8 +159,6 @@
     
 MissingNullability: android.telephony.ModemActivityInfo#writeToParcel(android.os.Parcel, int) parameter #0:
     
-MissingNullability: android.telephony.ModemActivityInfo.TransmitPower#toString():
-    
 MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
     
 MissingNullability: android.telephony.SmsCbCmasInfo#toString():
diff --git a/api/test-current.txt b/api/test-current.txt
index 98ca665..6c3df28 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -80,20 +80,22 @@
   }
 
   public class ActivityManager {
+    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
     method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String);
     method public long getTotalRam();
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
-    method @RequiresPermission("android.permission.INJECT_EVENTS") public void holdLock(int);
+    method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public void holdLock(int);
     method public static boolean isHighEndGfx();
     method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
     method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
     method public static void resumeAppSwitches() throws android.os.RemoteException;
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
-    method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle);
     field public static final int PROCESS_CAPABILITY_ALL = 7; // 0x7
     field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
     field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
@@ -202,12 +204,12 @@
   public class AppOpsManager {
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory();
-    method @Nullable @RequiresPermission("android.permission.GET_APP_OPS_STATS") public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
-    method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
+    method @Nullable @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
+    method @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
     method public static int getNumOps();
     method public static String[] getOpStrs();
-    method @NonNull @RequiresPermission("android.permission.GET_APP_OPS_STATS") public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
+    method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
     method public boolean isOperationActive(int, int, String);
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void offsetHistory(long);
     method public static int opToDefaultMode(@NonNull String);
@@ -453,10 +455,15 @@
   }
 
   public class DreamManager {
-    method @RequiresPermission("android.permission.READ_DREAM_STATE") public boolean isDreaming();
-    method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void setActiveDream(@NonNull android.content.ComponentName);
-    method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void startDream(@NonNull android.content.ComponentName);
-    method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void stopDream();
+    method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isDreaming();
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@NonNull android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@NonNull android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
+  }
+
+  public abstract class HomeVisibilityListener {
+    ctor public HomeVisibilityListener();
+    method public abstract void onHomeVisibilityChanged(boolean);
   }
 
   public final class NotificationChannel implements android.os.Parcelable {
@@ -550,14 +557,14 @@
   }
 
   public class UiModeManager {
-    method @RequiresPermission("android.permission.ENTER_CAR_MODE_PRIORITIZED") public void enableCarMode(@IntRange(from=0) int, int);
+    method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int);
     method public boolean isNightModeLocked();
     method public boolean isUiModeLocked();
   }
 
   public class WallpaperManager {
     method @Nullable public android.graphics.Bitmap getBitmap();
-    method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
     method public boolean shouldEnableWideColorGamut();
     method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
   }
@@ -633,11 +640,11 @@
 package android.app.backup {
 
   public class BackupManager {
-    method @RequiresPermission("android.permission.BACKUP") public android.content.Intent getConfigurationIntent(String);
-    method @RequiresPermission("android.permission.BACKUP") public android.content.Intent getDataManagementIntent(String);
-    method @Nullable @RequiresPermission("android.permission.BACKUP") public CharSequence getDataManagementIntentLabel(@NonNull String);
-    method @Deprecated @Nullable @RequiresPermission("android.permission.BACKUP") public String getDataManagementLabel(@NonNull String);
-    method @RequiresPermission("android.permission.BACKUP") public String getDestinationString(String);
+    method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getConfigurationIntent(String);
+    method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getDataManagementIntent(String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public CharSequence getDataManagementIntentLabel(@NonNull String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public String getDataManagementLabel(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.BACKUP) public String getDestinationString(String);
   }
 
 }
@@ -762,21 +769,21 @@
   }
 
   public class RoleControllerManager {
-    method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
   }
 
   public final class RoleManager {
-    method @RequiresPermission("android.permission.OBSERVE_ROLE_HOLDERS") public void addOnRoleHoldersChangedListenerAsUser(@NonNull java.util.concurrent.Executor, @NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
-    method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void addOnRoleHoldersChangedListenerAsUser(@NonNull java.util.concurrent.Executor, @NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
-    method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
-    method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
-    method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
     method @Nullable public String getSmsRoleHolder(int);
-    method @RequiresPermission("android.permission.OBSERVE_ROLE_HOLDERS") public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
-    method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
     method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
     field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
@@ -883,7 +890,7 @@
     method public int getUserId();
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
     method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
-    method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
     field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
     field public static final String BUGREPORT_SERVICE = "bugreport";
     field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
@@ -902,7 +909,7 @@
   }
 
   public class Intent implements java.lang.Cloneable android.os.Parcelable {
-    field @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
+    field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
     field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
     field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
@@ -1002,7 +1009,7 @@
   public static class PackageInstaller.SessionParams implements android.os.Parcelable {
     method public void setEnableRollback(boolean);
     method public void setEnableRollback(boolean, int);
-    method @RequiresPermission("android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS") public void setGrantedRuntimePermissions(String[]);
+    method @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
     method public void setInstallAsInstantApp(boolean);
     method public void setInstallerPackageName(@Nullable String);
@@ -1014,25 +1021,25 @@
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract boolean arePermissionsIndividuallyControlled();
     method @Nullable public String getContentCaptureServicePackageName();
-    method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int);
     method @Nullable public String getDefaultTextClassifierPackageName();
     method @Nullable public String getIncidentReportApproverPackageName();
     method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
-    method @NonNull @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
     method @Nullable public abstract String[] getNamesForUids(int[]);
     method @NonNull public abstract String getPermissionControllerPackageName();
-    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+    method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
     method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
     method @Nullable public String getSystemTextClassifierPackageName();
     method @Nullable public String getWellbeingPackageName();
-    method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
-    method @RequiresPermission("android.permission.INJECT_EVENTS") public void holdLock(int);
+    method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public void holdLock(int);
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
-    method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull String);
-    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS"}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, int, int, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull String);
+    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
@@ -1230,20 +1237,19 @@
 package android.hardware.biometrics {
 
   public class BiometricManager {
-    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession getTestSession();
+    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
   }
 
   public class BiometricTestSession implements java.lang.AutoCloseable {
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void authenticateReject(int, int);
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void authenticateSuccess(int, int);
+    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void acceptAuthentication(int);
+    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void cleanupInternalState(int);
     method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void close();
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void enableTestHal(int, boolean);
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void enrollFinish(int, int);
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void enrollStart(int, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void internalCleanup(int, int);
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyAcquired(int, int);
-    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyError(int, int);
+    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void finishEnroll(int);
+    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyAcquired(int);
+    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyError(int);
+    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void rejectAuthentication(int);
+    method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void startEnroll(int);
   }
 
   public class SensorProperties {
@@ -1346,7 +1352,7 @@
   }
 
   public final class DisplayManager {
-    method @RequiresPermission("android.permission.ACCESS_AMBIENT_LIGHT_STATS") public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
     method @RequiresPermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
     method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
@@ -1364,7 +1370,8 @@
 package android.hardware.fingerprint {
 
   @Deprecated public class FingerprintManager {
-    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession getTestSession();
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
   }
 
 }
@@ -1373,7 +1380,7 @@
 
   public final class HdmiControlManager {
     method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
-    method @RequiresPermission("android.permission.HDMI_CEC") public void setStandbyMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
     field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
     field public static final int AVR_VOLUME_MUTED = 101; // 0x65
     field public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 162; // 0xa2
@@ -1484,11 +1491,9 @@
     field public static final int PORT_OUTPUT = 1; // 0x1
   }
 
-  public class HdmiSwitchClient {
+  public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient {
     method public int getDeviceType();
     method @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo();
-    method public void sendKeyEvent(int, boolean);
-    method public void sendVendorCommand(int, byte[], boolean);
   }
 
 }
@@ -1759,7 +1764,7 @@
     method @NonNull public String[] getBackgroundThrottlingWhitelist();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull android.location.LocationRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.location.Location>);
     method @NonNull public String[] getIgnoreSettingsWhitelist();
-    method @Deprecated @Nullable @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public java.util.List<java.lang.String> getProviderPackages(@NonNull String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public java.util.List<java.lang.String> getProviderPackages(@NonNull String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
@@ -1780,7 +1785,7 @@
   }
 
   public static final class LocationRequest.Builder {
-    method @NonNull @RequiresPermission("android.permission.UPDATE_APP_OPS_STATS") public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public android.location.LocationRequest.Builder setLowPower(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
@@ -1815,12 +1820,12 @@
   }
 
   public class AudioManager {
-    method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method public boolean hasRegisteredDynamicPolicy();
-    method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
-    method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
-    method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
-    method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
     field public static final int SUCCESS = 0; // 0x0
   }
 
@@ -2135,6 +2140,21 @@
 
 }
 
+package android.media.tv.tuner {
+
+  public final class TunerVersionChecker {
+    method public static int getMajorVersion(int);
+    method public static int getMinorVersion(int);
+    method public static int getTunerVersion();
+    method public static boolean isHigherOrEqualVersionTo(int);
+    method public static boolean supportTunerVersion(int);
+    field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
+    field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
+    field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
+  }
+
+}
+
 package android.metrics {
 
   public class LogMaker {
@@ -2313,15 +2333,15 @@
     method @NonNull public android.net.NetworkCapabilities build();
     method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
     method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
-    method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
     method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
     method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int);
     method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier);
-    method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setOwnerUid(int);
-    method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
-    method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setRequestorUid(int);
-    method @NonNull @RequiresPermission("android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP") public android.net.NetworkCapabilities.Builder setSignalStrength(int);
-    method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
     method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
   }
 
@@ -2399,11 +2419,11 @@
 
   public class TetheringManager {
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
-    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
-    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
-    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
-    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
-    method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
     field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
     field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
     field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
@@ -2468,9 +2488,9 @@
   public static class TetheringManager.TetheringRequest.Builder {
     ctor public TetheringManager.TetheringRequest.Builder(int);
     method @NonNull public android.net.TetheringManager.TetheringRequest build();
-    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
-    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
-    method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
   }
 
   public class TrafficStats {
@@ -2672,7 +2692,7 @@
 package android.os {
 
   public class BatteryManager {
-    method @RequiresPermission("android.permission.POWER_SAVER") public boolean setChargingStateUpdateDelayMillis(int);
+    method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
   }
 
   public final class BugreportManager {
@@ -2716,7 +2736,7 @@
   }
 
   public class DeviceIdleManager {
-    method @RequiresPermission("android.permission.DEVICE_POWER") public void endIdle(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void endIdle(@NonNull String);
     method @NonNull public String[] getSystemPowerWhitelist();
     method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
   }
@@ -2969,21 +2989,21 @@
   }
 
   public final class PowerManager {
-    method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveModeTrigger();
-    method @RequiresPermission("android.permission.DEVICE_POWER") public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
-    method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSaveHint(boolean, int);
-    method @RequiresPermission(anyOf={"android.permission.DEVICE_POWER", "android.permission.POWER_SAVER"}) public boolean setPowerSaveModeEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public int getPowerSaveModeTrigger();
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
+    method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
     field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
     field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
     field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
   }
 
   public class PowerWhitelistManager {
-    method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull String);
-    method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
-    method @RequiresPermission("android.permission.DEVICE_POWER") public void removeFromWhitelist(@NonNull String);
-    method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public void whitelistAppTemporarily(@NonNull String, long);
-    method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
     field public static final int EVENT_MMS = 2; // 0x2
     field public static final int EVENT_SMS = 1; // 0x1
     field public static final int EVENT_UNSPECIFIED = 0; // 0x0
@@ -3049,8 +3069,8 @@
   }
 
   public class SystemConfigManager {
-    method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
-    method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
   }
 
   public class SystemProperties {
@@ -3079,7 +3099,7 @@
   }
 
   public class UserManager {
-    method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isSplitSystemUser();
     field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
   }
@@ -3139,10 +3159,10 @@
   }
 
   public abstract class Vibrator {
-    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
-    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
-    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public boolean isVibrating();
-    method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
   }
 
   public static interface Vibrator.OnVibratorStateChangedListener {
@@ -3241,12 +3261,12 @@
 
   public class DynamicSystemClient {
     ctor public DynamicSystemClient(@NonNull android.content.Context);
-    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void bind();
+    method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void bind();
     method public void setOnStatusChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
     method public void setOnStatusChangedListener(@NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
-    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long);
-    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long, long);
-    method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void unbind();
+    method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void start(@NonNull android.net.Uri, long);
+    method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void start(@NonNull android.net.Uri, long, long);
+    method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void unbind();
     field public static final int CAUSE_ERROR_EXCEPTION = 6; // 0x6
     field public static final int CAUSE_ERROR_INVALID_URL = 4; // 0x4
     field public static final int CAUSE_ERROR_IO = 3; // 0x3
@@ -3300,13 +3320,13 @@
 package android.permission {
 
   public final class PermissionControllerManager {
-    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler);
-    method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
-    method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
-    method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermission(@NonNull String, @NonNull String);
-    method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
-    method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
+    method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermission(@NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
     field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
     field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
     field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
@@ -3327,9 +3347,9 @@
   }
 
   public final class PermissionManager {
-    method @IntRange(from=0) @RequiresPermission(anyOf={"android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY", android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
+    method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
     method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions();
-    method @RequiresPermission(anyOf={"android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY", android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
   }
 
   public static final class PermissionManager.SplitPermissionInfo {
@@ -3395,14 +3415,14 @@
   }
 
   public final class DeviceConfig {
-    method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
-    method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
-    method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static float getFloat(@NonNull String, @NonNull String, float);
-    method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static int getInt(@NonNull String, @NonNull String, int);
-    method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static long getLong(@NonNull String, @NonNull String, long);
-    method @NonNull @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
-    method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getProperty(@NonNull String, @NonNull String);
-    method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getString(@NonNull String, @NonNull String, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
+    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
+    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
+    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
+    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
     method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
@@ -3475,6 +3495,7 @@
     field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
     field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
     field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
+    field public static final String HIDDEN_API_POLICY = "hidden_api_policy";
     field public static final String HIDE_ERROR_DIALOGS = "hide_error_dialogs";
     field public static final String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch";
     field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
@@ -3490,6 +3511,11 @@
   public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
     field public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled";
+    field public static final String ACCESSIBILITY_MAGNIFICATION_CAPABILITY = "accessibility_magnification_capability";
+    field public static final String ACCESSIBILITY_MAGNIFICATION_MODE = "accessibility_magnification_mode";
+    field public static final int ACCESSIBILITY_MAGNIFICATION_MODE_ALL = 3; // 0x3
+    field public static final int ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN = 1; // 0x1
+    field public static final int ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW = 2; // 0x2
     field public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service";
     field public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
     field public static final String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification";
@@ -4217,6 +4243,27 @@
     field public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    ctor public ModemActivityInfo(long, int, int, @NonNull int[], int);
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public boolean isValid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
@@ -4259,8 +4306,8 @@
     method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
     method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
-    field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
-    field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
   }
 
   public final class PreciseDataConnectionState implements android.os.Parcelable {
@@ -4301,10 +4348,10 @@
   public class TelephonyManager {
     method public int addDevicePolicyOverrideApn(@NonNull android.content.Context, @NonNull android.telephony.data.ApnSetting);
     method public int checkCarrierPrivilegesForPackage(String);
-    method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
+    method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
     method public int getCarrierIdListVersion();
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
-    method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getDefaultRespondViaMessageApplication();
+    method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getDefaultRespondViaMessageApplication();
     method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
@@ -4312,13 +4359,13 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
     method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
-    method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void resetOtaEmergencyNumberDbFilePath();
+    method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void resetOtaEmergencyNumberDbFilePath();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
     method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
-    method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
+    method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -5401,7 +5448,7 @@
   }
 
   public interface WindowManager extends android.view.ViewManager {
-    method @RequiresPermission("android.permission.INJECT_EVENTS") public default void holdLock(int);
+    method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public default void holdLock(int);
     method public default void setShouldShowIme(int, boolean);
     method public default void setShouldShowSystemDecors(int, boolean);
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
@@ -5422,11 +5469,11 @@
 
   public final class AccessibilityManager {
     method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, @Nullable android.os.Handler);
-    method @NonNull @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
-    method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void performAccessibilityShortcut();
-    method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
     method public void removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
-    method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void unregisterSystemAction(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
   }
 
   public static interface AccessibilityManager.AccessibilityServicesStateChangeListener {
@@ -5769,6 +5816,15 @@
     field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
   }
 
+  public final class TaskAppearedInfo implements android.os.Parcelable {
+    ctor public TaskAppearedInfo(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
+    method public int describeContents();
+    method @NonNull public android.view.SurfaceControl getLeash();
+    method @NonNull public android.app.ActivityManager.RunningTaskInfo getTaskInfo();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
+  }
+
   public class TaskOrganizer extends android.window.WindowOrganizer {
     ctor public TaskOrganizer();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.app.ActivityManager.RunningTaskInfo createRootTask(int, int);
@@ -5780,10 +5836,10 @@
     method @BinderThread public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
     method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void registerOrganizer();
+    method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setLaunchRoot(int, @NonNull android.window.WindowContainerToken);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void unregisterOrganizer();
+    method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void unregisterOrganizer();
   }
 
   public final class WindowContainerToken implements android.os.Parcelable {
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 91a09e3..0440d1a 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -2511,6 +2511,8 @@
     
 NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_BLACKLIST_EXEMPTIONS:
     
+NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_POLICY:
+    
 NoSettingsProvider: android.provider.Settings.Global#HIDE_ERROR_DIALOGS:
     
 NoSettingsProvider: android.provider.Settings.Global#LOCATION_GLOBAL_KILL_SWITCH:
@@ -2530,6 +2532,16 @@
 NoSettingsProvider: android.provider.Settings.Global#USE_OPEN_WIFI_PACKAGE:
     
 NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_ALL:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
     
 NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE:
     
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c99b39d..091e9a7 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -266,7 +266,7 @@
                 147 [(module) = "framework", (module) = "statsd"];
         BiometricSystemHealthIssueDetected biometric_system_health_issue_detected =
                 148 [(module) = "framework"];
-        BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"];
+        BubbleUIChanged bubble_ui_changed = 149 [(module) = "framework"];
         ScheduledJobConstraintChanged scheduled_job_constraint_changed =
                 150 [(module) = "framework"];
         BluetoothActiveDeviceChanged bluetooth_active_device_changed =
@@ -493,6 +493,8 @@
         WifiConnectionStateChanged wifi_connection_state_changed = 308 [(module) = "wifi"];
         HdmiCecActiveSourceChanged hdmi_cec_active_source_changed = 309 [(module) = "framework"];
         HdmiCecMessageReported hdmi_cec_message_reported = 310 [(module) = "framework"];
+        AirplaneMode airplane_mode = 311 [(module) = "telephony"];
+        ModemRestart modem_restart = 312 [(module) = "telephony"];
 
         // StatsdStats tracks platform atoms with ids upto 500.
         // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -3832,6 +3834,12 @@
     // App startup time (until call to Activity#reportFullyDrawn()).
     optional int64 app_startup_time_millis = 6;
 
+    // The compiler filter used when when the package was optimized.
+    optional int32 package_optimization_compilation_filter = 7;
+
+    // The reason why the package was optimized.
+    optional int32 package_optimization_compilation_reason = 8;
+
     enum SourceType {
         UNAVAILABLE = 0;
         LAUNCHER = 1;
@@ -3839,11 +3847,11 @@
         LOCKSCREEN = 3;
     }
     // The type of the startup source.
-    optional SourceType source_type = 7;
+    optional SourceType source_type = 9;
 
     // The time from the startup source to the beginning of handling the startup event.
     // -1 means not available.
-    optional int32 source_event_delay_millis = 8;
+    optional int32 source_event_delay_millis = 10;
 }
 
 /**
@@ -5267,12 +5275,23 @@
  * Event to track Jank for various system interactions.
  *
  * Logged from:
- *  frameworks/base/core/java/android/os/aot/FrameTracker.java
+ *  frameworks/base/core/java/com/android/internal/jank/FrameTracker.java
  */
 message UIInteractionFrameInfoReported {
     enum InteractionType {
         UNKNOWN = 0;
         NOTIFICATION_SHADE_SWIPE = 1;
+        SHADE_EXPAND_COLLAPSE_LOCK = 2;
+        SHADE_SCROLL_FLING = 3;
+        SHADE_ROW_EXPAND = 4;
+        SHADE_ROW_SWIPE = 5;
+        SHADE_QS_EXPAND_COLLAPSE = 6;
+        SHADE_QS_SCROLL_SWIPE = 7;
+        LAUNCHER_APP_LAUNCH_FROM_RECENTS = 8;
+        LAUNCHER_APP_LAUNCH_FROM_ICON = 9;
+        LAUNCHER_APP_CLOSE_TO_HOME = 10;
+        LAUNCHER_APP_CLOSE_TO_PIP = 11;
+        LAUNCHER_QUICK_SWITCH = 12;
     }
 
     optional InteractionType interaction_type = 1;
@@ -10576,12 +10595,48 @@
 }
 
 /**
+ * Push information about usage of airplane mode.
+ *
+ * Logged from:
+ *   frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/AirplaneModeStats.java
+ */
+message AirplaneMode {
+    // Status of airplane mode
+    optional bool is_enabled = 1;
+
+    // When is_enabled is false, indicates if this was a very short airplane mode toggle
+    // (i.e. airplane mode was disabled after less than 10 seconds from enablement).
+    optional bool short_toggle = 2;
+
+    // Carrier ID of the SIM card.
+    // See https://source.android.com/devices/tech/config/carrierid.
+    optional int32 carrier_id = 3;
+}
+
+/**
+ * Push information about modem restarts.
+ *
+ * Logged from:
+ *   frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/ModemRestartStats.java
+ */
+message ModemRestart {
+    // Software version of the modem, as provided by android.os.Build.getRadioVersion().
+    optional string baseband_version = 1;
+
+    // Reason of the modem restart, as provided in the modemReset indication of IRadio HAL.
+    optional string reason = 2;
+
+    // Carrier ID of the first SIM card.
+    // See https://source.android.com/devices/tech/config/carrierid.
+    optional int32 carrier_id = 3;
+}
+
+/**
  * Logs gnss stats from location service provider
  *
  * Pulled from:
  *  frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
  */
-
 message GnssStats {
     // Number of location reports since boot
     optional int64 location_reports = 1;
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 93795d4..d32f5a9 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -516,6 +516,21 @@
             return false;
         }
     }
+    for (int i = 0; i < config.duration_metric_size(); i++, metricIndex++) {
+        const DurationMetric& metric = config.duration_metric(i);
+        set<int64_t> conditionDependencies({metric.what()});
+        if (metric.has_condition()) {
+            conditionDependencies.insert(metric.condition());
+        }
+        if (!determineMetricUpdateStatus(
+                    config, metric, metric.id(), METRIC_TYPE_DURATION, /*matcherDependencies=*/{},
+                    conditionDependencies, metric.slice_by_state(), metric.links(),
+                    oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+                    replacedMatchers, replacedConditions, replacedStates,
+                    metricsToUpdate[metricIndex])) {
+            return false;
+        }
+    }
     for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
         const EventMetric& metric = config.event_metric(i);
         set<int64_t> conditionDependencies;
@@ -538,8 +553,7 @@
         if (metric.has_condition()) {
             conditionDependencies.insert(metric.condition());
         }
-        set<int64_t> matcherDependencies;
-        matcherDependencies.insert(metric.what());
+        set<int64_t> matcherDependencies({metric.what()});
         if (metric.has_trigger_event()) {
             matcherDependencies.insert(metric.trigger_event());
         }
@@ -552,7 +566,7 @@
             return false;
         }
     }
-    // TODO: determine update status for value, duration metrics.
+    // TODO: determine update status for value metrics.
     return true;
 }
 
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index 60705cf..a20be15 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -155,6 +155,20 @@
     return metric;
 }
 
+DurationMetric createDurationMetric(string name, int64_t what, optional<int64_t> condition,
+                                    vector<int64_t> states) {
+    DurationMetric metric;
+    metric.set_id(StringToId(name));
+    metric.set_what(what);
+    metric.set_bucket(TEN_MINUTES);
+    if (condition) {
+        metric.set_condition(condition.value());
+    }
+    for (const int64_t state : states) {
+        metric.add_slice_by_state(state);
+    }
+    return metric;
+}
 }  // anonymous namespace
 
 TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) {
@@ -1398,6 +1412,130 @@
     EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
 }
 
+TEST_F(ConfigUpdateTest, TestDurationMetricPreserve) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate what = CreateScreenIsOnPredicate();
+    *config.add_predicate() = what;
+    Predicate condition = CreateScreenIsOffPredicate();
+    *config.add_predicate() = condition;
+
+    State sliceState = CreateScreenState();
+    *config.add_state() = sliceState;
+
+    *config.add_duration_metric() =
+            createDurationMetric("DURATION1", what.id(), condition.id(), {sliceState.id()});
+    EXPECT_TRUE(initConfig(config));
+
+    unordered_map<int64_t, int> metricToActivationMap;
+    vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+    EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+                                                 metricToActivationMap,
+                                                 /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+                                                 /*replacedStates=*/{}, metricsToUpdate));
+    EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricDefinitionChange) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate what = CreateScreenIsOnPredicate();
+    *config.add_predicate() = what;
+
+    *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {});
+    EXPECT_TRUE(initConfig(config));
+
+    config.mutable_duration_metric(0)->set_aggregation_type(DurationMetric::MAX_SPARSE);
+
+    unordered_map<int64_t, int> metricToActivationMap;
+    vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+    EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+                                                 metricToActivationMap, /*replacedMatchers*/ {},
+                                                 /*replacedConditions=*/{}, /*replacedStates=*/{},
+                                                 metricsToUpdate));
+    EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricWhatChanged) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate what = CreateScreenIsOnPredicate();
+    *config.add_predicate() = what;
+
+    *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {});
+    EXPECT_TRUE(initConfig(config));
+
+    unordered_map<int64_t, int> metricToActivationMap;
+    vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+    EXPECT_TRUE(determineAllMetricUpdateStatuses(
+            config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+            /*replacedMatchers*/ {}, /*replacedConditions=*/{what.id()},
+            /*replacedStates=*/{}, metricsToUpdate));
+    EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricConditionChanged) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate what = CreateScreenIsOnPredicate();
+    *config.add_predicate() = what;
+    Predicate condition = CreateScreenIsOffPredicate();
+    *config.add_predicate() = condition;
+
+    *config.add_duration_metric() = createDurationMetric("DURATION", what.id(), condition.id(), {});
+    EXPECT_TRUE(initConfig(config));
+
+    unordered_map<int64_t, int> metricToActivationMap;
+    vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+    EXPECT_TRUE(determineAllMetricUpdateStatuses(
+            config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+            /*replacedMatchers*/ {}, /*replacedConditions=*/{condition.id()},
+            /*replacedStates=*/{}, metricsToUpdate));
+    EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricStateChange) {
+    StatsdConfig config;
+    AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = startMatcher;
+    AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+    *config.add_atom_matcher() = stopMatcher;
+
+    Predicate what = CreateScreenIsOnPredicate();
+    *config.add_predicate() = what;
+
+    State sliceState = CreateScreenState();
+    *config.add_state() = sliceState;
+
+    *config.add_duration_metric() =
+            createDurationMetric("DURATION1", what.id(), nullopt, {sliceState.id()});
+    EXPECT_TRUE(initConfig(config));
+
+    unordered_map<int64_t, int> metricToActivationMap;
+    vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+    EXPECT_TRUE(determineAllMetricUpdateStatuses(
+            config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+            /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+            /*replacedStates=*/{sliceState.id()}, metricsToUpdate));
+    EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
 TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) {
     StatsdConfig config;
 
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 25729ab..e3139eb 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -20,6 +20,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
@@ -28,11 +29,13 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
@@ -85,13 +88,16 @@
     /** @hide */
     @IntDef(prefix = { "GESTURE_" }, value = {
             GESTURE_2_FINGER_SINGLE_TAP,
+            GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_DOUBLE_TAP,
             GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_2_FINGER_TRIPLE_TAP,
             GESTURE_3_FINGER_SINGLE_TAP,
+            GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_DOUBLE_TAP,
             GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
             GESTURE_3_FINGER_TRIPLE_TAP,
+            GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD,
             GESTURE_DOUBLE_TAP,
             GESTURE_DOUBLE_TAP_AND_HOLD,
             GESTURE_SWIPE_UP,
@@ -180,15 +186,21 @@
     private static String eventTypeToString(int eventType) {
         switch (eventType) {
             case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
+            case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD:
+                return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD";
             case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
             case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
                 return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
             case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
+            case GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD:
+                return "GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD";
             case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
             case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
                 return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
             case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
+            case GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD:
+                return "GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD";
             case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
             case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
             case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index b5b0ce3..7c6d448 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -421,6 +421,15 @@
     /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
     public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
 
+    /** The user has performed a two-finger  single-tap and hold gesture on the touch screen. */
+    public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43;
+
+    /** The user has performed a three-finger  single-tap and hold gesture on the touch screen. */
+    public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44;
+
+    /** The user has performed a three-finger  triple-tap and hold gesture on the touch screen. */
+    public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45;
+
     /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
     public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e75d2f6..30efb48 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -100,6 +100,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.Executor;
 
 /**
  * <p>
@@ -4752,31 +4753,43 @@
     }
 
     /**
-     * Register with {@link HomeVisibilityObserver} with ActivityManager.
-     * TODO: b/144351078 expose as SystemApi
+     * Register to be notified when the visibility of the home screen changes.
+     *
+     * @param executor The executor on which the listener should be called.
+     * @param listener The listener that is called when home visibility changes.
      * @hide
      */
-    public void registerHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) {
-        Preconditions.checkNotNull(observer);
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+    public void addHomeVisibilityListener(@NonNull Executor executor,
+            @NonNull HomeVisibilityListener listener) {
+        Preconditions.checkNotNull(listener);
+        Preconditions.checkNotNull(executor);
         try {
-            observer.init(mContext, this);
-            getService().registerProcessObserver(observer.mObserver);
+            listener.init(mContext, executor, this);
+            getService().registerProcessObserver(listener.mObserver);
             // Notify upon first registration.
-            observer.onHomeVisibilityChanged(observer.mIsHomeActivityVisible);
+            executor.execute(() ->
+                    listener.onHomeVisibilityChanged(listener.mIsHomeActivityVisible));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Unregister with {@link HomeVisibilityObserver} with ActivityManager.
-     * TODO: b/144351078 expose as SystemApi
+     * Removes a listener that was previously added with {@link #addHomeVisibilityListener}.
+     *
+     * @param listener The listener that was previously added.
      * @hide
      */
-    public void unregisterHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) {
-        Preconditions.checkNotNull(observer);
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+    public void removeHomeVisibilityListener(@NonNull HomeVisibilityListener listener) {
+        Preconditions.checkNotNull(listener);
         try {
-            getService().unregisterProcessObserver(observer.mObserver);
+            getService().unregisterProcessObserver(listener.mObserver);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 155de36..c5bc356 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7911,7 +7911,7 @@
      *
      * @param op The op to note
      * @param proxiedUid The uid to note the op for {@code null}
-     * @param proxiedUid The package name the uid belongs to
+     * @param proxiedPackageName The package name the uid belongs to
      * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
      * attribution tag} or {@code null} for default attribution
      * @param message A message describing the reason the op was noted
@@ -7939,17 +7939,7 @@
      *Like {@link #startProxyOp(String, int, String, String, String)} but instead
      * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
      *
-     * @param op The op to note
-     * @param proxiedUid The uid to note the op for {@code null}
-     * @param proxiedUid The package name the uid belongs to
-     * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
-     * attribution tag} or {@code null} for default attribution
-     * @param message A message describing the reason the op was noted*
-     * <p>This API requires package with the {@code proxiedPackageName} to belong to
-     * {@code proxiedUid}.
-     *
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
-     * if it is not allowed and should be silently ignored (without causing the app to crash).
+     * @see #startProxyOp(String, int, String, String, String)
      */
     public int startProxyOpNoThrow(@NonNull String op, int proxiedUid,
             @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag,
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
new file mode 100644
index 0000000..c6e5699
--- /dev/null
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.app;
+
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.Binder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A listener that will be invoked when the visibility of the home screen changes.
+ * Register this callback via {@link ActivityManager#addHomeVisibilityListener}
+ * @hide
+ */
+// This is a single-method listener that needs a bunch of supporting code, so it can't be an
+// interface
+@SuppressLint("ListenerInterface")
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@TestApi
+public abstract class HomeVisibilityListener {
+    private Context mContext;
+    private ActivityManager mActivityManager;
+    private Executor mExecutor;
+    /** @hide */
+    android.app.IProcessObserver.Stub mObserver;
+    /** @hide */
+    boolean mIsHomeActivityVisible;
+
+    /** @hide */
+    void init(Context context, Executor executor, ActivityManager activityManager) {
+        mContext = context;
+        mActivityManager = activityManager;
+        mIsHomeActivityVisible = isHomeActivityVisible();
+        mExecutor = executor;
+    }
+
+    /**
+     * Called when the visibility of the home screen changes.
+     *
+     * @param isHomeActivityVisible Whether the home screen activity is now visible.
+     */
+    public abstract void onHomeVisibilityChanged(boolean isHomeActivityVisible);
+
+    public HomeVisibilityListener() {
+        mObserver = new android.app.IProcessObserver.Stub() {
+            @Override
+            public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
+                refreshHomeVisibility();
+            }
+
+            @Override
+            public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
+            }
+
+            @Override
+            public void onProcessDied(int pid, int uid) {
+                refreshHomeVisibility();
+            }
+
+            private void refreshHomeVisibility() {
+                boolean isHomeActivityVisible = isHomeActivityVisible();
+                if (mIsHomeActivityVisible != isHomeActivityVisible) {
+                    mIsHomeActivityVisible = isHomeActivityVisible;
+                    Binder.withCleanCallingIdentity(() ->
+                            mExecutor.execute(() ->
+                                    onHomeVisibilityChanged(mIsHomeActivityVisible)));
+                }
+            }
+        };
+    }
+
+    private boolean isHomeActivityVisible() {
+        List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
+        if (tasks == null || tasks.isEmpty()) {
+            return false;
+        }
+
+        String top = tasks.get(0).topActivity.getPackageName();
+        if (top == null) {
+            return false;
+        }
+
+        // We can assume that the screen is idle if the home application is in the foreground.
+        String defaultHomePackage = mContext.getPackageManager()
+                .getHomeActivities(new ArrayList<>()).getPackageName();
+        if (Objects.equals(top, defaultHomePackage)) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/core/java/android/app/HomeVisibilityObserver.java b/core/java/android/app/HomeVisibilityObserver.java
deleted file mode 100644
index 8422c6f..0000000
--- a/core/java/android/app/HomeVisibilityObserver.java
+++ /dev/null
@@ -1,97 +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 android.app;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-
-import java.util.List;
-
-/**
- * An observer / callback to create and register by
- * {@link ActivityManager#registerHomeVisibilityObserver} so that it's triggered when
- * visibility of home page changes.
- * TODO: b/144351078 expose as SystemApi
- * @hide
- */
-public abstract class HomeVisibilityObserver {
-    private Context mContext;
-    private ActivityManager mActivityManager;
-    /** @hide */
-    IProcessObserver.Stub mObserver;
-    /** @hide */
-    boolean mIsHomeActivityVisible;
-
-    /** @hide */
-    void init(Context context, ActivityManager activityManager) {
-        mContext = context;
-        mActivityManager = activityManager;
-        mIsHomeActivityVisible = isHomeActivityVisible();
-    }
-
-    /**
-     * The API that needs implemented and will be triggered when activity on home page changes.
-     */
-    public abstract void onHomeVisibilityChanged(boolean isHomeActivityVisible);
-
-    public HomeVisibilityObserver() {
-        mObserver = new IProcessObserver.Stub() {
-            @Override
-            public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
-                boolean isHomeActivityVisible = isHomeActivityVisible();
-                if (mIsHomeActivityVisible != isHomeActivityVisible) {
-                    mIsHomeActivityVisible = isHomeActivityVisible;
-                    onHomeVisibilityChanged(mIsHomeActivityVisible);
-                }
-            }
-
-            @Override
-            public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
-            }
-
-            @Override
-            public void onProcessDied(int pid, int uid) {
-            }
-        };
-    }
-
-    private boolean isHomeActivityVisible() {
-        List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
-        if (tasks == null || tasks.isEmpty()) {
-            return false;
-        }
-
-        String top = tasks.get(0).topActivity.getPackageName();
-        if (top == null) {
-            return false;
-        }
-
-        // We can assume that the screen is idle if the home application is in the foreground.
-        final Intent intent = new Intent(Intent.ACTION_MAIN, null);
-        intent.addCategory(Intent.CATEGORY_HOME);
-
-        ResolveInfo info = mContext.getPackageManager().resolveActivity(intent,
-                PackageManager.MATCH_DEFAULT_ONLY);
-        if (info != null && top.equals(info.activityInfo.packageName)) {
-            return true;
-        }
-
-        return false;
-    }
-}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 66007e5..7530229 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -165,7 +165,8 @@
     int getTaskForActivity(in IBinder token, in boolean onlyRoot);
     /** Finish all activities that were started for result from the specified activity. */
     void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
-    ParceledListSlice getRecentTasks(int maxNum, int flags, int userId);
+    ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+            int userId);
     boolean willActivityBeVisible(in IBinder token);
     void setRequestedOrientation(in IBinder token, int requestedOrientation);
     int getRequestedOrientation(in IBinder token);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0d682d6..5438062 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2240,7 +2240,8 @@
                 .setTicker(tickerText)
                 .setContentTitle(contentTitle)
                 .setContentText(contentText)
-                .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
+                .setContentIntent(PendingIntent.getActivity(
+                        context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
                 .buildInto(this);
     }
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 90401ad..b020c70 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -81,6 +81,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.IAuthService;
 import android.hardware.camera2.CameraManager;
+import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.face.FaceManager;
@@ -1348,6 +1349,12 @@
                             throws ServiceNotFoundException {
                         return new DreamManager(ctx);
                     }});
+        registerService(Context.DEVICE_STATE_SERVICE, DeviceStateManager.class,
+                new CachedServiceFetcher<DeviceStateManager>() {
+                    @Override
+                    public DeviceStateManager createService(ContextImpl ctx) {
+                        return new DeviceStateManager();
+                    }});
 
         sInitializing = true;
         try {
@@ -1404,6 +1411,7 @@
                 case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
                 case Context.APP_PREDICTION_SERVICE:
                 case Context.INCREMENTAL_SERVICE:
+                case Context.ETHERNET_SERVICE:
                     return null;
             }
             Slog.wtf(TAG, "Manager wrapper not available: " + name);
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 79f05a3..eedf958 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -852,15 +852,6 @@
     }
 
     /**
-     * Returns true if this container may be scaled without resizing, and windows within may need
-     * to be configured as such.
-     * @hide
-     */
-    public boolean windowsAreScaleable() {
-        return mWindowingMode == WINDOWING_MODE_PINNED;
-    }
-
-    /**
      * Returns true if windows in this container should be given move animations by default.
      * @hide
      */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9c216a3..c4157cf 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3508,6 +3508,7 @@
             PERMISSION_SERVICE,
             LIGHTS_SERVICE,
             //@hide: PEOPLE_SERVICE,
+            //@hide: DEVICE_STATE_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -5246,6 +5247,14 @@
     public static final String PEOPLE_SERVICE = "people";
 
     /**
+     * Use with {@link #getSystemService(String)} to access device state service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String DEVICE_STATE_SERVICE = "device_state";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2f1254f..a14e21b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2744,7 +2744,6 @@
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
-     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
@@ -2755,13 +2754,13 @@
      * <ul>
      * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. </li>
      * <li> {@link #EXTRA_PACKAGE_NAME} containing the package name. </li>
-     * <li> {@link #EXTRA_REASON} containing the integer indicating the reason for the state change,
+     * <li> {@link #EXTRA_UNSTARTABLE_REASON} containing the integer indicating the reason for
+     * the state change,
      * @see PackageManager.UnstartableReason
      * </li>
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
-     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_UNSTARTABLE =
@@ -2776,7 +2775,6 @@
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
-     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_FULLY_LOADED =
@@ -6015,6 +6013,13 @@
      */
     public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
 
+    /**
+     * Intent extra: the reason that the package associated with this intent has become unstartable.
+     *
+     * <p>Type: String
+     */
+    public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1a992f5..32ae105 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3790,8 +3790,8 @@
      * @hide
      */
     @IntDef({UNSTARTABLE_REASON_UNKNOWN,
-            UNSTARTABLE_REASON_DATALOADER_TRANSPORT,
-            UNSTARTABLE_REASON_DATALOADER_STORAGE
+            UNSTARTABLE_REASON_CONNECTION_ERROR,
+            UNSTARTABLE_REASON_INSUFFICIENT_STORAGE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UnstartableReason {}
@@ -3800,23 +3800,20 @@
      * Unstartable state with no root cause specified. E.g., data loader seeing missing pages but
      * unclear about the cause. This corresponds to a generic alert window shown to the user when
      * the user attempts to launch the app.
-     * @hide
      */
     public static final int UNSTARTABLE_REASON_UNKNOWN = 0;
 
     /**
-     * Unstartable state after hint from dataloader of issues with the transport layer.
-     * This corresponds to an alert window shown to the user indicating network errors.
-     * @hide
+     * Unstartable state due to connection issues that interrupt package loading.
+     * This corresponds to an alert window shown to the user indicating connection errors.
      */
-    public static final int UNSTARTABLE_REASON_DATALOADER_TRANSPORT = 1;
+    public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1;
 
     /**
      * Unstartable state after encountering storage limitations.
      * This corresponds to an alert window indicating limited storage.
-     * @hide
      */
-    public static final int UNSTARTABLE_REASON_DATALOADER_STORAGE = 2;
+    public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2;
 
     /** {@hide} */
     public int getUserId() {
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 35ef53b..25c749b 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -35,6 +35,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A class that contains biometric utilities. For authentication, see {@link BiometricPrompt}.
@@ -199,13 +201,24 @@
     }
 
     /**
+     * @return A list of {@link SensorProperties}
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    @RequiresPermission(TEST_BIOMETRIC)
+    public List<SensorProperties> getSensorProperties() {
+        return new ArrayList<>(); // TODO(169459906)
+    }
+
+    /**
      * Retrieves a test session for BiometricManager/BiometricPrompt.
      * @hide
      */
     @TestApi
     @NonNull
     @RequiresPermission(TEST_BIOMETRIC)
-    public BiometricTestSession getTestSession() {
+    public BiometricTestSession createTestSession(int sensorId) {
         return null; // TODO(169459906)
     }
 
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 719efa8..60ed404 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -23,10 +23,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.util.ArraySet;
 
 /**
  * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
@@ -35,37 +32,20 @@
  */
 @TestApi
 public class BiometricTestSession implements AutoCloseable {
-
-    private static final String TAG = "TestManager";
-
     private final Context mContext;
-    private final ITestService mTestService;
+    private final ITestSession mTestSession;
+
+    // Keep track of users that were tested, which need to be cleaned up when finishing.
+    private final ArraySet<Integer> mTestedUsers;
 
     /**
      * @hide
      */
-    public BiometricTestSession(@NonNull Context context, @NonNull ITestService testService) {
+    public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) {
         mContext = context;
-        mTestService = testService;
-    }
-
-    /**
-     * @return A list of {@link SensorProperties}
-     */
-    @NonNull
-    @RequiresPermission(TEST_BIOMETRIC)
-    public List<SensorProperties> getSensorProperties() {
-        try {
-            final List<SensorPropertiesInternal> internalProps =
-                    mTestService.getSensorPropertiesInternal(mContext.getOpPackageName());
-            final List<SensorProperties> props = new ArrayList<>();
-            for (SensorPropertiesInternal internalProp : internalProps) {
-                props.add(new SensorProperties(internalProp.sensorId, internalProp.sensorStrength));
-            }
-            return props;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        mTestSession = testSession;
+        mTestedUsers = new ArraySet<>();
+        enableTestHal(true);
     }
 
     /**
@@ -75,105 +55,101 @@
      * secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
      * equivalent for the secret key.
      *
-     * @param sensorId Sensor that this command applies to.
      * @param enableTestHal If true, enable testing with a fake HAL instead of the real HAL.
+     * @hide
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void enableTestHal(int sensorId, boolean enableTestHal) {
+    private void enableTestHal(boolean enableTestHal) {
         try {
-            mTestService.enableTestHal(sensorId, enableTestHal);
+            mTestSession.enableTestHal(enableTestHal);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
     /**
      * Starts the enrollment process. This should generally be used when the test HAL is enabled.
      *
-     * @param sensorId Sensor that this command applies to.
      * @param userId User that this command applies to.
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void enrollStart(int sensorId, int userId) {
+    public void startEnroll(int userId) {
         try {
-            mTestService.enrollStart(sensorId, userId);
+            mTestedUsers.add(userId);
+            mTestSession.startEnroll(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
     /**
      * Finishes the enrollment process. Simulates the HAL's callback.
      *
-     * @param sensorId Sensor that this command applies to.
      * @param userId User that this command applies to.
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void enrollFinish(int sensorId, int userId) {
+    public void finishEnroll(int userId) {
         try {
-            mTestService.enrollFinish(sensorId, userId);
+            mTestedUsers.add(userId);
+            mTestSession.finishEnroll(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
     /**
      * Simulates a successful authentication, but does not provide a valid HAT.
      *
-     * @param sensorId Sensor that this command applies to.
      * @param userId User that this command applies to.
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void authenticateSuccess(int sensorId, int userId) {
+    public void acceptAuthentication(int userId) {
         try {
-            mTestService.authenticateSuccess(sensorId, userId);
+            mTestSession.acceptAuthentication(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
     /**
      * Simulates a rejected attempt.
      *
-     * @param sensorId Sensor that this command applies to.
      * @param userId User that this command applies to.
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void authenticateReject(int sensorId, int userId) {
+    public void rejectAuthentication(int userId) {
         try {
-            mTestService.authenticateReject(sensorId, userId);
+            mTestSession.rejectAuthentication(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
     /**
      * Simulates an acquired message from the HAL.
      *
-     * @param sensorId Sensor that this command applies to.
      * @param userId User that this command applies to.
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void notifyAcquired(int sensorId, int userId) {
+    public void notifyAcquired(int userId) {
         try {
-            mTestService.notifyAcquired(sensorId, userId);
+            mTestSession.notifyAcquired(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
     /**
      * Simulates an error message from the HAL.
      *
-     * @param sensorId Sensor that this command applies to.
      * @param userId User that this command applies to.
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void notifyError(int sensorId, int userId) {
+    public void notifyError(int userId) {
         try {
-            mTestService.notifyError(sensorId, userId);
+            mTestSession.notifyError(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -182,21 +158,24 @@
      * that isn't known by both sides are deleted. This should generally be used when the test
      * HAL is disabled (e.g. to clean up after a test).
      *
-     * @param sensorId Sensor that this command applies to.
      * @param userId User that this command applies to.
      */
     @RequiresPermission(TEST_BIOMETRIC)
-    public void internalCleanup(int sensorId, int userId) {
+    public void cleanupInternalState(int userId) {
         try {
-            mTestService.internalCleanup(sensorId, userId);
+            mTestSession.cleanupInternalState(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
     @RequiresPermission(TEST_BIOMETRIC)
     public void close() {
+        for (int user : mTestedUsers) {
+            cleanupInternalState(user);
+        }
 
+        enableTestHal(false);
     }
 }
diff --git a/core/java/android/hardware/biometrics/ITestService.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
similarity index 75%
rename from core/java/android/hardware/biometrics/ITestService.aidl
rename to core/java/android/hardware/biometrics/ITestSession.aidl
index 6373132..5677f65 100644
--- a/core/java/android/hardware/biometrics/ITestService.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -21,37 +21,34 @@
  * A test service for FingerprintManager and BiometricPrompt.
  * @hide
  */
-interface ITestService {
-    // Returns a list of sensor properties supported by the interface.
-    List<SensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
-
+interface ITestSession {
     // Switches the specified sensor to use a test HAL. In this mode, the framework will not invoke
     // any methods on the real HAL implementation. This allows the framework to test a substantial
     // portion of the framework code that would otherwise require human interaction. Note that
     // secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
     // equivalent for the secret key.
-    void enableTestHal(int sensorId, boolean enableTestHal);
+    void enableTestHal(boolean enableTestHal);
 
     // Starts the enrollment process. This should generally be used when the test HAL is enabled.
-    void enrollStart(int sensorId, int userId);
+    void startEnroll(int userId);
 
     // Finishes the enrollment process. Simulates the HAL's callback.
-    void enrollFinish(int sensorId, int userId);
+    void finishEnroll(int userId);
 
     // Simulates a successful authentication, but does not provide a valid HAT.
-    void authenticateSuccess(int sensorId, int userId);
+    void acceptAuthentication(int userId);
 
     // Simulates a rejected attempt.
-    void authenticateReject(int sensorId, int userId);
+    void rejectAuthentication(int userId);
 
     // Simulates an acquired message from the HAL.
-    void notifyAcquired(int sensorId, int userId);
+    void notifyAcquired(int userId);
 
     // Simulates an error message from the HAL.
-    void notifyError(int sensorId, int userId);
+    void notifyError(int userId);
 
     // Matches the framework's cached enrollments against the HAL's enrollments. Any enrollment
     // that isn't known by both sides are deleted. This should generally be used when the test
     // HAL is disabled (e.g. to clean up after a test).
-    void internalCleanup(int sensorId, int userId);
+    void cleanupInternalState(int userId);
 }
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
new file mode 100644
index 0000000..a52f983
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -0,0 +1,38 @@
+/*
+ * 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 android.hardware.devicestate;
+
+import android.annotation.SystemService;
+import android.content.Context;
+
+/**
+ * Manages the state of the system for devices with user-configurable hardware like a foldable
+ * phone.
+ *
+ * @hide
+ */
+@SystemService(Context.DEVICE_STATE_SERVICE)
+public final class DeviceStateManager {
+    /** Invalid device state. */
+    public static final int INVALID_DEVICE_STATE = -1;
+
+    private DeviceStateManagerGlobal mGlobal;
+
+    public DeviceStateManager() {
+        mGlobal = DeviceStateManagerGlobal.getInstance();
+    }
+}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
new file mode 100644
index 0000000..4e7cf4a
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -0,0 +1,58 @@
+/*
+ * 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 android.hardware.devicestate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * Provides communication with the device state system service on behalf of applications.
+ *
+ * @see DeviceStateManager
+ * @hide
+ */
+final class DeviceStateManagerGlobal {
+    private static DeviceStateManagerGlobal sInstance;
+
+    /**
+     * Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
+     * connection with the device state service couldn't be established.
+     */
+    @Nullable
+    static DeviceStateManagerGlobal getInstance() {
+        synchronized (DeviceStateManagerGlobal.class) {
+            if (sInstance == null) {
+                IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
+                if (b != null) {
+                    sInstance = new DeviceStateManagerGlobal(IDeviceStateManager
+                            .Stub.asInterface(b));
+                }
+            }
+            return sInstance;
+        }
+    }
+
+    @NonNull
+    private final IDeviceStateManager mDeviceStateManager;
+
+    private DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
+        mDeviceStateManager = deviceStateManager;
+    }
+}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
new file mode 100644
index 0000000..24913e9
--- /dev/null
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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 android.hardware.devicestate;
+
+/** @hide */
+interface IDeviceStateManager {}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index dda1890b..fc14b89 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -16,6 +16,8 @@
 
 package android.hardware.display;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -381,6 +383,7 @@
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
                     addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
+                    addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL);
                 }
                 return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
             } finally {
@@ -401,6 +404,9 @@
     private void addPresentationDisplaysLocked(
             ArrayList<Display> displays, int[] displayIds, int matchType) {
         for (int i = 0; i < displayIds.length; i++) {
+            if (displayIds[i] == DEFAULT_DISPLAY) {
+                continue;
+            }
             Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
             if (display != null
                     && (display.getFlags() & Display.FLAG_PRESENTATION) != 0
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 84c5ea5..c5f8dac 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -39,6 +39,7 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricTestSession;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.SensorProperties;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.CancellationSignal.OnCancelListener;
@@ -97,6 +98,24 @@
     private Fingerprint mRemovalFingerprint;
     private Handler mHandler;
 
+
+    /**
+     * Retrieves a list of properties for all fingerprint sensors on the device.
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    @RequiresPermission(TEST_BIOMETRIC)
+    public List<SensorProperties> getSensorProperties() {
+        final List<SensorProperties> properties = new ArrayList<>();
+        final List<FingerprintSensorPropertiesInternal> internalProperties
+                = getSensorPropertiesInternal();
+        for (FingerprintSensorPropertiesInternal internalProp : internalProperties) {
+            properties.add(FingerprintSensorProperties.from(internalProp));
+        }
+        return properties;
+    }
+
     /**
      * Retrieves a test session for FingerprintManager.
      * @hide
@@ -104,10 +123,10 @@
     @TestApi
     @NonNull
     @RequiresPermission(TEST_BIOMETRIC)
-    public BiometricTestSession getTestSession() {
+    public BiometricTestSession createTestSession(int sensorId) {
         try {
             return new BiometricTestSession(mContext,
-                    mService.getTestService(mContext.getOpPackageName()));
+                    mService.createTestSession(sensorId, mContext.getOpPackageName()));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -636,13 +655,24 @@
     }
 
     /**
-     * Finishes enrollment and cancels the current auth token.
+     * Revokes the current challenge.
      * @hide
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void revokeChallenge() {
+        // On HALs with only single in-flight challenge such as IBiometricsFingerprint@2.1,
+        // this parameter is ignored.
+        revokeChallenge(0L);
+    }
+
+    /**
+     * Revokes the specified challenge.
+     * @hide
+     */
+    @RequiresPermission(MANAGE_FINGERPRINT)
+    public void revokeChallenge(long challenge) {
         if (mService != null) try {
-            mService.revokeChallenge(mToken, mContext.getOpPackageName());
+            mService.revokeChallenge(mToken, mContext.getOpPackageName(), challenge);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -867,21 +897,6 @@
     }
 
     /**
-     * Retrieves a list of properties for all fingerprint sensors on the device.
-     * @hide
-     */
-    @NonNull
-    public List<FingerprintSensorProperties> getSensorProperties() {
-        final List<FingerprintSensorProperties> properties = new ArrayList<>();
-        final List<FingerprintSensorPropertiesInternal> internalProperties
-                = getSensorPropertiesInternal();
-        for (FingerprintSensorPropertiesInternal internalProp : internalProperties) {
-            properties.add(FingerprintSensorProperties.from(internalProp));
-        }
-        return properties;
-    }
-
-    /**
      * Get statically configured sensor properties.
      * @hide
      */
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 518e3ca..cc086cf 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -17,7 +17,7 @@
 
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-import android.hardware.biometrics.ITestService;
+import android.hardware.biometrics.ITestSession;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -32,8 +32,8 @@
  */
 interface IFingerprintService {
 
-    // Retrieves a test service
-    ITestService getTestService(String opPackageName);
+    // Creates a test session with the specified sensorId
+    ITestSession createTestSession(int sensorId, String opPackageName);
 
     // Retrieve static sensor properties for all fingerprint sensors
     List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
@@ -96,7 +96,7 @@
     void generateChallenge(IBinder token, int sensorId, IFingerprintServiceReceiver receiver, String opPackageName);
 
     // Finish an enrollment sequence and invalidate the authentication token
-    void revokeChallenge(IBinder token, String opPackageName);
+    void revokeChallenge(IBinder token, String opPackageName, long challenge);
 
     // Determine if a user has at least one enrolled fingerprint
     boolean hasEnrolledFingerprints(int userId, String opPackageName);
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
index 75086cf..d31218d 100644
--- a/core/java/android/net/NetworkProvider.java
+++ b/core/java/android/net/NetworkProvider.java
@@ -30,7 +30,7 @@
 
 /**
  * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device
- * to networks and makes them available to to the core network stack by creating
+ * to networks and makes them available to the core network stack by creating
  * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted
  * with via networking APIs such as {@link ConnectivityManager}.
  *
diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java
index 121fd33..fc17002 100644
--- a/core/java/android/os/connectivity/CellularBatteryStats.java
+++ b/core/java/android/os/connectivity/CellularBatteryStats.java
@@ -109,7 +109,7 @@
                         CellSignalStrength.getNumSignalStrengthLevels()));
         mTxTimeMs = Arrays.copyOfRange(
                 txTimeMs, 0,
-                Math.min(txTimeMs.length, ModemActivityInfo.TX_POWER_LEVELS));
+                Math.min(txTimeMs.length, ModemActivityInfo.getNumTxPowerLevels()));
         mMonitoredRailChargeConsumedMaMs = monitoredRailChargeConsumedMaMs;
     }
 
diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md
index 0495495..4224b7a 100644
--- a/core/java/android/permission/Permissions.md
+++ b/core/java/android/permission/Permissions.md
@@ -848,7 +848,7 @@
 
 @Test
 fun onlySomeAppsAreAllowedToHavePermissionGranted() {
-    assertThat(whitelistedPkgs).containsAllIn(
+    assertThat(whitelistedPkgs).containsAtLeastElementsIn(
             context.packageManager.getInstalledPackages(MATCH_ALL)
                     .filter { pkg ->
                         context.checkPermission(android.Manifest.permission.MY_PRIVILEGED_PERMISSION, -1,
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index e1aa21e..8ac1d84e 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -2618,7 +2618,8 @@
             intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime));
             intent.putExtra(ALARM_TIME, alarmTime);
             intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+            PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
+                    PendingIntent.FLAG_IMMUTABLE);
             manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pi);
         }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7df9a5f..b133bcd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9088,6 +9088,7 @@
          * @see#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
          * @hide
          */
+        @TestApi
         public static final String ACCESSIBILITY_MAGNIFICATION_MODE =
                 "accessibility_magnification_mode";
 
@@ -9095,12 +9096,14 @@
          * Magnification mode value that magnifies whole display.
          * @hide
          */
+        @TestApi
         public static final int ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN = 0x1;
 
         /**
          * Magnification mode value that magnifies magnify particular region in a window
          * @hide
          */
+        @TestApi
         public static final int ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW = 0x2;
 
         /**
@@ -9108,6 +9111,7 @@
          * region in a window.
          * @hide
          */
+        @TestApi
         public static final int ACCESSIBILITY_MAGNIFICATION_MODE_ALL = 0x3;
 
         /**
@@ -9119,6 +9123,7 @@
          * @see#ACCESSIBILITY_MAGNIFICATION_MODE_ALL
          * @hide
          */
+        @TestApi
         public static final String ACCESSIBILITY_MAGNIFICATION_CAPABILITY =
                 "accessibility_magnification_capability";
 
@@ -13407,6 +13412,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String HIDDEN_API_POLICY = "hidden_api_policy";
 
         /**
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ebd114a..8f119fc 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1865,8 +1865,9 @@
         }
 
         /**
-         * Returns whether this notification is a conversation notification.
-         * @hide
+         * Returns whether this notification is a conversation notification, and would appear
+         * in the conversation section of the notification shade, on devices that separate that
+         * type of notification.
          */
         public boolean isConversation() {
             return mIsConversation;
@@ -1881,7 +1882,10 @@
         }
 
         /**
-         * @hide
+         * Returns the shortcut information associated with this notification, if it is a
+         * {@link #isConversation() conversation notification}.
+         * <p>This might be null even if the notification is a conversation notification, if
+         * the posting app hasn't opted into the full conversation feature set yet.</p>
          */
         public @Nullable ShortcutInfo getShortcutInfo() {
             return mShortcutInfo;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index e520d7c..b5080cd 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -181,7 +181,8 @@
     private long mFrameIntervalNanos;
     private boolean mDebugPrintNextFrameTimeDelta;
     private int mFPSDivisor = 1;
-    private long mLastVsyncId = FrameInfo.INVALID_VSYNC_ID;
+    private DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
+            new DisplayEventReceiver.VsyncEventData();
 
     /**
      * Contains information about the current frame for jank-tracking,
@@ -664,7 +665,18 @@
      * @hide
      */
     public long getVsyncId() {
-        return mLastVsyncId;
+        return mLastVsyncEventData.id;
+    }
+
+    /**
+     * Returns the frame deadline in {@link System#nanoTime()} timebase that it is allotted for the
+     * frame to be completed. Client are expected to call this function from their frame callback
+     * function. Calling this function from anywhere else will return an undefined value.
+     *
+     * @hide
+     */
+    public long getFrameDeadline() {
+        return mLastVsyncEventData.frameDeadline;
     }
 
     void setFPSDivisor(int divisor) {
@@ -673,7 +685,8 @@
         ThreadedRenderer.setFPSDivisor(divisor);
     }
 
-    void doFrame(long frameTimeNanos, int frame, long frameTimelineVsyncId) {
+    void doFrame(long frameTimeNanos, int frame,
+            DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         synchronized (mLock) {
             if (!mFrameScheduled) {
@@ -723,10 +736,11 @@
                 }
             }
 
-            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, frameTimelineVsyncId);
+            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
+                    vsyncEventData.frameDeadline);
             mFrameScheduled = false;
             mLastFrameTimeNanos = frameTimeNanos;
-            mLastVsyncId = frameTimelineVsyncId;
+            mLastVsyncEventData = vsyncEventData;
         }
 
         try {
@@ -910,7 +924,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_DO_FRAME:
-                    doFrame(System.nanoTime(), 0, FrameInfo.INVALID_VSYNC_ID);
+                    doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
                     break;
                 case MSG_DO_SCHEDULE_VSYNC:
                     doScheduleVsync();
@@ -927,7 +941,7 @@
         private boolean mHavePendingVsync;
         private long mTimestampNanos;
         private int mFrame;
-        private long mFrameTimelineVsyncId;
+        private VsyncEventData mLastVsyncEventData = new VsyncEventData();
 
         public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
             super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS);
@@ -938,7 +952,7 @@
         // for the internal display implicitly.
         @Override
         public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
-                long frameTimelineVsyncId) {
+                VsyncEventData vsyncEventData) {
             // Post the vsync event to the Handler.
             // The idea is to prevent incoming vsync events from completely starving
             // the message queue.  If there are no messages in the queue with timestamps
@@ -961,7 +975,7 @@
 
             mTimestampNanos = timestampNanos;
             mFrame = frame;
-            mFrameTimelineVsyncId = frameTimelineVsyncId;
+            mLastVsyncEventData = vsyncEventData;
             Message msg = Message.obtain(mHandler, this);
             msg.setAsynchronous(true);
             mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
@@ -970,7 +984,7 @@
         @Override
         public void run() {
             mHavePendingVsync = false;
-            doFrame(mTimestampNanos, mFrame, mFrameTimelineVsyncId);
+            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
         }
     }
 
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 51474d3..467d93e 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.FrameInfo;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.util.Log;
@@ -145,6 +146,26 @@
         mMessageQueue = null;
     }
 
+    static final class VsyncEventData {
+        // The frame timeline vsync id, used to correlate a frame
+        // produced by HWUI with the timeline data stored in Surface Flinger.
+        public final long id;
+
+        // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
+        // allotted for the frame to be completed.
+        public final long frameDeadline;
+
+        VsyncEventData(long id, long frameDeadline) {
+            this.id = id;
+            this.frameDeadline = frameDeadline;
+        }
+
+        VsyncEventData() {
+            this.id = FrameInfo.INVALID_VSYNC_ID;
+            this.frameDeadline = Long.MAX_VALUE;
+        }
+    }
+
     /**
      * Called when a vertical sync pulse is received.
      * The recipient should render a frame and then call {@link #scheduleVsync}
@@ -154,11 +175,10 @@
      * timebase.
      * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
      * @param frame The frame number.  Increases by one for each vertical sync interval.
-     * @param frameTimelineVsyncId The frame timeline vsync id, used to correlate a frame
-     * produced by HWUI with the timeline data stored in Surface Flinger.
+     * @param vsyncEventData The vsync event data.
      */
     public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
-            long frameTimelineVsyncId) {
+            VsyncEventData vsyncEventData) {
     }
 
     /**
@@ -201,8 +221,9 @@
     // Called from native code.
     @SuppressWarnings("unused")
     private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
-            long frameTimelineVsyncId) {
-        onVsync(timestampNanos, physicalDisplayId, frame, frameTimelineVsyncId);
+            long frameTimelineVsyncId, long frameDeadline) {
+        onVsync(timestampNanos, physicalDisplayId, frame,
+                new VsyncEventData(frameTimelineVsyncId, frameDeadline));
     }
 
     // Called from native code.
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 280a1c0..387787e 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -198,6 +198,7 @@
             Index.ANIMATION_START,
             Index.PERFORM_TRAVERSALS_START,
             Index.DRAW_START,
+            Index.FRAME_DEADLINE,
             Index.SYNC_QUEUED,
             Index.SYNC_START,
             Index.ISSUE_DRAW_COMMANDS_START,
@@ -216,13 +217,15 @@
         int ANIMATION_START = 7;
         int PERFORM_TRAVERSALS_START = 8;
         int DRAW_START = 9;
-        int SYNC_QUEUED = 10;
-        int SYNC_START = 11;
-        int ISSUE_DRAW_COMMANDS_START = 12;
-        int SWAP_BUFFERS = 13;
-        int FRAME_COMPLETED = 14;
+        int FRAME_DEADLINE = 10;
+        int SYNC_QUEUED = 11;
+        int SYNC_START = 12;
+        int ISSUE_DRAW_COMMANDS_START = 13;
+        int SWAP_BUFFERS = 14;
+        int FRAME_COMPLETED = 15;
 
-        int FRAME_STATS_COUNT = 18; // must always be last
+        int FRAME_STATS_COUNT = 19; // must always be last and in sync with
+                                    // FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
     }
 
     /*
diff --git a/core/java/android/view/KeyEvent.aidl b/core/java/android/view/KeyEvent.aidl
index dc15ecf..1f6d16e 100644
--- a/core/java/android/view/KeyEvent.aidl
+++ b/core/java/android/view/KeyEvent.aidl
@@ -17,4 +17,4 @@
 
 package android.view;
 
-parcelable KeyEvent;
+@JavaOnlyStableParcelable parcelable KeyEvent;
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 5b0d950..0847a17 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -739,7 +739,7 @@
      * Set the scaling mode to be used for this surfaces buffers
      * @hide
      */
-    void setScalingMode(@ScalingMode int scalingMode) {
+     public void setScalingMode(@ScalingMode int scalingMode) {
         synchronized (mLock) {
             checkNotReleasedLocked();
             int err = nativeSetScalingMode(mNativeObject, scalingMode);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ed9deec..566ebf3 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -187,8 +187,6 @@
     private static native void nativeReparent(long transactionObj, long nativeObject,
             long newParentNativeObject);
     private static native void nativeSeverChildren(long transactionObj, long nativeObject);
-    private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
-            int scalingMode);
 
     private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
 
@@ -1521,16 +1519,6 @@
     /**
      * @hide
      */
-    public void setOverrideScalingMode(int scalingMode) {
-        checkNotReleased();
-        synchronized(SurfaceControl.class) {
-            sGlobalTransaction.setOverrideScalingMode(this, scalingMode);
-        }
-    }
-
-    /**
-     * @hide
-     */
     @UnsupportedAppUsage
     public void setLayer(int zorder) {
         checkNotReleased();
@@ -2989,16 +2977,6 @@
         }
 
         /**
-         * @hide
-         */
-        public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
-            checkPreconditions(sc);
-            nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
-                    overrideScalingMode);
-            return this;
-        }
-
-        /**
          * Fills the surface with the specified color.
          * @param color A float array with three values to represent r, g, b in range [0..1]. An
          * invalid color will remove the color fill.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c430a4d..4f05a59 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15165,6 +15165,42 @@
     }
 
     /**
+     * Called by the {@link android.view.inputmethod.InputMethodManager} to notify the application
+     * that the system has successfully initialized an {@link InputConnection} and it is ready for
+     * use.
+     *
+     * <p>The default implementation does nothing, since a view doesn't support input methods by
+     * default (see {@link #onCreateInputConnection}).
+     *
+     * @param inputConnection The {@link InputConnection} from {@link #onCreateInputConnection},
+     * after it's been fully initialized by the system.
+     * @param editorInfo The {@link EditorInfo} that was used to create the {@link InputConnection}.
+     * @param handler The dedicated {@link Handler} on which IPC method calls from input methods
+     * will be dispatched. This is the handler returned by {@link InputConnection#getHandler()}. If
+     * that method returns null, this parameter will be null also.
+     *
+     * @hide
+     */
+    public void onInputConnectionOpenedInternal(@NonNull InputConnection inputConnection,
+            @NonNull EditorInfo editorInfo, @Nullable Handler handler) {}
+
+    /**
+     * Called by the {@link android.view.inputmethod.InputMethodManager} to notify the application
+     * that the {@link InputConnection} has been closed.
+     *
+     * <p>The default implementation does nothing, since a view doesn't support input methods by
+     * default (see {@link #onCreateInputConnection}).
+     *
+     * <p><strong>Note:</strong> This callback is not invoked if the view is already detached when
+     * the {@link InputConnection} is closed or the connection is not valid and managed by
+     * {@link com.android.server.inputmethod.InputMethodManagerService}.
+     * TODO(b/170645312): Before un-hiding this API, handle the detached view scenario.
+     *
+     * @hide
+     */
+    public void onInputConnectionClosedInternal() {}
+
+    /**
      * Called by the {@link android.view.inputmethod.InputMethodManager}
      * when a view who is not the current
      * input connection target is trying to make a call on the manager.  The
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index b8f04159..5785999 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1006,6 +1006,24 @@
                 return;
             }
             closeConnection();
+
+            // Notify the app that the InputConnection was closed.
+            final View servedView = mServedView.get();
+            if (servedView != null) {
+                final Handler handler = servedView.getHandler();
+                // The handler is null if the view is already detached. When that's the case, for
+                // now, we simply don't dispatch this callback.
+                if (handler != null) {
+                    if (DEBUG) {
+                        Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView);
+                    }
+                    if (handler.getLooper().isCurrentThread()) {
+                        servedView.onInputConnectionClosedInternal();
+                    } else {
+                        handler.post(servedView::onInputConnectionClosedInternal);
+                    }
+                }
+            }
         }
 
         @Override
@@ -1940,6 +1958,8 @@
         InputConnection ic = view.onCreateInputConnection(tba);
         if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
 
+        final Handler icHandler;
+        InputBindResult res = null;
         synchronized (mH) {
             // Now that we are locked again, validate that our state hasn't
             // changed.
@@ -1976,7 +1996,6 @@
                 mCursorCandEnd = -1;
                 mCursorRect.setEmpty();
                 mCursorAnchorInfo = null;
-                final Handler icHandler;
                 missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
                 if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
                         != 0) {
@@ -1990,6 +2009,7 @@
             } else {
                 servedContext = null;
                 missingMethodFlags = 0;
+                icHandler = null;
             }
             mServedInputConnectionWrapper = servedContext;
 
@@ -1997,7 +2017,7 @@
                 if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
                         + ic + " tba=" + tba + " startInputFlags="
                         + InputMethodDebug.startInputFlagsToString(startInputFlags));
-                final InputBindResult res = mService.startInputOrWindowGainedFocus(
+                res = mService.startInputOrWindowGainedFocus(
                         startInputReason, mClient, windowGainingFocus, startInputFlags,
                         softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
                         view.getContext().getApplicationInfo().targetSdkVersion);
@@ -2036,6 +2056,15 @@
             }
         }
 
+        // Notify the app that the InputConnection is initialized and ready for use.
+        if (ic != null && res != null && res.method != null) {
+            if (DEBUG) {
+                Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view
+                        + ", ic=" + ic + ", tba=" + tba + ", handler=" + icHandler);
+            }
+            view.onInputConnectionOpenedInternal(ic, tba, icHandler);
+        }
+
         return true;
     }
 
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 2e5ee04..5bcfa8b 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@
     private final UserInfo mUserInfo;
     private final PackageInfo mPackageInfo;
 
-    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.R;
+    public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.S;
 
     public UserPackage(UserInfo user, PackageInfo packageInfo) {
         this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 8790bbd..5fc9344 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,7 +47,7 @@
     // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
     /** @hide */
     private static final String CHROMIUM_WEBVIEW_FACTORY =
-            "com.android.webview.chromium.WebViewChromiumFactoryProviderForR";
+            "com.android.webview.chromium.WebViewChromiumFactoryProviderForS";
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 12b16ff..3a84c1f 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -17,7 +17,9 @@
 package android.window;
 
 import android.app.ActivityManager;
+import android.content.pm.ParceledListSlice;
 import android.window.ITaskOrganizer;
+import android.window.TaskAppearedInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -26,8 +28,11 @@
 
     /**
      * Register a TaskOrganizer to manage all the tasks with supported windowing modes.
+     *
+     * @return a list of the tasks that should be managed by the organizer, not including tasks
+     *         created via {@link #createRootTask}.
      */
-    void registerTaskOrganizer(ITaskOrganizer organizer);
+    ParceledListSlice<TaskAppearedInfo> registerTaskOrganizer(ITaskOrganizer organizer);
 
     /**
      * Unregisters a previously registered task organizer.
diff --git a/core/java/android/window/TaskAppearedInfo.aidl b/core/java/android/window/TaskAppearedInfo.aidl
new file mode 100644
index 0000000..13eba25f
--- /dev/null
+++ b/core/java/android/window/TaskAppearedInfo.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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 android.window;
+
+/**
+ * Data object for the task info provided when a task is presented to an organizer.
+ * @hide
+ */
+parcelable TaskAppearedInfo;
+
diff --git a/core/java/android/window/TaskAppearedInfo.java b/core/java/android/window/TaskAppearedInfo.java
new file mode 100644
index 0000000..2ff331e
--- /dev/null
+++ b/core/java/android/window/TaskAppearedInfo.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.window;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+/**
+ * Data object for the task info provided when a task is presented to an organizer.
+ * @hide
+ */
+@TestApi
+public final class TaskAppearedInfo implements Parcelable {
+
+    @NonNull
+    private final RunningTaskInfo mTaskInfo;
+
+    @NonNull
+    private final SurfaceControl mLeash;
+
+    @NonNull
+    public static final Creator<TaskAppearedInfo> CREATOR = new Creator<TaskAppearedInfo>() {
+        @Override
+        public TaskAppearedInfo createFromParcel(Parcel source) {
+            final RunningTaskInfo taskInfo = source.readTypedObject(RunningTaskInfo.CREATOR);
+            final SurfaceControl leash = source.readTypedObject(SurfaceControl.CREATOR);
+            return new TaskAppearedInfo(taskInfo, leash);
+        }
+
+        @Override
+        public TaskAppearedInfo[] newArray(int size) {
+            return new TaskAppearedInfo[size];
+        }
+
+    };
+
+    public TaskAppearedInfo(@NonNull RunningTaskInfo taskInfo, @NonNull SurfaceControl leash) {
+        mTaskInfo = taskInfo;
+        mLeash = leash;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedObject(mTaskInfo, flags);
+        dest.writeTypedObject(mLeash, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @return the task info.
+     */
+    @NonNull
+    public RunningTaskInfo getTaskInfo() {
+        return mTaskInfo;
+    }
+
+    /**
+     * @return the leash for the task.
+     */
+    @NonNull
+    public SurfaceControl getLeash() {
+        return mLeash;
+    }
+}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index a7cb642..909bb47 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import android.annotation.BinderThread;
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -51,11 +52,16 @@
 
     /**
      * Register a TaskOrganizer to manage tasks as they enter a supported windowing mode.
+     *
+     * @return a list of the tasks that should be managed by the organizer, not including tasks
+     *         created via {@link #createRootTask}.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public final void registerOrganizer() {
+    @CallSuper
+    @NonNull
+    public List<TaskAppearedInfo> registerOrganizer() {
         try {
-            mTaskOrganizerController.registerTaskOrganizer(mInterface);
+            return mTaskOrganizerController.registerTaskOrganizer(mInterface).getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -63,7 +69,8 @@
 
     /** Unregisters a previously registered task organizer. */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public final void unregisterOrganizer() {
+    @CallSuper
+    public void unregisterOrganizer() {
         try {
             mTaskOrganizerController.unregisterTaskOrganizer(mInterface);
         } catch (RemoteException e) {
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index ba90154..eba4fd2 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -238,10 +238,10 @@
     }
 
     /**
-     * Sets whether a container should ignore the orientation request from apps below it. It
-     * currently only applies to {@link com.android.server.wm.TaskDisplayArea}. When {@code false},
-     * it may rotate based on the orientation request; When {@code true}, it can never specify
-     * orientation, but shows the fixed-orientation apps in the letterbox.
+     * Sets whether a container should ignore the orientation request from apps and windows below
+     * it. It currently only applies to {@link com.android.server.wm.DisplayArea}. When
+     * {@code false}, it may rotate based on the orientation request; When {@code true}, it can
+     * never specify orientation, but shows the fixed-orientation apps below it in the letterbox.
      * @hide
      */
     @NonNull
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 491ddba..2b4e09d 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -80,6 +80,8 @@
             "com.android.server.accessibility.MagnificationController";
     public static final ComponentName MAGNIFICATION_COMPONENT_NAME =
             new ComponentName("com.android.server.accessibility", "Magnification");
+    public static final ComponentName REDUCE_BRIGHT_COLORS_COMPONENT_NAME =
+            new ComponentName("com.android.server.accessibility", "ReduceBrightColors");
 
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -126,6 +128,11 @@
                             Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
                             "1" /* Value to enable */, "0" /* Value to disable */,
                             R.string.color_correction_feature_name));
+            featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
+                    new ToggleableFrameworkFeatureInfo(
+                            Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+                            "1" /* Value to enable */, "0" /* Value to disable */,
+                            R.string.reduce_bright_colors_feature_name));
             sFrameworkShortcutFeaturesMap = Collections.unmodifiableMap(featuresMap);
         }
         return sFrameworkShortcutFeaturesMap;
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index a7c5f6d..9d06bb9 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -21,6 +21,7 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
 import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
 
@@ -112,7 +113,7 @@
             @ShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
         targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
-        targets.addAll(getWhiteListingFeatureTargets(context, shortcutType));
+        targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
 
         return targets;
     }
@@ -196,12 +197,12 @@
         return targets;
     }
 
-    private static List<AccessibilityTarget> getWhiteListingFeatureTargets(Context context,
+    private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
             @ShortcutType int shortcutType) {
         final List<AccessibilityTarget> targets = new ArrayList<>();
 
-        final InvisibleToggleWhiteListingFeatureTarget magnification =
-                new InvisibleToggleWhiteListingFeatureTarget(context,
+        final InvisibleToggleAllowListingFeatureTarget magnification =
+                new InvisibleToggleAllowListingFeatureTarget(context,
                 shortcutType,
                 isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
                 MAGNIFICATION_CONTROLLER_NAME,
@@ -209,8 +210,8 @@
                 context.getDrawable(R.drawable.ic_accessibility_magnification),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
 
-        final ToggleWhiteListingFeatureTarget daltonizer =
-                new ToggleWhiteListingFeatureTarget(context,
+        final ToggleAllowListingFeatureTarget daltonizer =
+                new ToggleAllowListingFeatureTarget(context,
                 shortcutType,
                 isShortcutContained(context, shortcutType,
                         DALTONIZER_COMPONENT_NAME.flattenToString()),
@@ -219,8 +220,8 @@
                 context.getDrawable(R.drawable.ic_accessibility_color_correction),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
 
-        final ToggleWhiteListingFeatureTarget colorInversion =
-                new ToggleWhiteListingFeatureTarget(context,
+        final ToggleAllowListingFeatureTarget colorInversion =
+                new ToggleAllowListingFeatureTarget(context,
                 shortcutType,
                 isShortcutContained(context, shortcutType,
                         COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
@@ -229,9 +230,21 @@
                 context.getDrawable(R.drawable.ic_accessibility_color_inversion),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
 
+        // TODO: Update with shortcut icon
+        final ToggleAllowListingFeatureTarget reduceBrightColors =
+                new ToggleAllowListingFeatureTarget(context,
+                        shortcutType,
+                        isShortcutContained(context, shortcutType,
+                                REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString()),
+                        REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString(),
+                        context.getString(R.string.reduce_bright_colors_feature_name),
+                        null,
+                        Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
+
         targets.add(magnification);
         targets.add(daltonizer);
         targets.add(colorInversion);
+        targets.add(reduceBrightColors);
 
         return targets;
     }
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
similarity index 91%
rename from core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java
rename to core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index acd101b..e78036d 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -26,9 +26,9 @@
  * Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#INVISIBLE_TOGGLE}
  * type.
  */
-class InvisibleToggleWhiteListingFeatureTarget extends AccessibilityTarget {
+class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
-    InvisibleToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+    InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
             boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
         super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE,
                 isShortcutSwitched, id, label, icon, key);
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
similarity index 94%
rename from core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java
rename to core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index 5ab9eb8..38aac70 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -32,9 +32,9 @@
  * Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#TOGGLE}
  * type.
  */
-class ToggleWhiteListingFeatureTarget extends AccessibilityTarget {
+class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
 
-    ToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+    ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
             boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
         super(context, shortcutType, AccessibilityFragmentType.TOGGLE,
                 isShortcutSwitched, id, label, icon, key);
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index dd4409c..3624f0d 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,7 +16,18 @@
 
 package com.android.internal.jank;
 
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -63,18 +74,19 @@
     // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
     @VisibleForTesting
     public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
+            // This should be mapping to CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE.
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
-            NO_STATSD_LOGGING,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH,
     };
 
     private static volatile InteractionJankMonitor sInstance;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 17323ba..4c5f988 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -65,7 +65,6 @@
 import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
-import android.telephony.ModemActivityInfo.TransmitPower;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
@@ -7791,7 +7790,7 @@
         public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() {
             if (mModemControllerActivity == null) {
                 mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
-                        ModemActivityInfo.TX_POWER_LEVELS);
+                        ModemActivityInfo.getNumTxPowerLevels());
             }
             return mModemControllerActivity;
         }
@@ -9257,7 +9256,7 @@
 
             if (in.readInt() != 0) {
                 mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
-                        ModemActivityInfo.TX_POWER_LEVELS, in);
+                        ModemActivityInfo.getNumTxPowerLevels(), in);
             } else {
                 mModemControllerActivity = null;
             }
@@ -10520,7 +10519,7 @@
         mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
                 NUM_BT_TX_LEVELS);
         mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
-                ModemActivityInfo.TX_POWER_LEVELS);
+                ModemActivityInfo.getNumTxPowerLevels());
         mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null, mOnBatteryTimeBase);
         mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
                 mOnBatteryTimeBase);
@@ -11710,26 +11709,7 @@
         }
     }
 
-    private ModemActivityInfo mLastModemActivityInfo =
-            new ModemActivityInfo(0, 0, 0, new int[0], 0);
-
-    private ModemActivityInfo getDeltaModemActivityInfo(ModemActivityInfo activityInfo) {
-        if (activityInfo == null) {
-            return null;
-        }
-        int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
-        for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
-            txTimeMs[i] = activityInfo.getTransmitPowerInfo().get(i).getTimeInMillis()
-                    - mLastModemActivityInfo.getTransmitPowerInfo().get(i).getTimeInMillis();
-        }
-        ModemActivityInfo deltaInfo = new ModemActivityInfo(activityInfo.getTimestamp(),
-                activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(),
-                activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(),
-                txTimeMs,
-                activityInfo.getReceiveTimeMillis() - mLastModemActivityInfo.getReceiveTimeMillis());
-        mLastModemActivityInfo = activityInfo;
-        return deltaInfo;
-    }
+    private ModemActivityInfo mLastModemActivityInfo = null;
 
     /**
      * Distribute Cell radio energy info and network traffic to apps.
@@ -11746,7 +11726,9 @@
         if (DEBUG_ENERGY) {
             Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
         }
-        ModemActivityInfo deltaInfo = getDeltaModemActivityInfo(activityInfo);
+        ModemActivityInfo deltaInfo = mLastModemActivityInfo == null ? activityInfo
+                : mLastModemActivityInfo.getDelta(activityInfo);
+        mLastModemActivityInfo = activityInfo;
 
         // Add modem tx power to history.
         addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs);
@@ -11778,10 +11760,9 @@
                 mModemActivity.getSleepTimeCounter().addCountLocked(
                         deltaInfo.getSleepTimeMillis());
                 mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis());
-                for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
+                for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) {
                     mModemActivity.getTxTimeCounters()[lvl]
-                        .addCountLocked(deltaInfo.getTransmitPowerInfo()
-                            .get(lvl).getTimeInMillis());
+                        .addCountLocked(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl));
                 }
 
                 // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
@@ -11795,11 +11776,11 @@
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)
                             + deltaInfo.getReceiveTimeMillis() *
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
-                    List<TransmitPower> txPowerInfo = deltaInfo.getTransmitPowerInfo();
-                    for (int i = 0; i < Math.min(txPowerInfo.size(),
+                    for (int i = 0; i < Math.min(ModemActivityInfo.getNumTxPowerLevels(),
                             CellSignalStrength.getNumSignalStrengthLevels()); i++) {
-                        energyUsed += txPowerInfo.get(i).getTimeInMillis() * mPowerProfile
-                            .getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
+                        energyUsed += deltaInfo.getTransmitDurationMillisAtPowerLevel(i)
+                                * mPowerProfile.getAveragePower(
+                                        PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
                     }
 
                     // We store the power drain as mAms.
@@ -11894,10 +11875,10 @@
                             }
 
                             if (totalTxPackets > 0 && entry.txPackets > 0) {
-                                for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
-                                    long txMs =
-                                            entry.txPackets * deltaInfo.getTransmitPowerInfo()
-                                                .get(lvl).getTimeInMillis();
+                                for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels();
+                                        lvl++) {
+                                    long txMs = entry.txPackets
+                                            * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl);
                                     txMs /= totalTxPackets;
                                     activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
                                 }
@@ -11929,18 +11910,14 @@
         if (activityInfo == null) {
             return;
         }
-        List<TransmitPower> txPowerInfo = activityInfo.getTransmitPowerInfo();
-        if (txPowerInfo == null || txPowerInfo.size() != ModemActivityInfo.TX_POWER_LEVELS) {
-            return;
-        }
         int levelMaxTimeSpent = 0;
-        for (int i = 1; i < txPowerInfo.size(); i++) {
-            if (txPowerInfo.get(i).getTimeInMillis() > txPowerInfo.get(levelMaxTimeSpent)
-                .getTimeInMillis()) {
+        for (int i = 1; i < ModemActivityInfo.getNumTxPowerLevels(); i++) {
+            if (activityInfo.getTransmitDurationMillisAtPowerLevel(i)
+                    > activityInfo.getTransmitDurationMillisAtPowerLevel(levelMaxTimeSpent)) {
                 levelMaxTimeSpent = i;
             }
         }
-        if (levelMaxTimeSpent == ModemActivityInfo.TX_POWER_LEVELS - 1) {
+        if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
             mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
             addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
         }
@@ -13462,7 +13439,7 @@
             timeInRxSignalStrengthLevelMs[i] =
                 getPhoneSignalStrengthTime(i, rawRealTimeUs, which) / 1000;
         }
-        long[] txTimeMs = new long[Math.min(ModemActivityInfo.TX_POWER_LEVELS,
+        long[] txTimeMs = new long[Math.min(ModemActivityInfo.getNumTxPowerLevels(),
             counter.getTxTimeCounters().length)];
         long totalTxTimeMs = 0;
         for (int i = 0; i < txTimeMs.length; i++) {
@@ -15601,7 +15578,7 @@
         mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
                 NUM_BT_TX_LEVELS, in);
         mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
-                ModemActivityInfo.TX_POWER_LEVELS, in);
+                ModemActivityInfo.getNumTxPowerLevels(), in);
         mHasWifiReporting = in.readInt() != 0;
         mHasBluetoothReporting = in.readInt() != 0;
         mHasModemReporting = in.readInt() != 0;
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index 4f687f1..e6a9623 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -45,6 +45,7 @@
     private static final String TAG = "KernelSingleProcCpuThreadRdr";
 
     private static final boolean DEBUG = false;
+    private static final boolean NATIVE_ENABLED = true;
 
     /**
      * The name of the file to read CPU statistics from, must be found in {@code
@@ -64,7 +65,7 @@
     private static final Path INITIAL_TIME_IN_STATE_PATH = Paths.get("self/time_in_state");
 
     /** See https://man7.org/linux/man-pages/man5/proc.5.html */
-    private static final int[] PROCESS_FULL_STATS_FORMAT = new int[]{
+    private static final int[] PROCESS_FULL_STATS_FORMAT = new int[] {
             PROC_SPACE_TERM,
             PROC_SPACE_TERM,
             PROC_SPACE_TERM,
@@ -162,6 +163,7 @@
     /**
      * Get the total and per-thread CPU usage of the process with the PID specified in the
      * constructor.
+     *
      * @param selectedThreadIds a SORTED array of native Thread IDs whose CPU times should
      *                          be aggregated as a group.  This is expected to be a subset
      *                          of all thread IDs owned by the process.
@@ -173,6 +175,20 @@
                     + mPid);
         }
 
+        int cpuFrequencyCount = getCpuFrequencyCount();
+        ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
+
+        if (NATIVE_ENABLED) {
+            boolean result = readProcessCpuUsage(mProcPath.toString(), mPid,
+                    selectedThreadIds, processCpuUsage.processCpuTimesMillis,
+                    processCpuUsage.threadCpuTimesMillis,
+                    processCpuUsage.selectedThreadCpuTimesMillis);
+            if (!result) {
+                return null;
+            }
+            return processCpuUsage;
+        }
+
         if (!isSorted(selectedThreadIds)) {
             throw new IllegalArgumentException("selectedThreadIds is not sorted: "
                     + Arrays.toString(selectedThreadIds));
@@ -189,8 +205,6 @@
 
         long processCpuTimeMillis = (utime + stime) * mJiffyMillis;
 
-        int cpuFrequencyCount = getCpuFrequencyCount();
-        ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
         try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
             for (Path threadDirectory : threadPaths) {
                 readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
@@ -274,4 +288,8 @@
         }
         return true;
     }
+
+    private native boolean readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds,
+            long[] processCpuTimesMillis, long[] threadCpuTimesMillis,
+            long[] selectedThreadCpuTimesMillis);
 }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index a23fc4b..0bafb2f 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -279,6 +279,7 @@
             final Runnable mScreenshotTimeout = () -> {
                 synchronized (mScreenshotLock) {
                     if (mScreenshotConnection != null) {
+                        Log.e(TAG, "Timed out before getting screenshot capture response");
                         mContext.unbindService(mScreenshotConnection);
                         mScreenshotConnection = null;
                         mScreenshotService = null;
@@ -353,6 +354,7 @@
                                 mScreenshotService = null;
                                 // only log an error if we're still within the timeout period
                                 if (handler.hasCallbacks(mScreenshotTimeout)) {
+                                    Log.e(TAG, "Screenshot service disconnected");
                                     handler.removeCallbacks(mScreenshotTimeout);
                                     notifyScreenshotError();
                                 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index adb0fad..4f97975 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -184,6 +184,7 @@
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
                 "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
+                "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
                 "com_android_internal_os_KernelSingleUidTimeReader.cpp",
                 "com_android_internal_os_Zygote.cpp",
                 "com_android_internal_os_ZygoteInit.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5b1196d..27b23bd 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -189,6 +189,7 @@
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
+extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
 extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
 extern int register_com_android_internal_os_Zygote(JNIEnv *env);
 extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
@@ -1581,6 +1582,7 @@
         REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
         REG_JNI(register_com_android_internal_os_FuseAppLoop),
         REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
+        REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader),
         REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
 };
 
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 5c4c509..1ca45fe 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -338,7 +338,7 @@
             return (jint)AUDIO_JAVA_BAD_VALUE;
         }
         const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL);
-        AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address);
+        AudioDeviceTypeAddr dev = AudioDeviceTypeAddr((audio_devices_t)typesPtr[i], address);
         audioDeviceTypeAddrVector.push_back(dev);
         env->ReleaseStringUTFChars((jstring)addrJobj, address);
     }
@@ -820,7 +820,8 @@
                                                bool useInMask)
 {
     nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex);
-    nAudioGainConfig->mode = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
+    nAudioGainConfig->mode =
+            (audio_gain_mode_t)env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
     ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index);
     jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask);
     audio_channel_mask_t nMask;
@@ -940,8 +941,8 @@
 
     jobject jAudioDevicePort = env->GetObjectField(jAudioPortConfig,
             gAudioPortConfigFields.mPort);
-    nAudioPortConfig->ext.device.type = env->GetIntField(jAudioDevicePort,
-            gAudioPortFields.mType);
+    nAudioPortConfig->ext.device.type =
+            (audio_devices_t)env->GetIntField(jAudioDevicePort, gAudioPortFields.mType);
     jstring jDeviceAddress = (jstring)env->GetObjectField(jAudioDevicePort,
             gAudioPortFields.mAddress);
     const char *nDeviceAddress = env->GetStringUTFChars(jDeviceAddress, NULL);
@@ -2334,7 +2335,7 @@
 
 static jint
 android_media_AudioSystem_setAllowedCapturePolicy(JNIEnv *env, jobject thiz, jint uid, jint flags) {
-    return AudioSystem::setAllowedCapturePolicy(uid, flags);
+    return AudioSystem::setAllowedCapturePolicy(uid, static_cast<audio_flags_mask_t>(flags));
 }
 
 static jint
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 3acd15a..e7e9c31 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -60,7 +60,7 @@
     sp<MessageQueue> mMessageQueue;
 
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
-                       int64_t sharedTimelineFrameCount) override;
+                       VsyncEventData vsyncEventData) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
                                int32_t configId, nsecs_t vsyncPeriod) override;
@@ -91,14 +91,15 @@
 }
 
 void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
-                                               uint32_t count, int64_t frameTimelineVsyncId) {
+                                               uint32_t count, VsyncEventData vsyncEventData) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking vsync handler.", this);
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
-                            timestamp, displayId.value, count, frameTimelineVsyncId);
+                            timestamp, displayId.value, count, vsyncEventData.id,
+                            vsyncEventData.deadlineTimestamp);
         ALOGV("receiver %p ~ Returned from vsync handler.", this);
     }
 
@@ -198,7 +199,8 @@
     gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
 
     gDisplayEventReceiverClassInfo.dispatchVsync =
-            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJIJ)V");
+            GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync",
+                             "(JJIJJ)V");
     gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
             gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
     gDisplayEventReceiverClassInfo.dispatchConfigChanged = GetMethodIDOrDie(env,
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1419855..a61903d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1360,15 +1360,6 @@
     transaction->detachChildren(ctrl);
 }
 
-static void nativeSetOverrideScalingMode(JNIEnv* env, jclass clazz, jlong transactionObj,
-        jlong nativeObject,
-        jint scalingMode) {
-    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
-    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    transaction->setOverrideScalingMode(ctrl, scalingMode);
-}
-
 static jobject nativeGetHdrCapabilities(JNIEnv* env, jclass clazz, jobject tokenObject) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
     if (token == NULL) return NULL;
@@ -1694,8 +1685,6 @@
             (void*)nativeReparent },
     {"nativeSeverChildren", "(JJ)V",
             (void*)nativeSeverChildren } ,
-    {"nativeSetOverrideScalingMode", "(JJI)V",
-            (void*)nativeSetOverrideScalingMode },
     {"nativeCaptureDisplay",
             "(Landroid/view/SurfaceControl$DisplayCaptureArgs;Landroid/view/SurfaceControl$ScreenCaptureListener;)I",
             (void*)nativeCaptureDisplay },
diff --git a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
new file mode 100644
index 0000000..52bed6b
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+#include <dirent.h>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android_runtime/Log.h>
+
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+namespace android {
+
+// Number of milliseconds in a jiffy - the unit of time measurement for processes and threads
+static const uint32_t gJiffyMillis = (uint32_t)(1000 / sysconf(_SC_CLK_TCK));
+
+// Given a PID, returns a vector of all TIDs for the process' tasks. Thread IDs are
+// file names in the /proc/<pid>/task directory.
+static bool getThreadIds(const std::string &procPath, const pid_t pid,
+                         std::vector<pid_t> &outThreadIds) {
+    std::string taskPath = android::base::StringPrintf("%s/%u/task", procPath.c_str(), pid);
+
+    struct dirent **dirlist;
+    int threadCount = scandir(taskPath.c_str(), &dirlist, NULL, NULL);
+    if (threadCount == -1) {
+        ALOGE("Cannot read directory %s", taskPath.c_str());
+        return false;
+    }
+
+    outThreadIds.reserve(threadCount);
+
+    for (int i = 0; i < threadCount; i++) {
+        pid_t tid;
+        if (android::base::ParseInt<pid_t>(dirlist[i]->d_name, &tid)) {
+            outThreadIds.push_back(tid);
+        }
+        free(dirlist[i]);
+    }
+    free(dirlist);
+
+    return true;
+}
+
+// Reads contents of a time_in_state file and returns times as a vector of times per frequency
+// A time_in_state file contains pairs of frequency - time (in jiffies):
+//
+//    cpu0
+//    300000 30
+//    403200 0
+//    cpu4
+//    710400 10
+//    825600 20
+//    940800 30
+//
+static bool getThreadTimeInState(const std::string &procPath, const pid_t pid, const pid_t tid,
+                                 const size_t frequencyCount,
+                                 std::vector<uint64_t> &outThreadTimeInState) {
+    std::string timeInStateFilePath =
+            android::base::StringPrintf("%s/%u/task/%u/time_in_state", procPath.c_str(), pid, tid);
+    std::string data;
+
+    if (!android::base::ReadFileToString(timeInStateFilePath, &data)) {
+        ALOGE("Cannot read file: %s", timeInStateFilePath.c_str());
+        return false;
+    }
+
+    auto lines = android::base::Split(data, "\n");
+    size_t index = 0;
+    for (const auto &line : lines) {
+        if (line.empty()) {
+            continue;
+        }
+
+        auto numbers = android::base::Split(line, " ");
+        if (numbers.size() != 2) {
+            continue;
+        }
+        uint64_t timeInState;
+        if (!android::base::ParseUint<uint64_t>(numbers[1], &timeInState)) {
+            ALOGE("Invalid time_in_state file format: %s", timeInStateFilePath.c_str());
+            return false;
+        }
+        if (index < frequencyCount) {
+            outThreadTimeInState[index] = timeInState;
+        }
+        index++;
+    }
+
+    if (index != frequencyCount) {
+        ALOGE("Incorrect number of frequencies %u in %s. Expected %u",
+              (uint32_t)outThreadTimeInState.size(), timeInStateFilePath.c_str(),
+              (uint32_t)frequencyCount);
+        return false;
+    }
+
+    return true;
+}
+
+static int pidCompare(const void *a, const void *b) {
+    return (*(pid_t *)a - *(pid_t *)b);
+}
+
+static inline bool isSelectedThread(const pid_t tid, const pid_t *selectedThreadIds,
+                                    const size_t selectedThreadCount) {
+    return bsearch(&tid, selectedThreadIds, selectedThreadCount, sizeof(pid_t), pidCompare) != NULL;
+}
+
+// Reads all /proc/<pid>/task/*/time_in_state files and aggregates per-frequency
+// time in state data for all threads.  Also, separately aggregates time in state for
+// selected threads whose TIDs are passes as selectedThreadIds.
+static void aggregateThreadCpuTimes(const std::string &procPath, const pid_t pid,
+                                    const std::vector<pid_t> &threadIds,
+                                    const size_t frequencyCount, const pid_t *selectedThreadIds,
+                                    const size_t selectedThreadCount,
+                                    uint64_t *threadCpuTimesMillis,
+                                    uint64_t *selectedThreadCpuTimesMillis) {
+    for (size_t j = 0; j < frequencyCount; j++) {
+        threadCpuTimesMillis[j] = 0;
+        selectedThreadCpuTimesMillis[j] = 0;
+    }
+
+    for (size_t i = 0; i < threadIds.size(); i++) {
+        pid_t tid = threadIds[i];
+        std::vector<uint64_t> timeInState(frequencyCount);
+        if (!getThreadTimeInState(procPath, pid, tid, frequencyCount, timeInState)) {
+            continue;
+        }
+
+        bool selectedThread = isSelectedThread(tid, selectedThreadIds, selectedThreadCount);
+        for (size_t j = 0; j < frequencyCount; j++) {
+            threadCpuTimesMillis[j] += timeInState[j];
+            if (selectedThread) {
+                selectedThreadCpuTimesMillis[j] += timeInState[j];
+            }
+        }
+    }
+    for (size_t i = 0; i < frequencyCount; i++) {
+        threadCpuTimesMillis[i] *= gJiffyMillis;
+        selectedThreadCpuTimesMillis[i] *= gJiffyMillis;
+    }
+}
+
+// Reads process utime and stime from the /proc/<pid>/stat file.
+// Format of this file is described in https://man7.org/linux/man-pages/man5/proc.5.html.
+static bool getProcessCpuTime(const std::string &procPath, const pid_t pid,
+                              uint64_t &outTimeMillis) {
+    std::string statFilePath = android::base::StringPrintf("%s/%u/stat", procPath.c_str(), pid);
+    std::string data;
+    if (!android::base::ReadFileToString(statFilePath, &data)) {
+        return false;
+    }
+
+    auto fields = android::base::Split(data, " ");
+    uint64_t utime, stime;
+
+    // Field 14 (counting from 1) is utime - process time in user space, in jiffies
+    // Field 15 (counting from 1) is stime - process time in system space, in jiffies
+    if (fields.size() < 15 || !android::base::ParseUint(fields[13], &utime) ||
+        !android::base::ParseUint(fields[14], &stime)) {
+        ALOGE("Invalid file format %s", statFilePath.c_str());
+        return false;
+    }
+
+    outTimeMillis = (utime + stime) * gJiffyMillis;
+    return true;
+}
+
+// Estimates per cluster per frequency CPU time for the entire process
+// by distributing the total process CPU time proportionately to how much
+// CPU time its threads took on those clusters/frequencies.  This algorithm
+// works more accurately when when we have equally distributed concurrency.
+// TODO(b/169279846): obtain actual process CPU times from the kernel
+static void estimateProcessTimeInState(const uint64_t processCpuTimeMillis,
+                                       const uint64_t *threadCpuTimesMillis,
+                                       const size_t frequencyCount,
+                                       uint64_t *processCpuTimesMillis) {
+    uint64_t totalCpuTimeAllThreads = 0;
+    for (size_t i = 0; i < frequencyCount; i++) {
+        totalCpuTimeAllThreads += threadCpuTimesMillis[i];
+    }
+
+    if (totalCpuTimeAllThreads != 0) {
+        for (size_t i = 0; i < frequencyCount; i++) {
+            processCpuTimesMillis[i] =
+                    processCpuTimeMillis * threadCpuTimesMillis[i] / totalCpuTimeAllThreads;
+        }
+    } else {
+        for (size_t i = 0; i < frequencyCount; i++) {
+            processCpuTimesMillis[i] = 0;
+        }
+    }
+}
+
+static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jstring procPath, jint pid,
+                                    jintArray selectedThreadIdArray,
+                                    jlongArray processCpuTimesMillisArray,
+                                    jlongArray threadCpuTimesMillisArray,
+                                    jlongArray selectedThreadCpuTimesMillisArray) {
+    ScopedUtfChars procPathChars(env, procPath);
+    ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
+    ScopedLongArrayRW processCpuTimesMillis(env, processCpuTimesMillisArray);
+    ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
+    ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
+
+    std::string procPathStr(procPathChars.c_str());
+
+    // Get all thread IDs for the process.
+    std::vector<pid_t> threadIds;
+    if (!getThreadIds(procPathStr, pid, threadIds)) {
+        ALOGE("Could not obtain thread IDs from: %s", procPathStr.c_str());
+        return false;
+    }
+
+    size_t frequencyCount = processCpuTimesMillis.size();
+
+    if (threadCpuTimesMillis.size() != frequencyCount) {
+        ALOGE("Invalid array length: threadCpuTimesMillis");
+        return false;
+    }
+    if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
+        ALOGE("Invalid array length: selectedThreadCpuTimesMillisArray");
+        return false;
+    }
+
+    aggregateThreadCpuTimes(procPathStr, pid, threadIds, frequencyCount, selectedThreadIds.get(),
+                            selectedThreadIds.size(),
+                            reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
+                            reinterpret_cast<uint64_t *>(selectedThreadCpuTimesMillis.get()));
+
+    uint64_t processCpuTime;
+    bool ret = getProcessCpuTime(procPathStr, pid, processCpuTime);
+    if (ret) {
+        estimateProcessTimeInState(processCpuTime,
+                                   reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
+                                   frequencyCount,
+                                   reinterpret_cast<uint64_t *>(processCpuTimesMillis.get()));
+    }
+    return ret;
+}
+
+static const JNINativeMethod g_single_methods[] = {
+        {"readProcessCpuUsage", "(Ljava/lang/String;I[I[J[J[J)Z", (void *)readProcessCpuUsage},
+};
+
+int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
+    return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleProcessCpuThreadReader",
+                                g_single_methods, NELEM(g_single_methods));
+}
+
+} // namespace android
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 7fac615..99fe1af 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2751,4 +2751,14 @@
 
     // OS: R QPR2
     BLUETOOTH_PAIRING_RECEIVER = 1851;
+
+    // OPEN: Settings > Display > Screen timeout
+    // CATEGORY: SETTINGS
+    // OS: S
+    SCREEN_TIMEOUT = 1852;
+
+    // OPEN: Settings > Accessibility > Reduce Bright Colors
+    // CATEGORY: SETTINGS
+    // OS: S
+    REDUCE_BRIGHT_COLORS_SETTINGS = 1853;
 }
diff --git a/core/proto/android/server/vibratorservice.proto b/core/proto/android/server/vibratorservice.proto
index 281a25e..9e42e9e 100644
--- a/core/proto/android/server/vibratorservice.proto
+++ b/core/proto/android/server/vibratorservice.proto
@@ -21,6 +21,12 @@
 
 import "frameworks/base/core/proto/android/privacy.proto";
 
+message OneShotProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 duration = 1;
+    repeated int32 amplitude = 2;
+}
+
 message WaveformProto {
    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
    repeated int32 timings = 1;
@@ -35,20 +41,41 @@
     optional int32 fallback = 3;
 }
 
+message ComposedProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    repeated int32 effect_ids = 1;
+    repeated float effect_scales = 2;
+    repeated int32 delays = 3;
+}
+
 // A com.android.os.VibrationEffect object.
 message VibrationEffectProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional OneShotProto oneshot = 3;
     optional WaveformProto waveform = 1;
     optional PrebakedProto prebaked = 2;
+    optional ComposedProto composed = 4;
 }
 
+message VibrationAttributesProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+    optional int32 usage = 1;
+    optional int32 audio_usage = 2;
+    optional int32 flags = 3;
+}
+
+// Next id: 7
 message VibrationProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     optional int64 start_time = 1;
+    optional int64 end_time = 4;
     optional VibrationEffectProto effect = 2;
-    optional VibrationEffectProto origin_effect = 3;
+    optional VibrationEffectProto original_effect = 3;
+    optional VibrationAttributesProto attributes = 5;
+    optional int32 status = 6;
 }
 
+// Next id: 17
 message VibratorServiceDumpProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
     optional VibrationProto current_vibration = 1;
@@ -57,10 +84,14 @@
     optional bool vibrator_under_external_control = 4;
     optional bool low_power_mode = 5;
     optional int32 haptic_feedback_intensity = 6;
+    optional int32 haptic_feedback_default_intensity = 14;
     optional int32 notification_intensity = 7;
+    optional int32 notification_default_intensity = 15;
     optional int32 ring_intensity = 8;
+    optional int32 ring_default_intensity = 16;
     repeated VibrationProto previous_ring_vibrations = 9;
     repeated VibrationProto previous_notification_vibrations = 10;
     repeated VibrationProto previous_alarm_vibrations = 11;
     repeated VibrationProto previous_vibrations = 12;
+    repeated VibrationProto previous_external_vibrations = 13;
 }
\ No newline at end of file
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 7057b844..012acf5 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1206,7 +1206,7 @@
     <string name="android_upgrading_apk" msgid="1339564803894466737">"<xliff:g id="NUMBER_1">%2$d</xliff:g> ичинен <xliff:g id="NUMBER_0">%1$d</xliff:g> колдонмо ыңгайлаштырылууда."</string>
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> даярдалууда."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Колдонмолорду иштетип баштоо"</string>
-    <string name="android_upgrading_complete" msgid="409800058018374746">"Жүктөө аякталууда."</string>
+    <string name="android_upgrading_complete" msgid="409800058018374746">"Жүктөлүүдө"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> иштеп жатат"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Оюнга кайтуу үчүн таптаңыз"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Оюн тандоо"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 1418248..2183d010 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -957,18 +957,18 @@
     <string name="autofill_parish" msgid="6847960518334530198">"Мөргөлч"</string>
     <string name="autofill_area" msgid="8289022370678448983">"Хэсэг"</string>
     <string name="autofill_emirate" msgid="2544082046790551168">"Эмират"</string>
-    <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"өөрийн Вэб хавчуурга болон түүхийг унших"</string>
+    <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"өөрийн Веб хавчуурга болон түүхийг унших"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Апп нь Хөтчийн зочилж байсан бүх URL-н түүх болон Хөтчийн бүх хавчуургыг унших боломжтой. Анхаар: Энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вебээр хөтөчлөх чадавхтай аппликейшнүүдэд ашиглагдахгүй байх боломжтой."</string>
-    <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"вэб хавчуурга болон түүхийг бичих"</string>
+    <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"веб хавчуурга болон түүхийг бичих"</string>
     <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Апп нь таны таблет дээр хадгалагдсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөх боломжтой. Энэ нь апп-д Хөтчийн датаг арилгах эсвэл өөрчлөх боломжийг олгоно. Анхаар: Энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вебээр хөтөчлөх чадвартай аппликейшнд ажиллахгүй байх боломжтой."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Аппад таны Android TV төхөөрөмжид хадгалсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөхийг зөвшөөрнө. Энэ нь аппад Хөтчийн өгөгдлийг устгах эсвэл өөрчлөхийг зөвшөөрч болзошгүй. Санамж: энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вэб хөтчийн чадамжтай бусад аппад хэрэгжихгүй байж болзошгүй."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Аппад таны Android TV төхөөрөмжид хадгалсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөхийг зөвшөөрнө. Энэ нь аппад Хөтчийн өгөгдлийг устгах эсвэл өөрчлөхийг зөвшөөрч болзошгүй. Санамж: энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл веб хөтчийн чадамжтай бусад аппад хэрэгжихгүй байж болзошгүй."</string>
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Апп нь таны утсан дээр хадгалагдсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөх боломжтой. Энэ нь апп-д Хөтчийн датаг арилгах эсвэл өөрчлөх боломжийг олгоно. Анхаар: Энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вебээр хөтөчлөх чадвартай аппликейшнд ажиллахгүй байх боломжтой."</string>
     <string name="permlab_setAlarm" msgid="1158001610254173567">"сэрүүлэг тохируулах"</string>
     <string name="permdesc_setAlarm" msgid="2185033720060109640">"Апп нь суулгагдсан сэрүүлэгний апп дээр сэрүүлэг тохируулах боломжтой. Зарим сэрүүлэгний апп нь энэ функцийг дэмжихгүй байж болзошгүй."</string>
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"дуут шуудан нэмэх"</string>
     <string name="permdesc_addVoicemail" msgid="5470312139820074324">"Таны дуут шуудангийн ирсэн мэйлд зурвас нэмэхийг апп-д зөвшөөрөх."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"Хөтчийн геобайршлын зөвшөөрлийг өөрчлөх"</string>
-    <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Апп нь Хөтчийн гео байршлын зөвшөөрлийг өөрчлөх боломжтой. Хортой апп нь энийг ашиглан дурын вэб хуудасруу байршлын мэдээллийг илгээх боломжтой."</string>
+    <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Апп нь Хөтчийн гео байршлын зөвшөөрлийг өөрчлөх боломжтой. Хортой апп нь энийг ашиглан дурын веб хуудасруу байршлын мэдээллийг илгээх боломжтой."</string>
     <string name="save_password_message" msgid="2146409467245462965">"Та хөтчид энэ нууц үгийг сануулах уу?"</string>
     <string name="save_password_notnow" msgid="2878327088951240061">"Одоо биш"</string>
     <string name="save_password_remember" msgid="6490888932657708341">"Санах"</string>
@@ -1459,7 +1459,7 @@
     <string name="progress_erasing" msgid="6891435992721028004">"Хуваалцсан хадгалах санг устгаж байна…"</string>
     <string name="share" msgid="4157615043345227321">"Хуваалцах"</string>
     <string name="find" msgid="5015737188624767706">"Олох"</string>
-    <string name="websearch" msgid="5624340204512793290">"Вэб хайлт"</string>
+    <string name="websearch" msgid="5624340204512793290">"Веб хайлт"</string>
     <string name="find_next" msgid="5341217051549648153">"Дараагийнхыг хайх"</string>
     <string name="find_previous" msgid="4405898398141275532">"Өмнөхөөс олох"</string>
     <string name="gpsNotifTicker" msgid="3207361857637620780">"<xliff:g id="NAME">%s</xliff:g>-н байршлын хүсэлт"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 2cdf709..092686b 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1389,7 +1389,7 @@
     <string name="ext_media_status_unmountable" msgid="7043574843541087748">"बिग्रेको"</string>
     <string name="ext_media_status_unsupported" msgid="5460509911660539317">"असमर्थित"</string>
     <string name="ext_media_status_ejecting" msgid="7532403368044013797">"निकाल्दै..."</string>
-    <string name="ext_media_status_formatting" msgid="774148701503179906">"फरम्याट गर्दै…"</string>
+    <string name="ext_media_status_formatting" msgid="774148701503179906">"फर्म्याट गर्दै…"</string>
     <string name="ext_media_status_missing" msgid="6520746443048867314">"सम्मिलित छैन"</string>
     <string name="activity_list_empty" msgid="4219430010716034252">"कुनै मिल्ने गतिविधि पाइएन।"</string>
     <string name="permlab_route_media_output" msgid="8048124531439513118">"मिडिया निकास दिशानिर्देश गराउनुहोस्"</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fc489b1..645bae7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4494,6 +4494,9 @@
      shown in the warning dialog about the accessibility shortcut. -->
     <string name="color_correction_feature_name">Color Correction</string>
 
+    <!-- Title of Reduce Bright Colors feature, shown in the warning dialog about the accessibility shortcut. [CHAR LIMIT=none] -->
+    <string name="reduce_bright_colors_feature_name">Reduce Bright Colors</string>
+
     <!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility service. [CHAR LIMIT=none] -->
     <string name="accessibility_shortcut_enabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned on.</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1249b17..9b52f54 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3249,6 +3249,7 @@
   <java-symbol type="string" name="accessibility_shortcut_disabling_service" />
   <java-symbol type="string" name="color_inversion_feature_name" />
   <java-symbol type="string" name="color_correction_feature_name" />
+  <java-symbol type="string" name="reduce_bright_colors_feature_name" />
   <java-symbol type="string" name="config_defaultAccessibilityService" />
   <java-symbol type="string" name="accessibility_shortcut_spoken_feedback" />
 
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 6f55b88..38dce15 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -164,6 +164,14 @@
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
             </intent-filter>
         </activity>
+        <activity android:name="android.view.ViewInputConnectionTestActivity"
+                  android:label="View Input Connection Test"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
         <activity android:name="StubTestBrowserActivity"
             android:label="Stubbed Test Browser"
             android:exported="true">
diff --git a/core/tests/coretests/res/layout/activity_view_ic_test.xml b/core/tests/coretests/res/layout/activity_view_ic_test.xml
new file mode 100644
index 0000000..fa26869
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_view_ic_test.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/root"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index 7fa1613..3df0a68 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -117,8 +117,8 @@
         history.addNotificationToWrite(n);
 
         assertThat(history.getNotificationsToWrite().size()).isEqualTo(2);
-        assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n2);
-        assertThat(history.getNotificationsToWrite().get(1)).isSameAs(n);
+        assertThat(history.getNotificationsToWrite().get(0)).isSameInstanceAs(n2);
+        assertThat(history.getNotificationsToWrite().get(1)).isSameInstanceAs(n);
         assertThat(history.getHistoryCount()).isEqualTo(2);
     }
 
@@ -141,11 +141,11 @@
         history.addNotificationsToWrite(secondHistory);
 
         assertThat(history.getNotificationsToWrite().size()).isEqualTo(5);
-        assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n3);
-        assertThat(history.getNotificationsToWrite().get(1)).isSameAs(n);
-        assertThat(history.getNotificationsToWrite().get(2)).isSameAs(n4);
-        assertThat(history.getNotificationsToWrite().get(3)).isSameAs(n2);
-        assertThat(history.getNotificationsToWrite().get(4)).isSameAs(n5);
+        assertThat(history.getNotificationsToWrite().get(0)).isSameInstanceAs(n3);
+        assertThat(history.getNotificationsToWrite().get(1)).isSameInstanceAs(n);
+        assertThat(history.getNotificationsToWrite().get(2)).isSameInstanceAs(n4);
+        assertThat(history.getNotificationsToWrite().get(3)).isSameInstanceAs(n2);
+        assertThat(history.getNotificationsToWrite().get(4)).isSameInstanceAs(n5);
         assertThat(history.getHistoryCount()).isEqualTo(5);
 
         assertThat(history.getPooledStringsToWrite()).asList().contains(n2.getChannelName());
diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
index ba060fa..593e70e 100644
--- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
@@ -45,7 +45,8 @@
                         CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
 
         assertThat(compoundFormula.getConnector()).isEqualTo(CompoundFormula.AND);
-        assertThat(compoundFormula.getFormulas()).containsAllOf(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2);
+        assertThat(compoundFormula.getFormulas())
+                .containsAtLeast(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt b/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
index d45fee9..9ad63ad 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
+++ b/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
@@ -113,7 +113,7 @@
         assertError(result)
         assertThat(result.errorCode).isEqualTo(errorCode)
         assertThat(result.errorMessage).isEqualTo(errorMessage)
-        assertThat(result.exception).isSameAs(exception)
+        assertThat(result.exception).isSameInstanceAs(exception)
     }
 
     @Test
@@ -125,13 +125,13 @@
         assertError(result)
         assertThat(result.errorCode).isEqualTo(errorCode)
         assertThat(result.errorMessage).isEqualTo(errorMessage)
-        assertThat(result.exception).isSameAs(exception)
+        assertThat(result.exception).isSameInstanceAs(exception)
 
         val carriedResult = input.error<Int>(result)
         assertError(carriedResult)
         assertThat(carriedResult.errorCode).isEqualTo(errorCode)
         assertThat(carriedResult.errorMessage).isEqualTo(errorMessage)
-        assertThat(carriedResult.exception).isSameAs(exception)
+        assertThat(carriedResult.exception).isSameInstanceAs(exception)
     }
 
     @Test
@@ -259,7 +259,7 @@
     private fun assertSuccess(expected: Any? = null, result: ParseResult<*>) {
         assertThat(result.isError).isFalse()
         assertThat(result.isSuccess).isTrue()
-        assertThat(result.result).isSameAs(expected)
+        assertThat(result.result).isSameInstanceAs(expected)
         assertThat(result.errorCode).isEqualTo(PackageManager.INSTALL_SUCCEEDED)
         assertThat(result.errorMessage).isNull()
         assertThat(result.exception).isNull()
diff --git a/core/tests/coretests/src/android/text/TextShaperTest.java b/core/tests/coretests/src/android/text/TextShaperTest.java
new file mode 100644
index 0000000..f92ea99
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextShaperTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.text;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.text.PositionedGlyphs;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextShaperTest {
+
+    @Test
+    public void testFontWithPath() {
+        TextPaint p = new TextPaint();
+        p.setFontFeatureSettings("'wght' 900");
+        List<PositionedGlyphs> glyphs = StyledTextShaper.shapeText("a", 0, 1,
+                TextDirectionHeuristics.LTR, p);
+        assertThat(glyphs.size()).isEqualTo(1);
+        // This test only passes if the font of the Latin font is variable font.
+        assertThat(glyphs.get(0).getFont(0).getFile()).isNotNull();
+
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
new file mode 100644
index 0000000..d667af3
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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 android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Handler;
+import android.text.format.DateUtils;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for internal APIs/behaviors of {@link View} and {@link InputConnection}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewInputConnectionTest {
+    @Rule
+    public ActivityTestRule<ViewInputConnectionTestActivity> mActivityRule =
+            new ActivityTestRule<>(ViewInputConnectionTestActivity.class);
+
+    private Instrumentation mInstrumentation;
+    private ViewInputConnectionTestActivity mActivity;
+    private InputMethodManager mImm;
+
+    @Before
+    public void before() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityRule.getActivity();
+        PollingCheck.waitFor(5 * DateUtils.SECOND_IN_MILLIS, mActivity::hasWindowFocus);
+        assertTrue(mActivity.hasWindowFocus());
+        mImm = mActivity.getSystemService(InputMethodManager.class);
+    }
+
+    @Test
+    public void testInputConnectionCallbacks() throws Throwable {
+        // Add two EditText inputs to the layout view.
+        final ViewGroup viewGroup = mActivity.findViewById(R.id.root);
+        final TestEditText editText1 = new TestEditText(mActivity, false);
+        final TestEditText editText2 = new TestEditText(mActivity, false);
+        mActivityRule.runOnUiThread(() -> {
+            viewGroup.addView(editText1);
+            viewGroup.addView(editText2);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Focus into the first EditText.
+        mActivityRule.runOnUiThread(editText1::requestFocus);
+        mInstrumentation.waitForIdleSync();
+        assertThat(editText1.isFocused()).isTrue();
+        assertThat(editText2.isFocused()).isFalse();
+
+        // Show the IME for the first EditText. Assert that the appropriate opened/closed callbacks
+        // have been invoked (InputConnection opened for the first EditText).
+        mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText1, 0));
+        mInstrumentation.waitForIdleSync();
+        mActivityRule.runOnUiThread(() -> {
+            assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+            assertThat(editText1.mCalledOnInputConnectionOpened).isTrue();
+            assertThat(editText1.mCalledOnInputConnectionClosed).isFalse();
+
+            assertThat(editText2.mCalledOnCreateInputConnection).isFalse();
+            assertThat(editText2.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+        });
+
+        // Focus into the second EditText.
+        mActivityRule.runOnUiThread(editText2::requestFocus);
+        mInstrumentation.waitForIdleSync();
+        assertThat(editText1.isFocused()).isFalse();
+        assertThat(editText2.isFocused()).isTrue();
+
+        // Show the IME for the second EditText. Assert that the appropriate opened/closed callbacks
+        // have been invoked (InputConnection closed for the first EditText and opened for the
+        // second EditText).
+        mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText2, 0));
+        mInstrumentation.waitForIdleSync();
+        mActivityRule.runOnUiThread(() -> {
+            assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+            assertThat(editText1.mCalledOnInputConnectionOpened).isTrue();
+            assertThat(editText1.mCalledOnInputConnectionClosed).isTrue();
+
+            assertThat(editText2.mCalledOnCreateInputConnection).isTrue();
+            assertThat(editText2.mCalledOnInputConnectionOpened).isTrue();
+            assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+        });
+    }
+
+    @Test
+    public void testInputConnectionCallbacks_nullInputConnection() throws Throwable {
+        // Add two EditText inputs to the layout view.
+        final ViewGroup viewGroup = mActivity.findViewById(R.id.root);
+        final TestEditText editText1 = new TestEditText(mActivity, true);
+        final TestEditText editText2 = new TestEditText(mActivity, true);
+        mActivityRule.runOnUiThread(() -> {
+            viewGroup.addView(editText1);
+            viewGroup.addView(editText2);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Focus into the first EditText.
+        mActivityRule.runOnUiThread(editText1::requestFocus);
+        mInstrumentation.waitForIdleSync();
+        assertThat(editText1.isFocused()).isTrue();
+        assertThat(editText2.isFocused()).isFalse();
+
+        // Show the IME for the first EditText. Assert that the opened/closed callbacks are not
+        // invoked since there's no input connection.
+        mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText1, 0));
+        mInstrumentation.waitForIdleSync();
+        mActivityRule.runOnUiThread(() -> {
+            assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+            assertThat(editText1.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(editText1.mCalledOnInputConnectionClosed).isFalse();
+
+            assertThat(editText2.mCalledOnCreateInputConnection).isFalse();
+            assertThat(editText2.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+        });
+
+        // Focus into the second EditText.
+        mActivityRule.runOnUiThread(editText2::requestFocus);
+        mInstrumentation.waitForIdleSync();
+        assertThat(editText1.isFocused()).isFalse();
+        assertThat(editText2.isFocused()).isTrue();
+
+        // Show the IME for the second EditText. Assert that the opened/closed callbacks are not
+        // invoked since there's no input connection.
+        mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText2, 0));
+        mInstrumentation.waitForIdleSync();
+        mActivityRule.runOnUiThread(() -> {
+            assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+            assertThat(editText1.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(editText1.mCalledOnInputConnectionClosed).isFalse();
+
+            assertThat(editText2.mCalledOnCreateInputConnection).isTrue();
+            assertThat(editText2.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+        });
+    }
+
+    @Test
+    public void testInputConnectionCallbacks_nonEditableInput() throws Throwable {
+        final ViewGroup viewGroup = mActivity.findViewById(R.id.root);
+        final TestButton view1 = new TestButton(mActivity);
+        final TestButton view2 = new TestButton(mActivity);
+        mActivityRule.runOnUiThread(() -> {
+            viewGroup.addView(view1);
+            viewGroup.addView(view2);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // Request focus + IME on the first view.
+        mActivityRule.runOnUiThread(view1::requestFocus);
+        mInstrumentation.waitForIdleSync();
+        assertThat(view1.isFocused()).isTrue();
+        assertThat(view2.isFocused()).isFalse();
+        mActivityRule.runOnUiThread(() -> mImm.showSoftInput(view1, 0));
+        mInstrumentation.waitForIdleSync();
+
+        // Assert that the opened/closed callbacks are not invoked since there's no InputConnection.
+        mActivityRule.runOnUiThread(() -> {
+            assertThat(view1.mCalledOnCreateInputConnection).isTrue();
+            assertThat(view1.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(view1.mCalledOnInputConnectionClosed).isFalse();
+
+            assertThat(view2.mCalledOnCreateInputConnection).isFalse();
+            assertThat(view2.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(view2.mCalledOnInputConnectionClosed).isFalse();
+        });
+
+        // Request focus + IME on the second view.
+        mActivityRule.runOnUiThread(view2::requestFocus);
+        mInstrumentation.waitForIdleSync();
+        assertThat(view1.isFocused()).isFalse();
+        assertThat(view2.isFocused()).isTrue();
+        mActivityRule.runOnUiThread(() -> mImm.showSoftInput(view1, 0));
+        mInstrumentation.waitForIdleSync();
+
+        // Assert that the opened/closed callbacks are not invoked since there's no InputConnection.
+        mActivityRule.runOnUiThread(() -> {
+            assertThat(view1.mCalledOnCreateInputConnection).isTrue();
+            assertThat(view1.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(view1.mCalledOnInputConnectionClosed).isFalse();
+
+            assertThat(view2.mCalledOnCreateInputConnection).isTrue();
+            assertThat(view2.mCalledOnInputConnectionOpened).isFalse();
+            assertThat(view2.mCalledOnInputConnectionClosed).isFalse();
+        });
+    }
+
+    private static class TestEditText extends EditText {
+        private final boolean mReturnNullInputConnection;
+
+        public boolean mCalledOnCreateInputConnection = false;
+        public boolean mCalledOnInputConnectionOpened = false;
+        public boolean mCalledOnInputConnectionClosed = false;
+
+        TestEditText(Context context, boolean returnNullInputConnection) {
+            super(context);
+            mReturnNullInputConnection = returnNullInputConnection;
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            mCalledOnCreateInputConnection = true;
+            if (mReturnNullInputConnection) {
+                return null;
+            } else {
+                return super.onCreateInputConnection(outAttrs);
+            }
+        }
+
+        @Override
+        public void onInputConnectionOpenedInternal(@NonNull InputConnection inputConnection,
+                @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
+            mCalledOnInputConnectionOpened = true;
+            super.onInputConnectionOpenedInternal(inputConnection, editorInfo, handler);
+        }
+
+        @Override
+        public void onInputConnectionClosedInternal() {
+            mCalledOnInputConnectionClosed = true;
+            super.onInputConnectionClosedInternal();
+        }
+    }
+
+    private static class TestButton extends Button {
+        public boolean mCalledOnCreateInputConnection = false;
+        public boolean mCalledOnInputConnectionOpened = false;
+        public boolean mCalledOnInputConnectionClosed = false;
+
+        TestButton(Context context) {
+            super(context);
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            mCalledOnCreateInputConnection = true;
+            return super.onCreateInputConnection(outAttrs);
+        }
+
+        @Override
+        public void onInputConnectionOpenedInternal(@NonNull InputConnection inputConnection,
+                @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
+            mCalledOnInputConnectionOpened = true;
+            super.onInputConnectionOpenedInternal(inputConnection, editorInfo, handler);
+        }
+
+        @Override
+        public void onInputConnectionClosedInternal() {
+            mCalledOnInputConnectionClosed = true;
+            super.onInputConnectionClosedInternal();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewInputConnectionTestActivity.java b/core/tests/coretests/src/android/view/ViewInputConnectionTestActivity.java
new file mode 100644
index 0000000..55c812d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewInputConnectionTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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 android.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+public class ViewInputConnectionTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_view_ic_test);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 628252d..402b92a 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -52,7 +52,8 @@
 
     @Test
     public void testGetLocalTextClassifier() {
-        assertThat(mTcm.getTextClassifier(TextClassifier.LOCAL)).isSameAs(TextClassifier.NO_OP);
+        assertThat(mTcm.getTextClassifier(TextClassifier.LOCAL))
+                .isSameInstanceAs(TextClassifier.NO_OP);
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index f108eb8..a2bc77a 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -81,7 +81,7 @@
         future.completeExceptionally(origException);
         ExecutionException executionException =
                 expectThrows(ExecutionException.class, future::get);
-        assertThat(executionException.getCause()).isSameAs(origException);
+        assertThat(executionException.getCause()).isSameInstanceAs(origException);
     }
 
     @Test
@@ -92,7 +92,7 @@
         CountDownLatch latch = new CountDownLatch(1);
         future.whenComplete((obj, err) -> {
             assertThat(obj).isNull();
-            assertThat(err).isSameAs(origException);
+            assertThat(err).isSameInstanceAs(origException);
             latch.countDown();
         });
         latch.await();
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 5914887..942045c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -107,7 +107,7 @@
             if (!isAlive) {
                 return false;
             }
-            assertThat(mRecipient).isSameAs(recipient);
+            assertThat(mRecipient).isSameInstanceAs(recipient);
             mRecipient = null;
             return true;
         }
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index 9f68ef3..7eca320 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -74,7 +74,7 @@
         assertThat(copy.mImeBackDisposition).isEqualTo(original.mImeBackDisposition);
         assertThat(copy.mShowImeSwitcher).isEqualTo(original.mShowImeSwitcher);
         assertThat(copy.mDisabledFlags2).isEqualTo(original.mDisabledFlags2);
-        assertThat(copy.mImeToken).isSameAs(original.mImeToken);
+        assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
         assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
         assertThat(copy.mAppFullscreen).isEqualTo(original.mAppFullscreen);
         assertThat(copy.mAppImmersive).isEqualTo(original.mAppImmersive);
diff --git a/core/tests/powertests/PowerStatsViewer/Android.bp b/core/tests/powertests/PowerStatsViewer/Android.bp
new file mode 100644
index 0000000..a3dc4fb
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/Android.bp
@@ -0,0 +1,13 @@
+android_test {
+    name: "PowerStatsViewer",
+    srcs: ["src/**/*.java"],
+    defaults: ["SettingsLibDefaults"],
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.cardview_cardview",
+        "androidx.recyclerview_recyclerview",
+        "com.google.android.material_material",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/core/tests/powertests/PowerStatsViewer/AndroidManifest.xml b/core/tests/powertests/PowerStatsViewer/AndroidManifest.xml
new file mode 100644
index 0000000..378d035
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.frameworks.core.powerstatsviewer"
+          android:sharedUserId="android.uid.system">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.BATTERY_STATS"/>
+
+    <application
+        android:theme="@style/Theme"
+        android:label="Power Stats Viewer">
+        <activity android:name=".PowerStatsViewerActivity"
+                  android:label="Power Stats Viewer"
+                  android:launchMode="singleTop"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".AppPickerActivity"
+                  android:label="Power Stats - Select an App"/>
+
+    </application>
+</manifest>
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/app_info_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/app_info_layout.xml
new file mode 100644
index 0000000..fe6fe2d
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/app_info_layout.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2018 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:background="?android:attr/selectableItemBackground"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/secondary_app_icon_size"
+        android:layout_height="@dimen/secondary_app_icon_size"
+        android:layout_marginEnd="12dp"
+        android:layout_marginTop="4dp"
+        android:layout_marginBottom="4dp"
+        android:gravity="start|center_vertical"/>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+        <TextView
+            android:id="@+id/uid"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+        <TextView
+            android:id="@+id/packages"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textDirection="locale"
+            android:maxLines="3"
+            android:ellipsize="end"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"/>
+
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/power_mah"
+        android:layout_width="100dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end|center_vertical"
+        android:layout_marginStart="16dp"
+        android:gravity="right"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:visibility="gone"/>
+
+</LinearLayout>
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/app_picker_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/app_picker_layout.xml
new file mode 100644
index 0000000..6f28999
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/app_picker_layout.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/app_list_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"/>
+
+    <ProgressBar
+        style="?android:attr/progressBarStyleLarge"
+        android:id="@+id/loading_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:indeterminate="true"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_entry_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_entry_layout.xml
new file mode 100644
index 0000000..1ced825
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_entry_layout.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingTop="8dp"
+    android:paddingBottom="8dp">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearanceBody"/>
+
+    <TextView
+        android:id="@+id/amount"
+        android:layout_width="0dp"
+        android:layout_weight="0.7"
+        android:layout_height="wrap_content"
+        android:gravity="right"
+        android:textAppearance="@style/TextAppearanceBody"/>
+
+    <TextView
+        android:id="@+id/percent"
+        android:layout_width="64dp"
+        android:layout_height="wrap_content"
+        android:gravity="right"
+        android:textAppearance="@style/TextAppearanceBody"/>
+</LinearLayout>
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_viewer_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_viewer_layout.xml
new file mode 100644
index 0000000..9949418
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_viewer_layout.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.cardview.widget.CardView
+            style="@style/LoadTestCardView"
+            android:id="@+id/app_card"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="10dp"
+            android:layout_marginEnd="10dp"
+            android:layout_marginBottom="10dp"
+            android:layout_marginStart="10dp"
+            android:padding="20dp">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:minHeight="80dp"
+                android:paddingStart="10dp"
+                android:paddingEnd="10dp">
+
+                <include layout="@layout/app_info_layout"/>
+
+            </LinearLayout>
+        </androidx.cardview.widget.CardView>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/power_stats_data_view"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"/>
+
+        <TextView
+            android:id="@+id/empty_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:visibility="gone"
+            android:text="No power stats available"/>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/loading_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#AAFFFFFF">
+        <ProgressBar
+            style="?android:attr/progressBarStyleLarge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:indeterminate="true"/>
+    </FrameLayout>
+</FrameLayout>
diff --git a/core/tests/powertests/PowerStatsViewer/res/values/styles.xml b/core/tests/powertests/PowerStatsViewer/res/values/styles.xml
new file mode 100644
index 0000000..629d729
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/values/styles.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <style name="Theme" parent="Theme.MaterialComponents.Light">
+        <item name="colorPrimary">#34a853</item>
+        <item name="android:windowActionBar">true</item>
+        <item name="android:windowNoTitle">false</item>
+    </style>
+
+    <style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView">
+        <item name="cardBackgroundColor">#ceead6</item>
+    </style>
+
+    <style name="TextAppearanceBody" parent="android:TextAppearance.DeviceDefault">
+        <item name="android:textColor">#000000</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppInfoHelper.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppInfoHelper.java
new file mode 100644
index 0000000..8526561
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppInfoHelper.java
@@ -0,0 +1,114 @@
+/*
+ * 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.frameworks.core.powerstatsviewer;
+
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+
+import com.android.internal.os.BatterySipper;
+
+class AppInfoHelper {
+
+    private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
+
+    public static class AppInfo {
+        public int uid;
+        public CharSequence label;
+        public double powerMah;
+        public ApplicationInfo iconInfo;
+        public CharSequence packages;
+    }
+
+    @Nullable
+    public static AppInfo makeApplicationInfo(PackageManager packageManager, int uid,
+            @Nullable BatterySipper sipper) {
+        if (sipper != null && sipper.drainType != BatterySipper.DrainType.APP) {
+            return null;
+        }
+
+        String packageWithHighestDrain = null;
+
+        AppInfo info = new AppInfo();
+        info.uid = uid;
+        if (sipper != null) {
+            sipper.sumPower();
+            info.powerMah = sipper.totalSmearedPowerMah;
+            packageWithHighestDrain = sipper.packageWithHighestDrain;
+        }
+        if (info.uid == Process.ROOT_UID) {
+            info.label = "<root>";
+        } else {
+            String[] packages = packageManager.getPackagesForUid(info.uid);
+            String primaryPackageName = null;
+            if (info.uid == Process.SYSTEM_UID) {
+                primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME;
+            } else if (packages != null) {
+                for (String name : packages) {
+                    primaryPackageName = name;
+                    if (name.equals(packageWithHighestDrain)) {
+                        break;
+                    }
+                }
+            }
+
+            if (primaryPackageName != null) {
+                try {
+                    ApplicationInfo applicationInfo =
+                            packageManager.getApplicationInfo(primaryPackageName, 0);
+                    info.label = applicationInfo.loadLabel(packageManager);
+                    info.iconInfo = applicationInfo;
+                } catch (PackageManager.NameNotFoundException e) {
+                    info.label = primaryPackageName;
+                }
+            } else if (packageWithHighestDrain != null) {
+                info.label = packageWithHighestDrain;
+            }
+
+            if (packages != null && packages.length > 0) {
+                StringBuilder sb = new StringBuilder();
+                if (primaryPackageName != null) {
+                    sb.append(primaryPackageName);
+                }
+                for (String packageName : packages) {
+                    if (packageName.equals(primaryPackageName)) {
+                        continue;
+                    }
+
+                    if (sb.length() != 0) {
+                        sb.append(", ");
+                    }
+                    sb.append(packageName);
+                }
+
+                info.packages = sb;
+            }
+        }
+
+        // Default the app icon to System Server. This includes root, dex2oat and other UIDs.
+        if (info.iconInfo == null) {
+            try {
+                info.iconInfo =
+                        packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0);
+            } catch (PackageManager.NameNotFoundException nameNotFoundException) {
+                // Won't happen
+            }
+        }
+        return info;
+    }
+}
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppPickerActivity.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppPickerActivity.java
new file mode 100644
index 0000000..b4fc73c
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppPickerActivity.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2008 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.frameworks.core.powerstatsviewer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.activity.ComponentActivity;
+import androidx.activity.result.contract.ActivityResultContract;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.frameworks.core.powerstatsviewer.AppInfoHelper.AppInfo;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Picker, showing a sorted list of applications consuming power.  Returns the selected
+ * application UID or Process.INVALID_UID.
+ */
+public class AppPickerActivity extends ComponentActivity {
+    private static final String TAG = "AppPicker";
+
+    public static final ActivityResultContract<Void, Integer> CONTRACT =
+            new ActivityResultContract<Void, Integer>() {
+                @NonNull
+                @Override
+                public Intent createIntent(@NonNull Context context, Void aVoid) {
+                    return new Intent(context, AppPickerActivity.class);
+                }
+
+                @Override
+                public Integer parseResult(int resultCode, @Nullable Intent intent) {
+                    if (resultCode != RESULT_OK || intent == null) {
+                        return Process.INVALID_UID;
+                    }
+                    return intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+                }
+            };
+
+    private AppListAdapter mAppListAdapter;
+    private RecyclerView mAppList;
+    private View mLoadingView;
+
+    private interface OnAppSelectedListener {
+        void onAppSelected(int uid);
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        getActionBar().setDisplayHomeAsUpEnabled(true);
+
+        setContentView(R.layout.app_picker_layout);
+
+        mLoadingView = findViewById(R.id.loading_view);
+
+        mAppList = findViewById(R.id.app_list_view);
+        mAppList.setLayoutManager(new LinearLayoutManager(this));
+        mAppListAdapter = new AppListAdapter(AppPickerActivity.this::setSelectedUid);
+        mAppList.setAdapter(mAppListAdapter);
+
+        LoaderManager.getInstance(this).initLoader(0, null,
+                new AppListLoaderCallbacks());
+    }
+
+    protected void setSelectedUid(int uid) {
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    @Override
+    public boolean onNavigateUp() {
+        onBackPressed();
+        return true;
+    }
+
+    private static class AppListLoader extends AsyncLoaderCompat<List<AppInfo>> {
+        private final BatteryStatsHelper mStatsHelper;
+        private final UserManager mUserManager;
+        private final PackageManager mPackageManager;
+
+        AppListLoader(Context context) {
+            super(context);
+            mUserManager = context.getSystemService(UserManager.class);
+            mStatsHelper = new BatteryStatsHelper(context, false /* collectBatteryBroadcast */);
+            mStatsHelper.create((Bundle) null);
+            mStatsHelper.clearStats();
+            mPackageManager = context.getPackageManager();
+        }
+
+        @Override
+        public List<AppInfo> loadInBackground() {
+            List<AppInfo> applicationList = new ArrayList<>();
+
+            mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
+                    mUserManager.getUserProfiles());
+
+            final List<BatterySipper> usageList = mStatsHelper.getUsageList();
+            for (BatterySipper sipper : usageList) {
+                AppInfo info =
+                        AppInfoHelper.makeApplicationInfo(mPackageManager, sipper.getUid(), sipper);
+                if (info != null) {
+                    applicationList.add(info);
+                }
+            }
+
+            applicationList.sort(
+                    Comparator.comparing((AppInfo a) -> a.powerMah).reversed());
+            return applicationList;
+        }
+
+        @Override
+        protected void onDiscardResult(List<AppInfo> result) {
+        }
+    }
+
+    private class AppListLoaderCallbacks implements
+            LoaderManager.LoaderCallbacks<List<AppInfo>> {
+
+        @NonNull
+        @Override
+        public Loader<List<AppInfo>> onCreateLoader(int id, Bundle args) {
+            return new AppListLoader(AppPickerActivity.this);
+        }
+
+        @Override
+        public void onLoadFinished(@NonNull Loader<List<AppInfo>> loader,
+                List<AppInfo> applicationList) {
+            mAppListAdapter.setApplicationList(applicationList);
+            mAppList.setVisibility(View.VISIBLE);
+            mLoadingView.setVisibility(View.GONE);
+        }
+
+        @Override
+        public void onLoaderReset(@NonNull Loader<List<AppInfo>> loader) {
+        }
+    }
+
+    public class AppListAdapter extends RecyclerView.Adapter<AppViewHolder> {
+        private final OnAppSelectedListener mListener;
+        private List<AppInfo> mApplicationList;
+
+        public AppListAdapter(OnAppSelectedListener listener) {
+            mListener = listener;
+        }
+
+        void setApplicationList(List<AppInfo> applicationList) {
+            mApplicationList = applicationList;
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getItemCount() {
+            return mApplicationList.size();
+        }
+
+        @NonNull
+        @Override
+        public AppViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
+            LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
+            View view = layoutInflater.inflate(R.layout.app_info_layout, viewGroup, false);
+            return new AppViewHolder(view, mListener);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull AppViewHolder appViewHolder, int position) {
+            AppInfo item = mApplicationList.get(position);
+            appViewHolder.uid = item.uid;
+            appViewHolder.titleView.setText(item.label);
+            appViewHolder.uidView.setText(
+                    String.format(Locale.getDefault(), "UID: %d", item.uid));
+            appViewHolder.powerView.setText(
+                    String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
+            appViewHolder.iconView.setImageDrawable(
+                    item.iconInfo.loadIcon(getPackageManager()));
+            if (item.packages != null) {
+                appViewHolder.packagesView.setText(item.packages);
+                appViewHolder.packagesView.setVisibility(View.VISIBLE);
+            } else {
+                appViewHolder.packagesView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    // View Holder used when displaying apps
+    public static class AppViewHolder extends RecyclerView.ViewHolder
+            implements View.OnClickListener {
+        private final OnAppSelectedListener mListener;
+
+        public int uid;
+        public TextView titleView;
+        public TextView uidView;
+        public ImageView iconView;
+        public TextView packagesView;
+        public TextView powerView;
+
+        AppViewHolder(View view, OnAppSelectedListener listener) {
+            super(view);
+            mListener = listener;
+            view.setOnClickListener(this);
+            titleView = view.findViewById(android.R.id.title);
+            uidView = view.findViewById(R.id.uid);
+            iconView = view.findViewById(android.R.id.icon);
+            packagesView = view.findViewById(R.id.packages);
+            powerView = view.findViewById(R.id.power_mah);
+            powerView.setVisibility(View.VISIBLE);
+        }
+
+        @Override
+        public void onClick(View v) {
+            mListener.onAppSelected(uid);
+        }
+    }
+}
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsData.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsData.java
new file mode 100644
index 0000000..09f20ba
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsData.java
@@ -0,0 +1,240 @@
+/*
+ * 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.frameworks.core.powerstatsviewer;
+
+import android.content.Context;
+import android.os.Process;
+
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PowerStatsData {
+    private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
+    private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
+    private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
+    private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
+            PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
+
+    enum EntryType {
+        POWER,
+        DURATION,
+    }
+
+    public static class Entry {
+        public String title;
+        public EntryType entryType;
+        public double value;
+        public double total;
+    }
+
+    private final AppInfoHelper.AppInfo mAppInfo;
+    private final List<Entry> mEntries = new ArrayList<>();
+
+    public PowerStatsData(Context context, BatteryStatsHelper batteryStatsHelper,
+            int uid) {
+        List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
+
+        double totalPowerMah = 0;
+        double totalSmearedPowerMah = 0;
+        double totalPowerExcludeSystemMah = 0;
+        double totalScreenPower = 0;
+        double totalProportionalSmearMah = 0;
+        double totalCpuPowerMah = 0;
+        double totalSystemServiceCpuPowerMah = 0;
+        double totalUsagePowerMah = 0;
+        double totalWakeLockPowerMah = 0;
+        double totalMobileRadioPowerMah = 0;
+        double totalWifiPowerMah = 0;
+        double totalBluetoothPowerMah = 0;
+        double totalGpsPowerMah = 0;
+        double totalCameraPowerMah = 0;
+        double totalFlashlightPowerMah = 0;
+        double totalSensorPowerMah = 0;
+        double totalAudioPowerMah = 0;
+        double totalVideoPowerMah = 0;
+
+        long totalCpuTimeMs = 0;
+        long totalCpuFgTimeMs = 0;
+        long totalWakeLockTimeMs = 0;
+        long totalWifiRunningTimeMs = 0;
+        long totalBluetoothRunningTimeMs = 0;
+        long totalGpsTimeMs = 0;
+        long totalCameraTimeMs = 0;
+        long totalFlashlightTimeMs = 0;
+        long totalAudioTimeMs = 0;
+        long totalVideoTimeMs = 0;
+
+        BatterySipper uidSipper = null;
+        for (BatterySipper sipper : usageList) {
+            if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
+                totalScreenPower = sipper.sumPower();
+            }
+
+            if (isHiddenDrainType(sipper.drainType)) {
+                continue;
+            }
+
+            if (sipper.drainType == BatterySipper.DrainType.APP && sipper.getUid() == uid) {
+                uidSipper = sipper;
+            }
+
+            totalPowerMah += sipper.sumPower();
+            totalSmearedPowerMah += sipper.totalSmearedPowerMah;
+            totalProportionalSmearMah += sipper.proportionalSmearMah;
+
+            if (!isSystemSipper(sipper)) {
+                totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah;
+            }
+
+            totalCpuPowerMah += sipper.cpuPowerMah;
+            totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah;
+            totalUsagePowerMah += sipper.usagePowerMah;
+            totalWakeLockPowerMah += sipper.wakeLockPowerMah;
+            totalMobileRadioPowerMah += sipper.mobileRadioPowerMah;
+            totalWifiPowerMah += sipper.wifiPowerMah;
+            totalBluetoothPowerMah += sipper.bluetoothPowerMah;
+            totalGpsPowerMah += sipper.gpsPowerMah;
+            totalCameraPowerMah += sipper.cameraPowerMah;
+            totalFlashlightPowerMah += sipper.flashlightPowerMah;
+            totalSensorPowerMah += sipper.sensorPowerMah;
+            totalAudioPowerMah += sipper.audioPowerMah;
+            totalVideoPowerMah += sipper.videoPowerMah;
+
+            totalCpuTimeMs += sipper.cpuTimeMs;
+            totalCpuFgTimeMs += sipper.cpuFgTimeMs;
+            totalWakeLockTimeMs += sipper.wakeLockTimeMs;
+            totalWifiRunningTimeMs += sipper.wifiRunningTimeMs;
+            totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs;
+            totalGpsTimeMs += sipper.gpsTimeMs;
+            totalCameraTimeMs += sipper.cameraTimeMs;
+            totalFlashlightTimeMs += sipper.flashlightTimeMs;
+            totalAudioTimeMs += sipper.audioTimeMs;
+            totalVideoTimeMs += sipper.videoTimeMs;
+        }
+
+        mAppInfo = AppInfoHelper.makeApplicationInfo(context.getPackageManager(), uid, uidSipper);
+
+        if (uidSipper == null) {
+            return;
+        }
+
+        addEntry("Total power", EntryType.POWER,
+                uidSipper.totalSmearedPowerMah, totalSmearedPowerMah);
+        addEntry("... excluding system", EntryType.POWER,
+                uidSipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
+        addEntry("Screen, smeared", EntryType.POWER,
+                uidSipper.screenPowerMah, totalScreenPower);
+        addEntry("Other, smeared", EntryType.POWER,
+                uidSipper.proportionalSmearMah, totalProportionalSmearMah);
+        addEntry("Excluding smeared", EntryType.POWER,
+                uidSipper.totalPowerMah, totalPowerMah);
+        addEntry("CPU", EntryType.POWER,
+                uidSipper.cpuPowerMah, totalCpuPowerMah);
+        addEntry("System services", EntryType.POWER,
+                uidSipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
+        addEntry("RAM", EntryType.POWER,
+                uidSipper.usagePowerMah, totalUsagePowerMah);
+        addEntry("Wake lock", EntryType.POWER,
+                uidSipper.wakeLockPowerMah, totalWakeLockPowerMah);
+        addEntry("Mobile radio", EntryType.POWER,
+                uidSipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
+        addEntry("WiFi", EntryType.POWER,
+                uidSipper.wifiPowerMah, totalWifiPowerMah);
+        addEntry("Bluetooth", EntryType.POWER,
+                uidSipper.bluetoothPowerMah, totalBluetoothPowerMah);
+        addEntry("GPS", EntryType.POWER,
+                uidSipper.gpsPowerMah, totalGpsPowerMah);
+        addEntry("Camera", EntryType.POWER,
+                uidSipper.cameraPowerMah, totalCameraPowerMah);
+        addEntry("Flashlight", EntryType.POWER,
+                uidSipper.flashlightPowerMah, totalFlashlightPowerMah);
+        addEntry("Sensors", EntryType.POWER,
+                uidSipper.sensorPowerMah, totalSensorPowerMah);
+        addEntry("Audio", EntryType.POWER,
+                uidSipper.audioPowerMah, totalAudioPowerMah);
+        addEntry("Video", EntryType.POWER,
+                uidSipper.videoPowerMah, totalVideoPowerMah);
+
+        addEntry("CPU time", EntryType.DURATION,
+                uidSipper.cpuTimeMs, totalCpuTimeMs);
+        addEntry("CPU foreground time", EntryType.DURATION,
+                uidSipper.cpuFgTimeMs, totalCpuFgTimeMs);
+        addEntry("Wake lock time", EntryType.DURATION,
+                uidSipper.wakeLockTimeMs, totalWakeLockTimeMs);
+        addEntry("WiFi running time", EntryType.DURATION,
+                uidSipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
+        addEntry("Bluetooth time", EntryType.DURATION,
+                uidSipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
+        addEntry("GPS time", EntryType.DURATION,
+                uidSipper.gpsTimeMs, totalGpsTimeMs);
+        addEntry("Camera time", EntryType.DURATION,
+                uidSipper.cameraTimeMs, totalCameraTimeMs);
+        addEntry("Flashlight time", EntryType.DURATION,
+                uidSipper.flashlightTimeMs, totalFlashlightTimeMs);
+        addEntry("Audio time", EntryType.DURATION,
+                uidSipper.audioTimeMs, totalAudioTimeMs);
+        addEntry("Video time", EntryType.DURATION,
+                uidSipper.videoTimeMs, totalVideoTimeMs);
+    }
+
+    protected boolean isHiddenDrainType(BatterySipper.DrainType drainType) {
+        return drainType == BatterySipper.DrainType.IDLE
+                || drainType == BatterySipper.DrainType.CELL
+                || drainType == BatterySipper.DrainType.SCREEN
+                || drainType == BatterySipper.DrainType.UNACCOUNTED
+                || drainType == BatterySipper.DrainType.OVERCOUNTED
+                || drainType == BatterySipper.DrainType.BLUETOOTH
+                || drainType == BatterySipper.DrainType.WIFI;
+    }
+
+    private boolean isSystemSipper(BatterySipper sipper) {
+        final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
+        if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
+            return true;
+        } else if (sipper.mPackages != null) {
+            for (final String packageName : sipper.mPackages) {
+                for (final String systemPackage : PACKAGES_SYSTEM) {
+                    if (systemPackage.equals(packageName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private void addEntry(String title, EntryType entryType, double amount, double totalAmount) {
+        Entry entry = new Entry();
+        entry.title = title;
+        entry.entryType = entryType;
+        entry.value = amount;
+        entry.total = totalAmount;
+        mEntries.add(entry);
+    }
+
+    public AppInfoHelper.AppInfo getAppInfo() {
+        return mAppInfo;
+    }
+
+    public List<Entry> getEntries() {
+        return mEntries;
+    }
+}
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsViewerActivity.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsViewerActivity.java
new file mode 100644
index 0000000..1605e9c
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsViewerActivity.java
@@ -0,0 +1,263 @@
+/*
+ * 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.frameworks.core.powerstatsviewer;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.activity.ComponentActivity;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.app.LoaderManager.LoaderCallbacks;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class PowerStatsViewerActivity extends ComponentActivity {
+    private static final int POWER_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
+    public static final String PREF_SELECTED_UID = "selectedUid";
+    private static final String LOADER_ARG_UID = "uid";
+
+    private PowerStatsDataAdapter mPowerStatsDataAdapter;
+    private Runnable mPowerStatsRefresh = this::periodicPowerStatsRefresh;
+    private SharedPreferences mSharedPref;
+    private int mUid = Process.INVALID_UID;
+    private TextView mTitleView;
+    private TextView mUidView;
+    private ImageView mIconView;
+    private TextView mPackagesView;
+    private RecyclerView mPowerStatsDataView;
+    private View mLoadingView;
+    private View mEmptyView;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mSharedPref = getPreferences(Context.MODE_PRIVATE);
+
+        setContentView(R.layout.power_stats_viewer_layout);
+
+        View appCard = findViewById(R.id.app_card);
+        appCard.setOnClickListener((e) -> startAppPicker());
+
+        mTitleView = findViewById(android.R.id.title);
+        mUidView = findViewById(R.id.uid);
+        mIconView = findViewById(android.R.id.icon);
+        mPackagesView = findViewById(R.id.packages);
+
+        mPowerStatsDataView = findViewById(R.id.power_stats_data_view);
+        mPowerStatsDataView.setLayoutManager(new LinearLayoutManager(this));
+        mPowerStatsDataAdapter = new PowerStatsDataAdapter();
+        mPowerStatsDataView.setAdapter(mPowerStatsDataAdapter);
+
+        mLoadingView = findViewById(R.id.loading_view);
+        mEmptyView = findViewById(R.id.empty_view);
+
+        mUid = mSharedPref.getInt(PREF_SELECTED_UID, Process.INVALID_UID);
+        loadPowerStats();
+        if (mUid == Process.INVALID_UID) {
+            startAppPicker();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        periodicPowerStatsRefresh();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        getMainThreadHandler().removeCallbacks(mPowerStatsRefresh);
+    }
+
+    private void startAppPicker() {
+        registerForActivityResult(AppPickerActivity.CONTRACT, this::onApplicationSelected)
+                .launch(null);
+    }
+
+    private void onApplicationSelected(int uid) {
+        if (uid == -1) {
+            if (mUid == Process.INVALID_UID) {
+                finish();
+            }
+        } else {
+            mUid = uid;
+            mSharedPref.edit().putInt(PREF_SELECTED_UID, mUid).apply();
+            mLoadingView.setVisibility(View.VISIBLE);
+            loadPowerStats();
+        }
+    }
+
+    private void periodicPowerStatsRefresh() {
+        loadPowerStats();
+        getMainThreadHandler().postDelayed(mPowerStatsRefresh, POWER_STATS_REFRESH_RATE_MILLIS);
+    }
+
+    private void loadPowerStats() {
+        Bundle args = new Bundle();
+        args.putInt(LOADER_ARG_UID, mUid);
+        LoaderManager.getInstance(this).restartLoader(0, args, new PowerStatsDataLoaderCallbacks());
+    }
+
+    private static class PowerStatsDataLoader extends AsyncLoaderCompat<PowerStatsData> {
+        private final int mUid;
+        private final BatteryStatsHelper mBatteryStatsHelper;
+        private final UserManager mUserManager;
+
+        PowerStatsDataLoader(Context context, int uid) {
+            super(context);
+            mUid = uid;
+            mUserManager = context.getSystemService(UserManager.class);
+            mBatteryStatsHelper = new BatteryStatsHelper(context,
+                    false /* collectBatteryBroadcast */);
+            mBatteryStatsHelper.create((Bundle) null);
+            mBatteryStatsHelper.clearStats();
+        }
+
+        @Override
+        public PowerStatsData loadInBackground() {
+            mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
+                    mUserManager.getUserProfiles());
+            return new PowerStatsData(getContext(), mBatteryStatsHelper, mUid);
+        }
+
+        @Override
+        protected void onDiscardResult(PowerStatsData result) {
+        }
+    }
+
+    private class PowerStatsDataLoaderCallbacks implements LoaderCallbacks<PowerStatsData> {
+        @NonNull
+        @Override
+        public Loader<PowerStatsData> onCreateLoader(int id, Bundle args) {
+            return new PowerStatsDataLoader(PowerStatsViewerActivity.this,
+                    args.getInt(LOADER_ARG_UID, Process.INVALID_UID));
+        }
+
+        @Override
+        public void onLoadFinished(@NonNull Loader<PowerStatsData> loader,
+                PowerStatsData powerStatsData) {
+
+            AppInfoHelper.AppInfo appInfo = powerStatsData.getAppInfo();
+            mTitleView.setText(appInfo.label);
+            mUidView.setText(String.format(Locale.getDefault(), "UID: %d", appInfo.uid));
+            mIconView.setImageDrawable(appInfo.iconInfo.loadIcon(getPackageManager()));
+
+            if (appInfo.packages != null) {
+                mPackagesView.setText(appInfo.packages);
+                mPackagesView.setVisibility(View.VISIBLE);
+            } else {
+                mPackagesView.setVisibility(View.GONE);
+            }
+
+            mPowerStatsDataAdapter.setEntries(powerStatsData.getEntries());
+
+            if (powerStatsData.getEntries().isEmpty()) {
+                mEmptyView.setVisibility(View.VISIBLE);
+                mPowerStatsDataView.setVisibility(View.GONE);
+            } else {
+                mEmptyView.setVisibility(View.GONE);
+                mPowerStatsDataView.setVisibility(View.VISIBLE);
+            }
+
+            mLoadingView.setVisibility(View.GONE);
+        }
+
+        @Override
+        public void onLoaderReset(@NonNull Loader<PowerStatsData> loader) {
+        }
+    }
+
+    private static class PowerStatsDataAdapter extends
+            RecyclerView.Adapter<PowerStatsDataAdapter.ViewHolder> {
+        public static class ViewHolder extends RecyclerView.ViewHolder {
+            public TextView titleTextView;
+            public TextView amountTextView;
+            public TextView percentTextView;
+
+            ViewHolder(View itemView) {
+                super(itemView);
+
+                titleTextView = itemView.findViewById(R.id.title);
+                amountTextView = itemView.findViewById(R.id.amount);
+                percentTextView = itemView.findViewById(R.id.percent);
+            }
+        }
+
+        private List<PowerStatsData.Entry> mEntries = Collections.emptyList();
+
+        public void setEntries(List<PowerStatsData.Entry> entries) {
+            mEntries = entries;
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getItemCount() {
+            return mEntries.size();
+        }
+
+        @NonNull
+        @Override
+        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
+            LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+            View itemView = layoutInflater.inflate(R.layout.power_stats_entry_layout, parent,
+                    false);
+            return new ViewHolder(itemView);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
+            PowerStatsData.Entry entry = mEntries.get(position);
+            switch (entry.entryType) {
+                case POWER:
+                    viewHolder.titleTextView.setText(entry.title);
+                    viewHolder.amountTextView.setText(
+                            String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+                    break;
+                case DURATION:
+                    viewHolder.titleTextView.setText(entry.title);
+                    viewHolder.amountTextView.setText(
+                            String.format(Locale.getDefault(), "%,d ms", (long) entry.value));
+                    break;
+            }
+
+            double proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0;
+            viewHolder.percentTextView.setText(String.format(Locale.getDefault(), "%.1f%%",
+                    proportion));
+        }
+    }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 59727d5..31dae22 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -301,12 +301,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
-    "-1741065110": {
-      "message": "No app is requesting an orientation, return %d for display id=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "-1730156332": {
       "message": "Display id=%d rotation changed to %d from %d, lastOrientation=%d",
       "level": "VERBOSE",
@@ -529,6 +523,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "-1480772131": {
+      "message": "No app or window is requesting an orientation, return %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-1474292612": {
       "message": "Could not find task for id: %d",
       "level": "DEBUG",
@@ -2515,12 +2515,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowToken.java"
     },
-    "845234215": {
-      "message": "App is requesting an orientation, return %d for display id=%d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ORIENTATION",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "849147756": {
       "message": "Finish collecting in transition %d",
       "level": "VERBOSE",
@@ -2923,6 +2917,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
     },
+    "1381227466": {
+      "message": "App is requesting an orientation, return %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
+    },
     "1401295262": {
       "message": "Mode default, asking user",
       "level": "WARN",
@@ -3133,6 +3133,18 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "1640436199": {
+      "message": "No app is requesting an orientation, return %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
+    },
+    "1648338379": {
+      "message": "Display id=%d is ignoring all orientation requests, return %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "1653210583": {
       "message": "Removing app %s delayed=%b animation=%s animating=%b",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index f768bc7..0061ea1 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -40,7 +40,7 @@
  */
 public final class FrameInfo {
 
-    public long[] frameInfo = new long[10];
+    public long[] frameInfo = new long[FRAME_INFO_SIZE];
 
     // Various flags set to provide extra metadata about the current frame
     private static final int FLAGS = 0;
@@ -87,14 +87,22 @@
     // When View:draw() started
     private static final int DRAW_START = 9;
 
+    // When the frame needs to be ready by
+    private static final int FRAME_DEADLINE = 10;
+
+    // Must be the last one
+    private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1;
+
     /** checkstyle */
-    public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId) {
+    public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
+            long frameDeadline) {
         frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
         frameInfo[INTENDED_VSYNC] = intendedVsync;
         frameInfo[VSYNC] = usedVsync;
         frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
         frameInfo[NEWEST_INPUT_EVENT] = 0;
         frameInfo[FLAGS] = 0;
+        frameInfo[FRAME_DEADLINE] = frameDeadline;
     }
 
     /** checkstyle */
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index fd5916c..a7f2739 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -355,7 +355,7 @@
          */
         public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
             // TODO(b/168552873): populate vsync Id once available to Choreographer public API
-            mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID);
+            mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE);
             mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
             return this;
         }
diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java
index d408ac3..3260849 100644
--- a/graphics/java/android/graphics/ParcelableColorSpace.java
+++ b/graphics/java/android/graphics/ParcelableColorSpace.java
@@ -73,6 +73,9 @@
         }
     }
 
+    /**
+     * @return the backing ColorSpace that this ParcelableColorSpace is wrapping.
+     */
     public @NonNull ColorSpace getColorSpace() {
         return mColorSpace;
     }
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 21b8fc6..97cd8ab 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -687,7 +687,9 @@
                 charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32);
                 axes[i] = new FontVariationAxis(new String(charBuffer), value);
             }
-            Font.Builder builder = new Font.Builder(buffer)
+            String path = nGetFontPath(ptr);
+            File file = (path == null) ? null : new File(path);
+            Font.Builder builder = new Font.Builder(buffer, file, "")
                     .setWeight(weight)
                     .setSlant(italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)
                     .setTtcIndex(ttcIndex)
@@ -712,6 +714,9 @@
     private static native long nGetAxisInfo(long ptr, int i);
 
     @FastNative
+    private static native String nGetFontPath(long ptr);
+
+    @FastNative
     private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect);
 
     @FastNative
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index bcef154..44744bc 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -7,6 +7,12 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
     },
+    "-1683614271": {
+      "message": "Existing task: id=%d component=%s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    },
     "-1534364071": {
       "message": "onTransitionReady %s: %s",
       "level": "VERBOSE",
@@ -61,6 +67,12 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
     },
+    "580605218": {
+      "message": "Registering organizer",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TASK_ORG",
+      "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+    },
     "980952660": {
       "message": "Task root back pressed taskId=%d",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 7ce65fd..d87de5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -28,12 +28,14 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration.WindowingMode;
 import android.util.Log;
-import android.util.Pair;
 import android.util.SparseArray;
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
@@ -42,6 +44,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Unified task organizer for all components in the shell.
@@ -82,7 +85,7 @@
 
     // Keeps track of all the tasks reported to this organizer (changes in windowing mode will
     // require us to report to both old and new listeners)
-    private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>();
+    private final SparseArray<TaskAppearedInfo> mTasks = new SparseArray<>();
 
     // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks
     private final Transitions mTransitions;
@@ -102,6 +105,19 @@
         if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
     }
 
+    @Override
+    public List<TaskAppearedInfo> registerOrganizer() {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Registering organizer");
+        final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
+        for (int i = 0; i < taskInfos.size(); i++) {
+            final TaskAppearedInfo info = taskInfos.get(i);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Existing task: id=%d component=%s",
+                    info.getTaskInfo().taskId, info.getTaskInfo().baseIntent);
+            onTaskAppeared(info.getTaskInfo(), info.getLeash());
+        }
+        return taskInfos;
+    }
+
     /**
      * Adds a listener for tasks with given types.
      */
@@ -117,10 +133,11 @@
 
             // Notify the listener of all existing tasks with the given type.
             for (int i = mTasks.size() - 1; i >= 0; i--) {
-                Pair<RunningTaskInfo, SurfaceControl> data = mTasks.valueAt(i);
-                final @TaskListenerType int taskListenerType = getTaskListenerType(data.first);
+                TaskAppearedInfo data = mTasks.valueAt(i);
+                final @TaskListenerType int taskListenerType = getTaskListenerType(
+                        data.getTaskInfo());
                 if (taskListenerType == listenerType) {
-                    listener.onTaskAppeared(data.first, data.second);
+                    listener.onTaskAppeared(data.getTaskInfo(), data.getLeash());
                 }
             }
         }
@@ -143,7 +160,7 @@
     public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task appeared taskId=%d",
                 taskInfo.taskId);
-        mTasks.put(taskInfo.taskId, new Pair<>(taskInfo, leash));
+        mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, leash));
         final TaskListener listener = mTaskListenersByType.get(getTaskListenerType(taskInfo));
         if (listener != null) {
             listener.onTaskAppeared(taskInfo, leash);
@@ -154,10 +171,10 @@
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task info changed taskId=%d",
                 taskInfo.taskId);
-        final Pair<RunningTaskInfo, SurfaceControl> data = mTasks.get(taskInfo.taskId);
+        final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
         final @TaskListenerType int listenerType = getTaskListenerType(taskInfo);
-        final @TaskListenerType int prevListenerType = getTaskListenerType(data.first);
-        mTasks.put(taskInfo.taskId, new Pair<>(taskInfo, data.second));
+        final @TaskListenerType int prevListenerType = getTaskListenerType(data.getTaskInfo());
+        mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash()));
         if (prevListenerType != listenerType) {
             // TODO: We currently send vanished/appeared as the task moves between types, but
             //       we should consider adding a different mode-changed callback
@@ -167,7 +184,7 @@
             }
             listener = mTaskListenersByType.get(listenerType);
             if (listener != null) {
-                SurfaceControl leash = data.second;
+                SurfaceControl leash = data.getLeash();
                 listener.onTaskAppeared(taskInfo, leash);
             }
         } else {
@@ -193,7 +210,7 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task vanished taskId=%d",
                 taskInfo.taskId);
         final @TaskListenerType int prevListenerType =
-                getTaskListenerType(mTasks.get(taskInfo.taskId).first);
+                getTaskListenerType(mTasks.get(taskInfo.taskId).getTaskInfo());
         mTasks.remove(taskInfo.taskId);
         final TaskListener listener = mTaskListenersByType.get(prevListenerType);
         if (listener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
new file mode 100644
index 0000000..10e5c3d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -0,0 +1,49 @@
+/*
+ * 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.wm.shell.pip;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+
+import java.io.PrintWriter;
+
+/**
+ * Singleton source of truth for the current state of PIP bounds.
+ */
+public final class PipBoundsState {
+    private static final String TAG = PipBoundsState.class.getSimpleName();
+
+    private final @NonNull Rect mBounds = new Rect();
+
+    void setBounds(@NonNull Rect bounds) {
+        mBounds.set(bounds);
+    }
+
+    @NonNull
+    public Rect getBounds() {
+        return new Rect(mBounds);
+    }
+
+    /**
+     * Dumps internal state.
+     */
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mBounds=" + mBounds);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 846da0a..3485c7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -134,11 +134,11 @@
 
     private final Handler mMainHandler;
     private final Handler mUpdateHandler;
+    private final PipBoundsState mPipBoundsState;
     private final PipBoundsHandler mPipBoundsHandler;
     private final PipAnimationController mPipAnimationController;
     private final PipUiEventLogger mPipUiEventLoggerLogger;
     private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
-    private final Rect mLastReportedBounds = new Rect();
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
@@ -262,7 +262,8 @@
      */
     private boolean mShouldIgnoreEnteringPipTransition;
 
-    public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
+    public PipTaskOrganizer(Context context, @NonNull PipBoundsState pipBoundsState,
+            @NonNull PipBoundsHandler boundsHandler,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
             Optional<SplitScreen> splitScreenOptional,
             @NonNull DisplayController displayController,
@@ -270,6 +271,7 @@
             @NonNull ShellTaskOrganizer shellTaskOrganizer) {
         mMainHandler = new Handler(Looper.getMainLooper());
         mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
+        mPipBoundsState = pipBoundsState;
         mPipBoundsHandler = boundsHandler;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
@@ -292,17 +294,13 @@
         return mUpdateHandler;
     }
 
-    public Rect getLastReportedBounds() {
-        return new Rect(mLastReportedBounds);
-    }
-
     public Rect getCurrentOrAnimatingBounds() {
         PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getCurrentAnimator();
         if (animator != null && animator.isRunning()) {
             return new Rect(animator.getDestinationBounds());
         }
-        return getLastReportedBounds();
+        return mPipBoundsState.getBounds();
     }
 
     public boolean isInPip() {
@@ -347,7 +345,7 @@
      * Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
      */
     public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
-        mLastReportedBounds.set(destinationBounds);
+        mPipBoundsState.setBounds(destinationBounds);
     }
 
     /**
@@ -394,7 +392,7 @@
             final SurfaceControl.Transaction tx =
                     mSurfaceControlTransactionFactory.getTransaction();
             mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
-                    mLastReportedBounds);
+                    mPipBoundsState.getBounds());
             tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
             // We set to fullscreen here for now, but later it will be set to UNDEFINED for
             // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
@@ -408,9 +406,9 @@
                 @Override
                 public void onTransactionReady(int id, SurfaceControl.Transaction t) {
                     t.apply();
-                    scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
-                            getValidSourceHintRect(mTaskInfo, destinationBounds), direction,
-                            animationDurationMs, null /* updateBoundsCallback */);
+                    scheduleAnimateResizePip(mPipBoundsState.getBounds(),
+                            destinationBounds, getValidSourceHintRect(mTaskInfo, destinationBounds),
+                            direction, animationDurationMs, null /* updateBoundsCallback */);
                     mState = State.EXITING_PIP;
                 }
             });
@@ -441,7 +439,7 @@
 
         // removePipImmediately is expected when the following animation finishes.
         mUpdateHandler.post(() -> mPipAnimationController
-                .getAnimator(mLeash, mLastReportedBounds, 1f, 0f)
+                .getAnimator(mLeash, mPipBoundsState.getBounds(), 1f, 0f)
                 .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration)
@@ -480,7 +478,7 @@
         if (mShouldIgnoreEnteringPipTransition) {
             // Animation has been finished together with Recents, directly apply the sync
             // transaction to PiP here.
-            applyEnterPipSyncTransaction(mLastReportedBounds, () -> {
+            applyEnterPipSyncTransaction(mPipBoundsState.getBounds(), () -> {
                 mState = State.ENTERED_PIP;
             });
             mShouldIgnoreEnteringPipTransition = false;
@@ -572,7 +570,7 @@
 
     private void sendOnPipTransitionStarted(
             @PipAnimationController.TransitionDirection int direction) {
-        final Rect pipBounds = new Rect(mLastReportedBounds);
+        final Rect pipBounds = mPipBoundsState.getBounds();
         runOnMainHandler(() -> {
             for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
                 final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
@@ -701,7 +699,7 @@
         }
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
                 info.topActivity, getAspectRatioOrDefault(newParams),
-                mLastReportedBounds, getMinimalSize(info.topActivityInfo),
+                mPipBoundsState.getBounds(), getMinimalSize(info.topActivityInfo),
                 true /* userCurrentMinEdgeSize */);
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
@@ -759,7 +757,7 @@
                     sendOnPipTransitionCancelled(direction);
                     sendOnPipTransitionFinished(direction);
                 }
-                mLastReportedBounds.set(destinationBoundsOut);
+                mPipBoundsState.setBounds(destinationBoundsOut);
 
                 // Create a reset surface transaction for the new bounds and update the window
                 // container transaction
@@ -774,8 +772,8 @@
                         destinationBoundsOut.set(animator.getDestinationBounds());
                     }
                 } else {
-                    if (!mLastReportedBounds.isEmpty()) {
-                        destinationBoundsOut.set(mLastReportedBounds);
+                    if (!mPipBoundsState.getBounds().isEmpty()) {
+                        destinationBoundsOut.set(mPipBoundsState.getBounds());
                     }
                 }
             }
@@ -827,7 +825,7 @@
             Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
             return;
         }
-        scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */,
+        scheduleAnimateResizePip(mPipBoundsState.getBounds(), toBounds, null /* sourceHintRect */,
                 TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
     }
 
@@ -963,7 +961,7 @@
             Log.w(TAG, "Abort animation, invalid leash");
             return;
         }
-        mLastReportedBounds.set(destinationBounds);
+        mPipBoundsState.setBounds(destinationBounds);
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, destinationBounds)
@@ -999,7 +997,7 @@
             throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
                     + "directly");
         }
-        mLastReportedBounds.set(destinationBounds);
+        mPipBoundsState.setBounds(destinationBounds);
         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
             removePipImmediately();
             return;
@@ -1141,7 +1139,6 @@
         pw.println(innerPrefix + "mState=" + mState);
         pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
-        pw.println(innerPrefix + "mLastReportedBounds=" + mLastReportedBounds);
         pw.println(innerPrefix + "mInitialState:");
         for (Map.Entry<IBinder, Configuration> e : mInitialState.entrySet()) {
             pw.println(innerPrefix + "  binder=" + e.getKey()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index fddd547..18b6922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.pip.phone;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -28,6 +29,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 
@@ -50,6 +52,7 @@
 
     private Context mContext;
     private Handler mHandler;
+    private final @NonNull PipBoundsState mPipBoundsState;
     private PipMotionHelper mMotionHelper;
     private PipTaskOrganizer mTaskOrganizer;
     private PipSnapAlgorithm mSnapAlgorithm;
@@ -62,12 +65,14 @@
     private final Rect mExpandedMovementBounds = new Rect();
     private Rect mTmpBounds = new Rect();
 
-    public PipAccessibilityInteractionConnection(Context context, PipMotionHelper motionHelper,
+    public PipAccessibilityInteractionConnection(Context context,
+            @NonNull PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
             PipTaskOrganizer taskOrganizer, PipSnapAlgorithm snapAlgorithm,
             AccessibilityCallbacks callbacks, Runnable updateMovementBoundCallback,
             Handler handler) {
         mContext = context;
         mHandler = handler;
+        mPipBoundsState = pipBoundsState;
         mMotionHelper = motionHelper;
         mTaskOrganizer = taskOrganizer;
         mSnapAlgorithm = snapAlgorithm;
@@ -148,7 +153,7 @@
 
     private void setToExpandedBounds() {
         float savedSnapFraction = mSnapAlgorithm.getSnapFraction(
-                new Rect(mTaskOrganizer.getLastReportedBounds()), mNormalMovementBounds);
+                mPipBoundsState.getBounds(), mNormalMovementBounds);
         mSnapAlgorithm.applySnapFraction(mExpandedBounds, mExpandedMovementBounds,
                 savedSnapFraction);
         mTaskOrganizer.scheduleFinishResizePip(mExpandedBounds, (Rect bounds) -> {
@@ -159,7 +164,7 @@
 
     private void setToNormalBounds() {
         float savedSnapFraction = mSnapAlgorithm.getSnapFraction(
-                new Rect(mTaskOrganizer.getLastReportedBounds()), mExpandedMovementBounds);
+                mPipBoundsState.getBounds(), mExpandedMovementBounds);
         mSnapAlgorithm.applySnapFraction(mNormalBounds, mNormalMovementBounds, savedSnapFraction);
         mTaskOrganizer.scheduleFinishResizePip(mNormalBounds, (Rect bounds) -> {
             mMotionHelper.synchronizePinnedStackBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 41c0a88..d191c98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -20,6 +20,7 @@
 
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
@@ -44,6 +45,7 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 
 import java.io.PrintWriter;
@@ -66,6 +68,7 @@
     private DisplayController mDisplayController;
     private PipAppOpsListener mAppOpsListener;
     private PipBoundsHandler mPipBoundsHandler;
+    private @NonNull PipBoundsState mPipBoundsState;
     private PipMediaController mMediaController;
     private PipTouchHandler mTouchHandler;
     private Consumer<Boolean> mPinnedStackAnimationRecentsCallback;
@@ -98,7 +101,7 @@
             // If the pip was in the offset zone earlier, adjust the new bounds to the bottom of the
             // movement bounds
             mTouchHandler.adjustBoundsForRotation(mTmpNormalBounds,
-                    mPipTaskOrganizer.getLastReportedBounds(), mTmpInsetBounds);
+                    mPipBoundsState.getBounds(), mTmpInsetBounds);
 
             // The bounds are being applied to a specific snap fraction, so reset any known offsets
             // for the previous orientation before updating the movement bounds.
@@ -197,6 +200,7 @@
             DisplayController displayController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsHandler pipBoundsHandler,
+            @NonNull PipBoundsState pipBoundsState,
             PipMediaController pipMediaController,
             PipMenuActivityController pipMenuActivityController,
             PipTaskOrganizer pipTaskOrganizer,
@@ -207,7 +211,7 @@
 
         if (PipUtils.hasSystemFeature(mContext)) {
             initController(context, displayController, pipAppOpsListener, pipBoundsHandler,
-                    pipMediaController, pipMenuActivityController, pipTaskOrganizer,
+                    pipBoundsState, pipMediaController, pipMenuActivityController, pipTaskOrganizer,
                     pipTouchHandler, windowManagerShellWrapper);
         } else {
             Log.w(TAG, "Device not support PIP feature");
@@ -218,6 +222,7 @@
             DisplayController displayController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsHandler pipBoundsHandler,
+            @NonNull PipBoundsState pipBoundsState,
             PipMediaController pipMediaController,
             PipMenuActivityController pipMenuActivityController,
             PipTaskOrganizer pipTaskOrganizer,
@@ -233,6 +238,7 @@
         mWindowManagerShellWrapper = windowManagerShellWrapper;
         mDisplayController = displayController;
         mPipBoundsHandler = pipBoundsHandler;
+        mPipBoundsState = pipBoundsState;
         mPipTaskOrganizer = pipTaskOrganizer;
         mPipTaskOrganizer.registerPipTransitionCallback(this);
         mMediaController = pipMediaController;
@@ -361,7 +367,7 @@
         final boolean changed = mPipBoundsHandler.setShelfHeight(visible, shelfHeight);
         if (changed) {
             mTouchHandler.onShelfVisibilityChanged(visible, shelfHeight);
-            updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(),
+            updateMovementBounds(mPipBoundsState.getBounds(),
                     false /* fromRotation */, false /* fromImeAdjustment */,
                     true /* fromShelfAdjustment */, null /* windowContainerTransaction */);
         }
@@ -457,5 +463,6 @@
         mTouchHandler.dump(pw, innerPrefix);
         mPipBoundsHandler.dump(pw, innerPrefix);
         mPipTaskOrganizer.dump(pw, innerPrefix);
+        mPipBoundsState.dump(pw, innerPrefix);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index fe1d44c7..b5fa030 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -37,6 +37,7 @@
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 
@@ -66,6 +67,7 @@
 
     private final Context mContext;
     private final PipTaskOrganizer mPipTaskOrganizer;
+    private final @NonNull PipBoundsState mPipBoundsState;
 
     private PipMenuActivityController mMenuController;
     private PipSnapAlgorithm mSnapAlgorithm;
@@ -178,11 +180,12 @@
         public void onPipTransitionCanceled(ComponentName activity, int direction) {}
     };
 
-    public PipMotionHelper(Context context, PipTaskOrganizer pipTaskOrganizer,
-            PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm,
-            FloatingContentCoordinator floatingContentCoordinator) {
+    public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+            PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
+            PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) {
         mContext = context;
         mPipTaskOrganizer = pipTaskOrganizer;
+        mPipBoundsState = pipBoundsState;
         mMenuController = menuController;
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
@@ -220,7 +223,7 @@
      */
     void synchronizePinnedStackBounds() {
         cancelAnimations();
-        mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
+        mBounds.set(mPipBoundsState.getBounds());
         mTemporaryBounds.setEmpty();
 
         if (mPipTaskOrganizer.isInPip()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 07beb43..a2233e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -22,6 +22,7 @@
 import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
 import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
 
+import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.content.Context;
@@ -48,6 +49,7 @@
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipUiEventLogger;
 
@@ -70,6 +72,7 @@
     private final boolean mEnableResize;
     private final Context mContext;
     private final PipBoundsHandler mPipBoundsHandler;
+    private final @NonNull PipBoundsState mPipBoundsState;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
 
@@ -161,6 +164,7 @@
     public PipTouchHandler(Context context,
             PipMenuActivityController menuController,
             PipBoundsHandler pipBoundsHandler,
+            @NonNull PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger) {
@@ -168,11 +172,12 @@
         mContext = context;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mPipBoundsHandler = pipBoundsHandler;
+        mPipBoundsState = pipBoundsState;
         mMenuController = menuController;
         mMenuController.addListener(new PipMenuListener());
         mGesture = new DefaultPipTouchGesture();
-        mMotionHelper = new PipMotionHelper(mContext, pipTaskOrganizer, mMenuController,
-                mPipBoundsHandler.getSnapAlgorithm(), floatingContentCoordinator);
+        mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
+                mMenuController, mPipBoundsHandler.getSnapAlgorithm(), floatingContentCoordinator);
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
                         pipTaskOrganizer, this::getMovementBounds,
@@ -189,8 +194,8 @@
         reloadResources();
 
         mFloatingContentCoordinator = floatingContentCoordinator;
-        mConnection = new PipAccessibilityInteractionConnection(mContext, mMotionHelper,
-                pipTaskOrganizer, mPipBoundsHandler.getSnapAlgorithm(),
+        mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
+                mMotionHelper, pipTaskOrganizer, mPipBoundsHandler.getSnapAlgorithm(),
                 this::onAccessibilityShowMenu, this::updateMovementBounds, mHandler);
 
         mPipUiEventLogger = pipUiEventLogger;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index a0ce9da..f3dadfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,7 +26,7 @@
 public enum ShellProtoLogGroup implements IProtoLogGroup {
     // NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
     // with those in the framework ProtoLogGroup
-    WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+    WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM_SHELL),
     WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM_SHELL),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f01fc51..5418a5b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -22,6 +22,10 @@
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -29,10 +33,12 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.content.pm.ParceledListSlice;
 import android.os.RemoteException;
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -93,8 +99,12 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue,
-                mTransactionPool, mTestExecutor, mTestExecutor);
+        try {
+            doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
+                    .when(mTaskOrganizerController).registerTaskOrganizer(any());
+        } catch (RemoteException e) {}
+        mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue,
+                mTransactionPool, mTestExecutor, mTestExecutor));
     }
 
     @Test
@@ -116,8 +126,29 @@
     }
 
     @Test
+    public void testRegisterWithExistingTasks() throws RemoteException {
+        // Setup some tasks
+        RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+        RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+        ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
+        taskInfos.add(new TaskAppearedInfo(task1, new SurfaceControl()));
+        taskInfos.add(new TaskAppearedInfo(task2, new SurfaceControl()));
+        doReturn(new ParceledListSlice(taskInfos))
+                .when(mTaskOrganizerController).registerTaskOrganizer(any());
+
+        // Register and expect the tasks to be stored
+        mOrganizer.registerOrganizer();
+
+        // Check that the tasks are next reported when the listener is added
+        TrackingTaskListener listener = new TrackingTaskListener();
+        mOrganizer.addListener(listener, TASK_LISTENER_TYPE_MULTI_WINDOW);
+        assertTrue(listener.appeared.contains(task1));
+        assertTrue(listener.appeared.contains(task2));
+    }
+
+    @Test
     public void testAppearedVanished() {
-        RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+        RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
         TrackingTaskListener listener = new TrackingTaskListener();
         mOrganizer.addListener(listener, TASK_LISTENER_TYPE_MULTI_WINDOW);
         mOrganizer.onTaskAppeared(taskInfo, null);
@@ -129,7 +160,7 @@
 
     @Test
     public void testAddListenerExistingTasks() {
-        RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+        RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
         mOrganizer.onTaskAppeared(taskInfo, null);
 
         TrackingTaskListener listener = new TrackingTaskListener();
@@ -139,7 +170,7 @@
 
     @Test
     public void testWindowingModeChange() {
-        RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+        RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
         TrackingTaskListener mwListener = new TrackingTaskListener();
         TrackingTaskListener pipListener = new TrackingTaskListener();
         mOrganizer.addListener(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW);
@@ -148,14 +179,15 @@
         assertTrue(mwListener.appeared.contains(taskInfo));
         assertTrue(pipListener.appeared.isEmpty());
 
-        taskInfo = createTaskInfo(WINDOWING_MODE_PINNED);
+        taskInfo = createTaskInfo(1, WINDOWING_MODE_PINNED);
         mOrganizer.onTaskInfoChanged(taskInfo);
         assertTrue(mwListener.vanished.contains(taskInfo));
         assertTrue(pipListener.appeared.contains(taskInfo));
     }
 
-    private RunningTaskInfo createTaskInfo(int windowingMode) {
+    private RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
+        taskInfo.taskId = taskId;
         taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
         return taskInfo;
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 92f03e1..2b987e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -34,6 +34,7 @@
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTestCase;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
@@ -67,10 +68,12 @@
     @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
     @Mock private PipTouchHandler mMockPipTouchHandler;
     @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
+    private PipBoundsState mPipBoundsState;
 
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
+        mPipBoundsState = new PipBoundsState();
 
         mSpiedContext = spy(mContext);
 
@@ -78,9 +81,9 @@
         when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
 
         mPipController = new PipController(mSpiedContext, mMockdDisplayController,
-                mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipMediaController,
-                mMockPipMenuActivityController, mMockPipTaskOrganizer, mMockPipTouchHandler,
-                mMockWindowManagerShellWrapper);
+                mMockPipAppOpsListener, mMockPipBoundsHandler, mPipBoundsState,
+                mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
+                mMockPipTouchHandler, mMockWindowManagerShellWrapper);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java
index c66ba13..37b93bcd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java
@@ -35,6 +35,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTestCase;
@@ -66,19 +67,21 @@
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
     @Mock private Optional<SplitScreen> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
+    private PipBoundsState mPipBoundsState;
 
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
+        mPipBoundsState = new PipBoundsState();
 
         mSpiedContext = spy(mContext);
 
         when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
         when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
 
-        mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mSpiedContext, mMockPipBoundsHandler,
-                mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
-                mMockPipUiEventLogger, mMockShellTaskOrganizer));
+        mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mSpiedContext, mPipBoundsState,
+                mMockPipBoundsHandler, mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen,
+                mMockdDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 93a45c4..4713142 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,6 +33,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTestCase;
@@ -74,6 +75,7 @@
     @Mock
     private PipUiEventLogger mPipUiEventLogger;
 
+    private PipBoundsState mPipBoundsState;
     private PipBoundsHandler mPipBoundsHandler;
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
@@ -90,11 +92,12 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mPipBoundsState = new PipBoundsState();
         mPipBoundsHandler = new PipBoundsHandler(mContext);
         mPipSnapAlgorithm = mPipBoundsHandler.getSnapAlgorithm();
         mPipSnapAlgorithm = new PipSnapAlgorithm(mContext);
         mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
-                mPipBoundsHandler, mPipTaskOrganizer, mFloatingContentCoordinator,
+                mPipBoundsHandler, mPipBoundsState, mPipTaskOrganizer, mFloatingContentCoordinator,
                 mPipUiEventLogger);
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 30ce537..fd18d2f 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -31,6 +31,7 @@
         "AnimationStart",
         "PerformTraversalsStart",
         "DrawStart",
+        "FrameDeadline",
         "SyncQueued",
         "SyncStart",
         "IssueDrawCommandsStart",
@@ -45,7 +46,7 @@
                       static_cast<int>(FrameInfoIndex::NumIndexes),
               "size mismatch: FrameInfoNames doesn't match the enum!");
 
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 18,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
               "Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
 
 void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index f5bfedd..d24eca7 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -27,7 +27,7 @@
 namespace android {
 namespace uirenderer {
 
-#define UI_THREAD_FRAME_INFO_SIZE 10
+#define UI_THREAD_FRAME_INFO_SIZE 11
 
 enum class FrameInfoIndex {
     Flags = 0,
@@ -40,6 +40,7 @@
     AnimationStart,
     PerformTraversalsStart,
     DrawStart,
+    FrameDeadline,
     // End of UI frame info
 
     SyncQueued,
@@ -77,9 +78,11 @@
     explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
         memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
         set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID;
+        set(FrameInfoIndex::FrameDeadline) = std::numeric_limits<int64_t>::max();
     }
 
-    UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync, int64_t vsyncId) {
+    UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync,
+                                 int64_t vsyncId, int64_t frameDeadline) {
         set(FrameInfoIndex::FrameTimelineVsyncId) = vsyncId;
         set(FrameInfoIndex::Vsync) = vsyncTime;
         set(FrameInfoIndex::IntendedVsync) = intendedVsync;
@@ -89,6 +92,7 @@
         set(FrameInfoIndex::AnimationStart) = vsyncTime;
         set(FrameInfoIndex::PerformTraversalsStart) = vsyncTime;
         set(FrameInfoIndex::DrawStart) = vsyncTime;
+        set(FrameInfoIndex::FrameDeadline) = frameDeadline;
         return *this;
     }
 
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index b802908..5f6b53a 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -21,7 +21,6 @@
 #include <log/log.h>
 
 #include <minikin/MeasuredText.h>
-#include <minikin/Measurement.h>
 #include "Paint.h"
 #include "SkPathMeasure.h"
 #include "Typeface.h"
@@ -70,18 +69,6 @@
     }
 }
 
-void MinikinUtils::getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
-                             const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out) {
-    minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
-
-    const minikin::U16StringPiece textBuf(buf, bufSize);
-    const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
-    const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
-
-    minikin::getBounds(textBuf, minikin::Range(0, textBuf.size()), bidiFlags, minikinPaint,
-        startHyphen, endHyphen, out);
-}
-
 float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                 const Typeface* typeface, const uint16_t* buf, size_t start,
                                 size_t count, size_t bufSize, float* advances) {
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index a15803a..7c3f0d8 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -48,9 +48,6 @@
                                                 size_t contextStart, size_t contextCount,
                                                 minikin::MeasuredText* mt);
 
-    static void getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
-                          const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out);
-
     static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                          const Typeface* typeface, const uint16_t* buf,
                                          size_t start, size_t count, size_t bufSize,
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 3c86b28..89ff9b2 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -339,13 +339,18 @@
     }
 
     static void doTextBounds(JNIEnv* env, const jchar* text, int count, jobject bounds,
-            const Paint& paint, const Typeface* typeface, jint bidiFlagsInt) {
+            const Paint& paint, const Typeface* typeface, jint bidiFlags) {
         SkRect  r;
         SkIRect ir;
 
+        minikin::Layout layout = MinikinUtils::doLayout(&paint,
+                static_cast<minikin::Bidi>(bidiFlags), typeface,
+                text, count,  // text buffer
+                0, count,  // draw range
+                0, count,  // context range
+                nullptr);
         minikin::MinikinRect rect;
-        minikin::Bidi bidiFlags = static_cast<minikin::Bidi>(bidiFlagsInt);
-        MinikinUtils::getBounds(&paint, bidiFlags, typeface, text, count, &rect);
+        layout.getBounds(&rect);
         r.fLeft = rect.mLeft;
         r.fTop = rect.mTop;
         r.fRight = rect.mRight;
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index c89463b..a146b64 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -514,7 +514,8 @@
         proxy.setLightGeometry((Vector3){0, 0, 0}, 0);
         nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
         UiFrameInfoBuilder(proxy.frameInfo())
-                .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID)
+                .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
+                    std::numeric_limits<int64_t>::max())
                 .addFlag(FrameInfoFlags::SurfaceCanvas);
         proxy.syncAndDrawFrame();
     }
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 6bc318d..aeb096d 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -221,6 +221,17 @@
     return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
 }
 
+// FastNative
+static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
+    const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+    const std::string& filePath = minikinSkia->getFilePath();
+    if (filePath.empty()) {
+        return nullptr;
+    }
+    return env->NewStringUTF(filePath.c_str());
+}
+
 // Critical Native
 static jlong Font_getNativeFontPtr(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
@@ -274,6 +285,7 @@
     { "nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F", (void*) Font_getFontMetrics },
     { "nGetFontInfo", "(J)J", (void*) Font_getFontInfo },
     { "nGetAxisInfo", "(JI)J", (void*) Font_getAxisInfo },
+    { "nGetFontPath", "(J)Ljava/lang/String;", (void*) Font_getFontPath },
     { "nGetNativeFontPtr", "(J)J", (void*) Font_getNativeFontPtr },
 };
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 0435981..1ac9922 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -629,10 +629,11 @@
 
     nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
     int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
+    int64_t frameDeadline = mRenderThread.timeLord().lastFrameDeadline();
     int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
     UiFrameInfoBuilder(frameInfo)
         .addFlag(FrameInfoFlags::RTAnimation)
-        .setVsync(vsync, vsync, vsyncId);
+        .setVsync(vsync, vsync, vsyncId, frameDeadline);
 
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
     prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1ea595d..c9146b2 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -130,7 +130,8 @@
     int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
     int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
     int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
-    mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId);
+    int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
+    mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId, frameDeadline);
     bool canDraw = mContext->makeCurrent();
     mContext->unpinImages();
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 9371656..a101d46 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -52,8 +52,9 @@
 void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
     RenderThread* rt = reinterpret_cast<RenderThread*>(data);
     int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
+    int64_t frameDeadline = AChoreographer_getFrameDeadline(rt->mChoreographer);
     rt->mVsyncRequested = false;
-    if (rt->timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId) &&
+    if (rt->timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId, frameDeadline) &&
             !rt->mFrameCallbackTaskPending) {
         ATRACE_NAME("queue mFrameCallbackTask");
         rt->mFrameCallbackTaskPending = true;
diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp
index 7dc36c4..abb6330 100644
--- a/libs/hwui/renderthread/TimeLord.cpp
+++ b/libs/hwui/renderthread/TimeLord.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 #include "TimeLord.h"
+#include <limits>
 
 namespace android {
 namespace uirenderer {
@@ -22,12 +23,15 @@
 TimeLord::TimeLord() : mFrameIntervalNanos(milliseconds_to_nanoseconds(16)),
                        mFrameTimeNanos(0),
                        mFrameIntendedTimeNanos(0),
-                       mFrameVsyncId(-1) {}
+                       mFrameVsyncId(-1),
+                       mFrameDeadline(std::numeric_limits<int64_t>::max()){}
 
-bool TimeLord::vsyncReceived(nsecs_t vsync, nsecs_t intendedVsync, int64_t vsyncId) {
+bool TimeLord::vsyncReceived(nsecs_t vsync, nsecs_t intendedVsync, int64_t vsyncId,
+                             int64_t frameDeadline) {
     if (intendedVsync > mFrameIntendedTimeNanos) {
         mFrameIntendedTimeNanos = intendedVsync;
         mFrameVsyncId = vsyncId;
+        mFrameDeadline = frameDeadline;
     }
 
     if (vsync > mFrameTimeNanos) {
diff --git a/libs/hwui/renderthread/TimeLord.h b/libs/hwui/renderthread/TimeLord.h
index 23c1e51..fa05c030 100644
--- a/libs/hwui/renderthread/TimeLord.h
+++ b/libs/hwui/renderthread/TimeLord.h
@@ -32,10 +32,12 @@
     nsecs_t frameIntervalNanos() const { return mFrameIntervalNanos; }
 
     // returns true if the vsync is newer, false if it was rejected for staleness
-    bool vsyncReceived(nsecs_t vsync, nsecs_t indendedVsync, int64_t vsyncId);
+    bool vsyncReceived(nsecs_t vsync, nsecs_t indendedVsync, int64_t vsyncId,
+                       int64_t frameDeadline);
     nsecs_t latestVsync() { return mFrameTimeNanos; }
     nsecs_t computeFrameTimeNanos();
     int64_t lastVsyncId() const { return mFrameVsyncId; }
+    int64_t lastFrameDeadline() const { return mFrameDeadline; }
 
 private:
     friend class RenderThread;
@@ -47,6 +49,7 @@
     nsecs_t mFrameTimeNanos;
     nsecs_t mFrameIntendedTimeNanos;
     int64_t mFrameVsyncId;
+    int64_t mFrameDeadline;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index ed89c59..eda5d22 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -146,7 +146,7 @@
         testContext.waitForVsync();
         nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
         UiFrameInfoBuilder(proxy->frameInfo())
-            .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
+            .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID, std::numeric_limits<int64_t>::max());
         proxy->syncAndDrawFrame();
     }
 
@@ -167,7 +167,7 @@
         {
             ATRACE_NAME("UI-Draw Frame");
             UiFrameInfoBuilder(proxy->frameInfo())
-                .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
+                .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID, std::numeric_limits<int64_t>::max());
             scene->doFrame(i);
             proxy->syncAndDrawFrame();
         }
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index eb2e23e..4a095c9 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -28,7 +28,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -161,7 +160,7 @@
                        be set to true when the phone is having emergency call, and then will
                        be set to false by mPhoneStateListener when the emergency call ends.
                 */
-                mIsInEmergencyCall = PhoneNumberUtils.isEmergencyNumber(phoneNumber);
+                mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(phoneNumber);
                 if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency());
             } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
                 updateLocationMode();
diff --git a/media/OWNERS b/media/OWNERS
index 36df3a0..0fc781c 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -15,6 +15,8 @@
 klhyun@google.com
 lajos@google.com
 marcone@google.com
+nchalko@google.com
 philburk@google.com
+quxiangfang@google.com
 sungsoo@google.com
 wonsik@google.com
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 0747ab13..ae97a71 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2711,12 +2711,12 @@
             }
         };
 
-        private final Pattern zeroPattern = new Pattern(0, 0);
+        private static final Pattern ZERO_PATTERN = new Pattern(0, 0);
 
         /**
          * The pattern applicable to the protected data in each subsample.
          */
-        private Pattern pattern;
+        private Pattern mPattern = ZERO_PATTERN;
 
         /**
          * Set the subsample count, clear/encrypted sizes, key, IV and mode fields of
@@ -2735,22 +2735,30 @@
             key = newKey;
             iv = newIV;
             mode = newMode;
-            pattern = zeroPattern;
+            mPattern = ZERO_PATTERN;
+        }
+
+        /**
+         * Returns the {@link Pattern encryption pattern}.
+         */
+        public @NonNull Pattern getPattern() {
+            return new Pattern(mPattern.getEncryptBlocks(), mPattern.getSkipBlocks());
         }
 
         /**
          * Set the encryption pattern on a {@link MediaCodec.CryptoInfo} instance.
-         * See {@link MediaCodec.CryptoInfo.Pattern}.
+         * See {@link Pattern}.
          */
         public void setPattern(Pattern newPattern) {
             if (newPattern == null) {
-                newPattern = zeroPattern;
+                newPattern = ZERO_PATTERN;
             }
-            pattern = newPattern;
+            setPattern(newPattern.getEncryptBlocks(), newPattern.getSkipBlocks());
         }
 
+        // Accessed from android_media_MediaExtractor.cpp.
         private void setPattern(int blocksToEncrypt, int blocksToSkip) {
-            pattern = new Pattern(blocksToEncrypt, blocksToSkip);
+            mPattern = new Pattern(blocksToEncrypt, blocksToSkip);
         }
 
         @Override
@@ -2772,9 +2780,9 @@
             builder.append(", encrypted ");
             builder.append(Arrays.toString(numBytesOfEncryptedData));
             builder.append(", pattern (encrypt: ");
-            builder.append(pattern.mEncryptBlocks);
+            builder.append(mPattern.mEncryptBlocks);
             builder.append(", skip: ");
-            builder.append(pattern.mSkipBlocks);
+            builder.append(mPattern.mSkipBlocks);
             builder.append(")");
             return builder.toString();
         }
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index dbf4ad0..78eeca1 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -427,7 +427,7 @@
 
     private MediaMetadata(Parcel in) {
         mBundle = in.readBundle();
-        mBitmapDimensionLimit = Math.max(in.readInt(), 0);
+        mBitmapDimensionLimit = Math.max(in.readInt(), 1);
     }
 
     /**
@@ -518,17 +518,18 @@
 
     /**
      * Gets the width/height limit (in pixels) for the bitmaps when this metadata was created.
-     * This method returns a positive value or zero.
+     * This method always returns a positive value.
      * <p>
-     * If it returns a positive value, then all the bitmaps in this metadata has width/height
+     * If it returns {@link Integer#MAX_VALUE}, then no scaling down was applied to the bitmaps
+     * when this metadata was created.
+     * <p>
+     * If it returns another positive value, then all the bitmaps in this metadata has width/height
      * not greater than this limit. Bitmaps may have been scaled down according to the limit.
      * <p>
-     * If it returns zero, then no scaling down was applied to the bitmaps when this metadata
-     * was created.
      *
      * @see Builder#setBitmapDimensionLimit(int)
      */
-    public @IntRange(from = 0) int getBitmapDimensionLimit() {
+    public @IntRange(from = 1) int getBitmapDimensionLimit() {
         return mBitmapDimensionLimit;
     }
 
@@ -738,7 +739,7 @@
      */
     public static final class Builder {
         private final Bundle mBundle;
-        private int mBitmapDimensionLimit;
+        private int mBitmapDimensionLimit = Integer.MAX_VALUE;
 
         /**
          * Create an empty Builder. Any field that should be included in the
@@ -925,14 +926,21 @@
          * Bitmaps will be replaced with scaled down copies if their width (or height) is
          * larger than {@code bitmapDimensionLimit}.
          * <p>
-         * In order to unset the limit, pass zero as {@code bitmapDimensionLimit}.
+         * In order to unset the limit, pass {@link Integer#MAX_VALUE} as
+         * {@code bitmapDimensionLimit}.
          *
          * @param bitmapDimensionLimit The maximum width/height (in pixels) for bitmaps
-         *                             contained in the metadata. Pass {@code 0} to unset the limit.
+         *                             contained in the metadata. Non-positive values are ignored.
+         *                             Pass {@link Integer#MAX_VALUE} to unset the limit.
          */
         @NonNull
-        public Builder setBitmapDimensionLimit(int bitmapDimensionLimit) {
-            mBitmapDimensionLimit = Math.max(bitmapDimensionLimit, 0);
+        public Builder setBitmapDimensionLimit(@IntRange(from = 1) int bitmapDimensionLimit) {
+            if (bitmapDimensionLimit > 0) {
+                mBitmapDimensionLimit = bitmapDimensionLimit;
+            } else {
+                Log.w(TAG, "setBitmapDimensionLimit(): Ignoring non-positive bitmapDimensionLimit: "
+                        + bitmapDimensionLimit);
+            }
             return this;
         }
 
@@ -942,7 +950,7 @@
          * @return The new MediaMetadata instance
          */
         public MediaMetadata build() {
-            if (mBitmapDimensionLimit > 0) {
+            if (mBitmapDimensionLimit != Integer.MAX_VALUE) {
                 for (String key : mBundle.keySet()) {
                     Object value = mBundle.get(key);
                     if (value instanceof Bitmap) {
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index cf03b06..835a709 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -16,10 +16,13 @@
 
 package android.media;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -1331,7 +1334,6 @@
      * @see MediaFormat#COLOR_RANGE_FULL
      */
     public static final int METADATA_KEY_COLOR_RANGE    = 37;
-    // Add more here...
 
     /**
      * This key retrieves the sample rate in Hz, if available.
@@ -1344,4 +1346,13 @@
      * This is a signed 32-bit integer formatted as a string in base 10.
      */
     public static final int METADATA_KEY_BITS_PER_SAMPLE = 39;
+
+    /**
+     * This key retrieves the video codec mimetype if available.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40;
+
+    // Add more here...
 }
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 2b3f420c..4d87fb3 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -45,6 +45,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.Display;
+import android.view.DisplayAddress;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1737,7 +1740,9 @@
          */
         private static final int DEFAULT_PLAYBACK_VOLUME = DEFAULT_PLAYBACK_MAX_VOLUME;
 
-        RouteInfo(RouteCategory category) {
+        /** @hide */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public RouteInfo(RouteCategory category) {
             mCategory = category;
             mDeviceType = DEVICE_TYPE_UNKNOWN;
         }
@@ -2078,7 +2083,9 @@
             return mPresentationDisplay;
         }
 
-        boolean updatePresentationDisplay() {
+        /** @hide */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public boolean updatePresentationDisplay() {
             Display display = choosePresentationDisplay();
             if (mPresentationDisplay != display) {
                 mPresentationDisplay = display;
@@ -2088,41 +2095,81 @@
         }
 
         private Display choosePresentationDisplay() {
-            if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
-                Display[] displays = sStatic.getAllPresentationDisplays();
+            if ((getSupportedTypes() & ROUTE_TYPE_LIVE_VIDEO) == 0) {
+                return null;
+            }
+            final Display[] displays = getAllPresentationDisplays();
+            if (displays == null || displays.length == 0) {
+                return null;
+            }
 
-                // Ensure that the specified display is valid for presentations.
-                // This check will normally disallow the default display unless it was
-                // configured as a presentation display for some reason.
-                if (mPresentationDisplayId >= 0) {
-                    for (Display display : displays) {
-                        if (display.getDisplayId() == mPresentationDisplayId) {
-                            return display;
-                        }
+            // Ensure that the specified display is valid for presentations.
+            // This check will normally disallow the default display unless it was
+            // configured as a presentation display for some reason.
+            if (mPresentationDisplayId >= 0) {
+                for (Display display : displays) {
+                    if (display.getDisplayId() == mPresentationDisplayId) {
+                        return display;
                     }
-                    return null;
                 }
+                return null;
+            }
 
-                // Find the indicated Wifi display by its address.
-                if (mDeviceAddress != null) {
-                    for (Display display : displays) {
-                        if (display.getType() == Display.TYPE_WIFI
-                                && mDeviceAddress.equals(display.getAddress())) {
-                            return display;
-                        }
+            // Find the indicated Wifi display by its address.
+            if (getDeviceAddress() != null) {
+                for (Display display : displays) {
+                    if (display.getType() == Display.TYPE_WIFI
+                            && displayAddressEquals(display)) {
+                        return display;
                     }
-                    return null;
-                }
-
-                // For the default route, choose the first presentation display from the list.
-                if (this == sStatic.mDefaultAudioVideo && displays.length > 0) {
-                    return displays[0];
                 }
             }
+
+            // Returns the first hard-wired display.
+            for (Display display : displays) {
+                if (display.getType() == Display.TYPE_EXTERNAL) {
+                    return display;
+                }
+            }
+
+            // Returns the first non-default built-in display.
+            for (Display display : displays) {
+                if (display.getType() == Display.TYPE_INTERNAL) {
+                    return display;
+                }
+            }
+
+            // For the default route, choose the first presentation display from the list.
+            if (this == getDefaultAudioVideo()) {
+                return displays[0];
+            }
             return null;
         }
 
         /** @hide */
+        @VisibleForTesting
+        public Display[] getAllPresentationDisplays() {
+            return sStatic.getAllPresentationDisplays();
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public RouteInfo getDefaultAudioVideo() {
+            return sStatic.mDefaultAudioVideo;
+        }
+
+        private boolean displayAddressEquals(Display display) {
+            final DisplayAddress displayAddress = display.getAddress();
+            // mDeviceAddress recorded mac address. If displayAddress is not a kind of Network,
+            // return false early.
+            if (!(displayAddress instanceof DisplayAddress.Network)) {
+                return false;
+            }
+            final DisplayAddress.Network networkAddress = (DisplayAddress.Network) displayAddress;
+            return getDeviceAddress().equals(networkAddress.toString());
+        }
+
+        /** @hide */
         @UnsupportedAppUsage
         public String getDeviceAddress() {
             return mDeviceAddress;
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index e148d0e..867eab0 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -98,15 +98,44 @@
      * Invalid timestamp.
      *
      * <p>Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()},
-     * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()}, or
-     * {@link Tuner#getAvSyncTime(int)} when the requested timestamp is not available.
+     * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()},
+     * {@link Tuner#getAvSyncTime(int)} or {@link TsRecordEvent#getPts()} and
+     * {@link MmtpRecordEvent#getPts()} when the requested timestamp is not available.
      *
      * @see android.media.tv.tuner.filter.TimeFilter#getSourceTime()
      * @see android.media.tv.tuner.filter.TimeFilter#getTimeStamp()
      * @see Tuner#getAvSyncTime(int)
+     * @see android.media.tv.tuner.filter.TsRecordEvent#getPts()
+     * @see android.media.tv.tuner.filter.MmtpRecordEvent#getPts()
      */
-    public static final long INVALID_TIMESTAMP = -1L;
-
+    public static final long INVALID_TIMESTAMP =
+            android.hardware.tv.tuner.V1_1.Constants.Constant64Bit.INVALID_PRESENTATION_TIME_STAMP;
+    /**
+     * Invalid mpu sequence number in MmtpRecordEvent.
+     *
+     * <p>Returned by {@link MmtpRecordEvent#getMpuSequenceNumber()} when the requested sequence
+     * number is not available.
+     *
+     * @see android.media.tv.tuner.filter.MmtpRecordEvent#getMpuSequenceNumber()
+     */
+    public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM =
+            android.hardware.tv.tuner.V1_1.Constants.Constant
+                    .INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM;
+    /**
+     * Invalid local transport stream id.
+     *
+     * <p>Returned by {@link #linkFrontendToCiCam(int)} when the requested failed
+     * or the hal implementation does not support the operation.
+     *
+     * @see #linkFrontendToCiCam(int)
+     */
+    public static final int INVALID_LTS_ID =
+            android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_LTS_ID;
+    /**
+     * Invalid 64-bit filter ID.
+     */
+    public static final long INVALID_FILTER_ID_64BIT =
+            android.hardware.tv.tuner.V1_1.Constants.Constant64Bit.INVALID_FILTER_ID_64BIT;
 
     /** @hide */
     @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
@@ -204,6 +233,7 @@
     private final Context mContext;
     private final TunerResourceManager mTunerResourceManager;
     private final int mClientId;
+    private static int sTunerVersion = TunerVersionChecker.TUNER_VERSION_UNKNOWN;
 
     private Frontend mFrontend;
     private EventHandler mHandler;
@@ -255,6 +285,14 @@
     public Tuner(@NonNull Context context, @Nullable String tvInputSessionId,
             @TvInputService.PriorityHintUseCaseType int useCase) {
         nativeSetup();
+        sTunerVersion = nativeGetTunerVersion();
+        if (sTunerVersion == TunerVersionChecker.TUNER_VERSION_UNKNOWN) {
+            Log.e(TAG, "Unknown Tuner version!");
+        } else {
+            Log.d(TAG, "Current Tuner version is "
+                    + TunerVersionChecker.getMajorVersion(sTunerVersion) + "."
+                    + TunerVersionChecker.getMinorVersion(sTunerVersion) + ".");
+        }
         mContext = context;
         mTunerResourceManager = (TunerResourceManager)
                 context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
@@ -295,6 +333,11 @@
     }
 
     /** @hide */
+    public static int getTunerVersion() {
+        return sTunerVersion;
+    }
+
+    /** @hide */
     public List<Integer> getFrontendIds() {
         return nativeGetFrontendIds();
     }
@@ -419,6 +462,11 @@
     /**
      * Native method to get all frontend IDs.
      */
+    private native int nativeGetTunerVersion();
+
+    /**
+     * Native method to get all frontend IDs.
+     */
     private native List<Integer> nativeGetFrontendIds();
 
     /**
@@ -437,7 +485,9 @@
     private native Integer nativeGetAvSyncHwId(Filter filter);
     private native Long nativeGetAvSyncTime(int avSyncId);
     private native int nativeConnectCiCam(int ciCamId);
+    private native int nativeLinkCiCam(int ciCamId);
     private native int nativeDisconnectCiCam();
+    private native int nativeUnlinkCiCam(int ciCamId);
     private native FrontendInfo nativeGetFrontendInfo(int id);
     private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
     private native TimeFilter nativeOpenTimeFilter();
@@ -760,6 +810,33 @@
     }
 
     /**
+     * Link Conditional Access Modules (CAM) Frontend to support Common Interface (CI) by-pass mode.
+     *
+     * <p>It is used by the client to link CI-CAM to a Frontend. CI by-pass mode requires that
+     * the CICAM also receives the TS concurrently from the frontend when the Demux is receiving
+     * the TS directly from the frontend.
+     *
+     * <p>Use {@link #unlinkFrontendToCicam(int)} to disconnect.
+     *
+     * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
+     * no-op and return {@link INVALID_LTS_ID}. Use {@link TunerVersionChecker.getTunerVersion()} to
+     * check the version.
+     *
+     * @param ciCamId specify CI-CAM Id to link.
+     * @return Local transport stream id when connection is successfully established. Failed
+     *         operation returns {@link INVALID_LTS_ID}.
+     */
+    public int linkFrontendToCiCam(int ciCamId) {
+        if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+                "linkFrontendToCiCam")) {
+            if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
+                return nativeLinkCiCam(ciCamId);
+            }
+        }
+        return INVALID_LTS_ID;
+    }
+
+    /**
      * Disconnects Conditional Access Modules (CAM)
      *
      * <p>The demux will use the output from the frontend as the input after this call.
@@ -775,6 +852,28 @@
     }
 
     /**
+     * Unlink Conditional Access Modules (CAM) Frontend.
+     *
+     * <p>It is used by the client to unlink CI-CAM to a Frontend.
+     *
+     * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
+     * no-op. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
+     *
+     * @param ciCamId specify CI-CAM Id to unlink.
+     * @return result status of the operation.
+     */
+    @Result
+    public int unlinkFrontendToCiCam(int ciCamId) {
+        if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+                "unlinkFrontendToCiCam")) {
+            if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
+                return nativeUnlinkCiCam(ciCamId);
+            }
+        }
+        return RESULT_UNAVAILABLE;
+    }
+
+    /**
      * Gets the frontend information.
      *
      * @return The frontend information. {@code null} if the operation failed.
diff --git a/media/java/android/media/tv/tuner/TunerVersionChecker.java b/media/java/android/media/tv/tuner/TunerVersionChecker.java
new file mode 100644
index 0000000..739f87d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerVersionChecker.java
@@ -0,0 +1,153 @@
+/*
+ * 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 android.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility class to check the currently running Tuner Hal implementation version.
+ *
+ * APIs that are not supported by the HAL implementation version would be no-op.
+ *
+ * @hide
+ */
+@TestApi
+@SystemApi
+public final class TunerVersionChecker {
+    private static final String TAG = "TunerVersionChecker";
+
+    private TunerVersionChecker() {}
+
+    /** @hide */
+    @IntDef(prefix = "TUNER_VERSION_", value = {TUNER_VERSION_UNKNOWN, TUNER_VERSION_1_0,
+                                                TUNER_VERSION_1_1})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TunerVersion {}
+    /**
+     * Unknown Tuner version.
+     */
+    public static final int TUNER_VERSION_UNKNOWN = 0;
+    /**
+     * Tuner version 1.0.
+     */
+    public static final int TUNER_VERSION_1_0 = (1 << 16);
+    /**
+     * Tuner version 1.1.
+     */
+    public static final int TUNER_VERSION_1_1 = ((1 << 16) | 1);
+
+    /**
+     * Get the current running Tuner version.
+     *
+     * @return Tuner version.
+     */
+    @TunerVersion
+    public static int getTunerVersion() {
+        return Tuner.getTunerVersion();
+    }
+
+    /**
+     * Check if the current running Tuner version supports the given version.
+     *
+     * <p>Note that we treat different major versions as unsupported among each other. If any
+     * feature could be supported across major versions, please use
+     * {@link #isHigherOrEqualVersionTo(int)} to check.
+     *
+     * @param version the version to support.
+     *
+     * @return true if the current version is under the same major version as the given version
+     * and has higher or the same minor version as the given version.
+     * @hide
+     */
+    @TestApi
+    public static boolean supportTunerVersion(@TunerVersion int version) {
+        int currentVersion = Tuner.getTunerVersion();
+        return isHigherOrEqualVersionTo(version)
+                && (getMajorVersion(version) == getMajorVersion(currentVersion));
+    }
+
+    /**
+     * Check if the current running Tuner version is higher than or equal to a given version.
+     *
+     * @param version the version to compare.
+     *
+     * @return true if the current version is higher or equal to the support version.
+     * @hide
+     */
+    @TestApi
+    public static boolean isHigherOrEqualVersionTo(@TunerVersion int version) {
+        int currentVersion = Tuner.getTunerVersion();
+        return currentVersion >= version;
+    }
+
+    /**
+     * Get the major version from a version number.
+     *
+     * @param version the version to be checked.
+     *
+     * @return the major version number.
+     * @hide
+     */
+    @TestApi
+    public static int getMajorVersion(@TunerVersion int version) {
+        return ((version & 0xFFFF0000) >>> 16);
+    }
+
+    /**
+     * Get the major version from a version number.
+     *
+     * @param version the version to be checked.
+     *
+     * @return the minor version number.
+     * @hide
+     */
+    @TestApi
+    public static int getMinorVersion(@TunerVersion int version) {
+        return (version & 0xFFFF);
+    }
+
+    /** @hide */
+    public static boolean checkHigherOrEqualVersionTo(
+            @TunerVersion int version, String methodName) {
+        if (!TunerVersionChecker.isHigherOrEqualVersionTo(version)) {
+            Log.e(TAG, "Current Tuner version "
+                    + TunerVersionChecker.getMajorVersion(Tuner.getTunerVersion()) + "."
+                    + TunerVersionChecker.getMinorVersion(Tuner.getTunerVersion())
+                    + " does not support " + methodName + ".");
+            return false;
+        }
+        return true;
+    }
+
+    /** @hide */
+    public static boolean checkSupportVersion(@TunerVersion int version, String methodName) {
+        if (!TunerVersionChecker.supportTunerVersion(version)) {
+            Log.e(TAG, "Current Tuner version "
+                    + TunerVersionChecker.getMajorVersion(Tuner.getTunerVersion()) + "."
+                    + TunerVersionChecker.getMinorVersion(Tuner.getTunerVersion())
+                    + " does not support " + methodName + ".");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index f0015b7..2f2d8f7 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -185,7 +185,7 @@
     private long mNativeContext;
     private FilterCallback mCallback;
     private Executor mExecutor;
-    private final int mId;
+    private final long mId;
     private int mMainType;
     private int mSubtype;
     private Filter mSource;
@@ -196,6 +196,7 @@
     private native int nativeConfigureFilter(
             int type, int subType, FilterConfiguration settings);
     private native int nativeGetId();
+    private native long nativeGetId64Bit();
     private native int nativeSetDataSource(Filter source);
     private native int nativeStartFilter();
     private native int nativeStopFilter();
@@ -204,7 +205,7 @@
     private native int nativeClose();
 
     // Called by JNI
-    private Filter(int id) {
+    private Filter(long id) {
         mId = id;
     }
 
@@ -269,6 +270,16 @@
     }
 
     /**
+     * Gets the 64-bit filter Id.
+     */
+    public long getId64Bit() {
+        synchronized (mLock) {
+            TunerUtils.checkResourceState(TAG, mIsClosed);
+            return nativeGetId64Bit();
+        }
+    }
+
+    /**
      * Sets the filter's data source.
      *
      * A filter uses demux as data source by default. If the data was packetized
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index f54b686..2649fcf 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.SystemApi;
+import android.media.tv.tuner.TunerVersionChecker;
 
 /**
  * Filter configuration for a IP filter.
@@ -28,20 +29,28 @@
  */
 @SystemApi
 public final class IpFilterConfiguration extends FilterConfiguration {
+    /**
+     * Undefined filter type.
+     */
+    public static final int INVALID_IP_FILTER_CONTEXT_ID =
+            android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_IP_FILTER_CONTEXT_ID;
+
     private final byte[] mSrcIpAddress;
     private final byte[] mDstIpAddress;
     private final int mSrcPort;
     private final int mDstPort;
     private final boolean mPassthrough;
+    private final int mIpFilterContextId;
 
     private IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort,
-            int dstPort, boolean passthrough) {
+            int dstPort, boolean passthrough, int ipCid) {
         super(settings);
         mSrcIpAddress = srcAddr;
         mDstIpAddress = dstAddr;
         mSrcPort = srcPort;
         mDstPort = dstPort;
         mPassthrough = passthrough;
+        mIpFilterContextId = ipCid;
     }
 
     @Override
@@ -86,6 +95,15 @@
     public boolean isPassthrough() {
         return mPassthrough;
     }
+    /**
+     * Gets the ip filter context id. Default value is {@link #INVALID_IP_FILTER_CONTEXT_ID}.
+     *
+     * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would return
+     * default value. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
+     */
+    public int getIpFilterContextId() {
+        return mIpFilterContextId;
+    }
 
     /**
      * Creates a builder for {@link IpFilterConfiguration}.
@@ -105,6 +123,7 @@
         private int mDstPort = 0;
         private boolean mPassthrough = false;
         private Settings mSettings;
+        private int mIpCid = INVALID_IP_FILTER_CONTEXT_ID;
 
         private Builder() {
         }
@@ -170,6 +189,21 @@
         }
 
         /**
+         * Sets the ip filter context id. Default value is {@link #INVALID_IP_FILTER_CONTEXT_ID}.
+         *
+         * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
+         * no-op. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
+         */
+        @NonNull
+        public Builder setIpFilterContextId(int ipContextId) {
+            if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_1_1, "setIpFilterContextId")) {
+                mIpCid = ipContextId;
+            }
+            return this;
+        }
+
+        /**
          * Builds a {@link IpFilterConfiguration} object.
          */
         @NonNull
@@ -180,8 +214,8 @@
                     "The lengths of src and dst IP address must be 4 or 16 and must be the same."
                             + "srcLength=" + ipAddrLength + ", dstLength=" + mDstIpAddress.length);
             }
-            return new IpFilterConfiguration(
-                    mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
+            return new IpFilterConfiguration(mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort,
+                    mDstPort, mPassthrough, mIpCid);
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index 466fa3e..7060bd72 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -29,11 +29,15 @@
 public class MmtpRecordEvent extends FilterEvent {
     private final int mScHevcIndexMask;
     private final long mDataLength;
+    private final int mMpuSequenceNumber;
+    private final long mPts;
 
     // This constructor is used by JNI code only
-    private MmtpRecordEvent(int scHevcIndexMask, long dataLength) {
+    private MmtpRecordEvent(int scHevcIndexMask, long dataLength, int mpuSequenceNumber, long pts) {
         mScHevcIndexMask = scHevcIndexMask;
         mDataLength = dataLength;
+        mMpuSequenceNumber = mpuSequenceNumber;
+        mPts = pts;
     }
 
     /**
@@ -51,4 +55,20 @@
     public long getDataLength() {
         return mDataLength;
     }
+
+    /**
+     * Get the MPU sequence number of the filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
+
+    /**
+     * Get the Presentation Time Stamp(PTS) for the audio or video frame. It is based on 90KHz
+     * and has the same format as the PTS in ISO/IEC 13818-1. It is used only for the SC and
+     * the SC_HEVC.
+     */
+    public long getPts() {
+        return mPts;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 7a14bb8..258e2f2 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -32,13 +32,15 @@
     private final int mTsIndexMask;
     private final int mScIndexMask;
     private final long mDataLength;
+    private final long mPts;
 
     // This constructor is used by JNI code only
-    private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long dataLength) {
+    private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long dataLength, long pts) {
         mPid = pid;
         mTsIndexMask = tsIndexMask;
         mScIndexMask = scIndexMask;
         mDataLength = dataLength;
+        mPts = pts;
     }
 
     /**
@@ -72,4 +74,13 @@
     public long getDataLength() {
         return mDataLength;
     }
+
+    /**
+     * Gets the Presentation Time Stamp(PTS) for the audio or video frame. It is based on 90KHz
+     * and has the same format as the PTS in ISO/IEC 13818-1. It is used only for the SC and
+     * the SC_HEVC.
+     */
+    public long getPts() {
+        return mPts;
+    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 4e27c8e..724965d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -142,6 +142,7 @@
     shared_libs: [
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hardware.tv.tuner@1.0",
+        "android.hardware.tv.tuner@1.1",
         "libandroid_runtime",
         "libcutils",
         "libfmq",
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 0b0e162..71c86cc 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2662,7 +2662,7 @@
     gFields.cryptoInfoModeID = env->GetFieldID(clazz.get(), "mode", "I");
     CHECK(gFields.cryptoInfoModeID != NULL);
 
-    gFields.cryptoInfoPatternID = env->GetFieldID(clazz.get(), "pattern",
+    gFields.cryptoInfoPatternID = env->GetFieldID(clazz.get(), "mPattern",
         "Landroid/media/MediaCodec$CryptoInfo$Pattern;");
     CHECK(gFields.cryptoInfoPatternID != NULL);
 
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 2ef7b9e..b6c47fca 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -140,6 +140,27 @@
     fmt = applyFormatOverrides(fmt, containerFormat);
     switch (fmt) {
         case HAL_PIXEL_FORMAT_YCbCr_420_888:
+            // Width and height should be multiple of 2. Wrong dataSize would be returned otherwise.
+            if (buffer->width % 2 != 0) {
+                ALOGE("YCbCr_420_888: width (%d) should be a multiple of 2", buffer->width);
+                return BAD_VALUE;
+            }
+
+            if (buffer->height % 2 != 0) {
+                ALOGE("YCbCr_420_888: height (%d) should be a multiple of 2", buffer->height);
+                return BAD_VALUE;
+            }
+
+            if (buffer->width <= 0) {
+                ALOGE("YCbCr_420_888: width (%d) should be a > 0", buffer->width);
+                return BAD_VALUE;
+            }
+
+            if (buffer->height <= 0) {
+                ALOGE("YCbCr_420_888: height (%d) should be a > 0", buffer->height);
+                return BAD_VALUE;
+            }
+
             pData =
                 (idx == 0) ?
                     buffer->data :
@@ -160,6 +181,27 @@
             break;
         // NV21
         case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+            // Width and height should be multiple of 2. Wrong dataSize would be returned otherwise.
+            if (buffer->width % 2 != 0) {
+                ALOGE("YCrCb_420_SP: width (%d) should be a multiple of 2", buffer->width);
+                return BAD_VALUE;
+            }
+
+            if (buffer->height % 2 != 0) {
+                ALOGE("YCrCb_420_SP: height (%d) should be a multiple of 2", buffer->height);
+                return BAD_VALUE;
+            }
+
+            if (buffer->width <= 0) {
+                ALOGE("YCrCb_420_SP: width (%d) should be a > 0", buffer->width);
+                return BAD_VALUE;
+            }
+
+            if (buffer->height <= 0) {
+                ALOGE("YCrCb_420_SP: height (%d) should be a > 0", buffer->height);
+                return BAD_VALUE;
+            }
+
             cr = buffer->data + (buffer->stride * buffer->height);
             cb = cr + 1;
             // only map until last pixel
@@ -178,6 +220,27 @@
             rStride = buffer->width;
             break;
         case HAL_PIXEL_FORMAT_YV12:
+            // Width and height should be multiple of 2. Wrong dataSize would be returned otherwise.
+            if (buffer->width % 2 != 0) {
+                ALOGE("YV12: width (%d) should be a multiple of 2", buffer->width);
+                return BAD_VALUE;
+            }
+
+            if (buffer->height % 2 != 0) {
+                ALOGE("YV12: height (%d) should be a multiple of 2", buffer->height);
+                return BAD_VALUE;
+            }
+
+            if (buffer->width <= 0) {
+                ALOGE("YV12: width (%d) should be a > 0", buffer->width);
+                return BAD_VALUE;
+            }
+
+            if (buffer->height <= 0) {
+                ALOGE("YV12: height (%d) should be a > 0", buffer->height);
+                return BAD_VALUE;
+            }
+
             // Y and C stride need to be 16 pixel aligned.
             LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
                                 "Stride is not 16 pixel aligned %d", buffer->stride);
@@ -344,6 +407,11 @@
     int flexFormat = format;
     if (isPossiblyYUV(format)) {
         res = buffer->lockAsyncYCbCr(inUsage, rect, &ycbcr, fenceFd);
+
+        if (res != OK) {
+            ALOGW("lockAsyncYCbCr failed with error %d", res);
+        }
+
         pData = ycbcr.y;
         flexFormat = HAL_PIXEL_FORMAT_YCbCr_420_888;
     }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 5daf8b0..0845933 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -22,7 +22,6 @@
 #include "android_runtime/AndroidRuntime.h"
 
 #include <android-base/logging.h>
-#include <android/hardware/tv/tuner/1.0/ITuner.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
@@ -34,7 +33,6 @@
 using ::android::hardware::hidl_bitfield;
 using ::android::hardware::hidl_vec;
 using ::android::hardware::tv::tuner::V1_0::AudioExtraMetaData;
-using ::android::hardware::tv::tuner::V1_0::Constant;
 using ::android::hardware::tv::tuner::V1_0::DataFormat;
 using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterType;
@@ -129,12 +127,13 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
 using ::android::hardware::tv::tuner::V1_0::FrontendStatusType;
 using ::android::hardware::tv::tuner::V1_0::FrontendType;
-using ::android::hardware::tv::tuner::V1_0::ITuner;
 using ::android::hardware::tv::tuner::V1_0::LnbPosition;
 using ::android::hardware::tv::tuner::V1_0::LnbTone;
 using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
 using ::android::hardware::tv::tuner::V1_0::PlaybackSettings;
 using ::android::hardware::tv::tuner::V1_0::RecordSettings;
+using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::Constant64Bit;
 
 struct fields_t {
     jfieldID tunerContext;
@@ -310,9 +309,9 @@
 
 /////////////// MediaEvent ///////////////////////
 
-MediaEvent::MediaEvent(sp<IFilter> iFilter, hidl_handle avHandle,
-        uint64_t dataId, uint64_t dataLength, jobject obj) : mIFilter(iFilter),
-        mDataId(dataId), mDataLength(dataLength), mBuffer(nullptr),
+MediaEvent::MediaEvent(sp<Filter> filter, hidl_handle avHandle,
+        uint64_t dataId, uint64_t dataSize, jobject obj) : mFilter(filter),
+        mDataId(dataId), mDataSize(dataSize), mBuffer(nullptr),
         mDataIdRefCnt(0), mAvHandleRefCnt(0), mIonHandle(nullptr) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     mMediaEventObj = env->NewWeakGlobalRef(obj);
@@ -336,7 +335,8 @@
 
 void MediaEvent::finalize() {
     if (mAvHandleRefCnt == 0) {
-        mIFilter->releaseAvHandle(hidl_handle(mAvHandle), mDataIdRefCnt == 0 ? mDataId : 0);
+        mFilter->mFilterSp->releaseAvHandle(
+                hidl_handle(mAvHandle), mDataIdRefCnt == 0 ? mDataId : 0);
         native_handle_close(mAvHandle);
     }
 }
@@ -349,7 +349,47 @@
     if (mLinearBlockObj != NULL) {
         return mLinearBlockObj;
     }
-    mIonHandle = new C2HandleIon(dup(mAvHandle->data[0]), mDataLength);
+
+    int fd;
+    int numInts = 0;
+    int memIndex;
+    int dataSize;
+    if (mAvHandle->numFds == 0) {
+        if (mFilter->mAvSharedHandle == NULL) {
+            ALOGE("Shared AV memory handle is not initialized.");
+            return NULL;
+        }
+        if (mFilter->mAvSharedHandle->numFds == 0) {
+            ALOGE("Shared AV memory handle is empty.");
+            return NULL;
+        }
+        fd = mFilter->mAvSharedHandle->data[0];
+        dataSize = mFilter->mAvSharedMemSize;
+        numInts = mFilter->mAvSharedHandle->numInts;
+        if (numInts > 0) {
+            // If the first int in the shared native handle has value, use it as the index
+            memIndex = mFilter->mAvSharedHandle->data[mFilter->mAvSharedHandle->numFds];
+        }
+    } else {
+        fd = mAvHandle->data[0];
+        dataSize = mDataSize;
+        numInts = mAvHandle->numInts;
+        if (numInts > 0) {
+            // Otherwise if the first int in the av native handle returned from the filter
+            // event has value, use it as the index
+            memIndex = mAvHandle->data[mAvHandle->numFds];
+        } else {
+            if (mFilter->mAvSharedHandle != NULL) {
+                numInts = mFilter->mAvSharedHandle->numInts;
+                if (numInts > 0) {
+                    // If the first int in the shared native handle has value, use it as the index
+                    memIndex = mFilter->mAvSharedHandle->data[mFilter->mAvSharedHandle->numFds];
+                }
+            }
+        }
+    }
+
+    mIonHandle = new C2HandleIon(dup(fd), dataSize);
     std::shared_ptr<C2LinearBlock> block = _C2BlockFactory::CreateLinearBlock(mIonHandle);
     if (block != nullptr) {
         // CreateLinearBlock delete mIonHandle after it create block successfully.
@@ -358,13 +398,11 @@
         JNIEnv *env = AndroidRuntime::getJNIEnv();
         std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
         context->mBlock = block;
-        std::shared_ptr<C2Buffer> pC2Buffer = context->toC2Buffer(0, mDataLength);
+        std::shared_ptr<C2Buffer> pC2Buffer = context->toC2Buffer(0, dataSize);
         context->mBuffer = pC2Buffer;
         mC2Buffer = pC2Buffer;
-        if (mAvHandle->numInts > 0) {
-            // use first int in the native_handle as the index
-            int index = mAvHandle->data[mAvHandle->numFds];
-            std::shared_ptr<C2Param> c2param = std::make_shared<C2DataIdInfo>(index, mDataId);
+        if (numInts > 0) {
+            std::shared_ptr<C2Param> c2param = std::make_shared<C2DataIdInfo>(memIndex, mDataId);
             std::shared_ptr<C2Info> info(std::static_pointer_cast<C2Info>(c2param));
             pC2Buffer->setInfo(info);
         }
@@ -471,7 +509,7 @@
 
         if (mediaEvent.avMemory.getNativeHandle() != NULL || mediaEvent.avDataId != 0) {
             sp<MediaEvent> mediaEventSp =
-                           new MediaEvent(mIFilter, mediaEvent.avMemory,
+                           new MediaEvent(mFilter, mediaEvent.avMemory,
                                mediaEvent.avDataId, dataLength + offset, obj);
             mediaEventSp->mAvHandleRefCnt++;
             env->SetLongField(obj, eventContext, (jlong) mediaEventSp.get());
@@ -505,10 +543,11 @@
 }
 
 jobjectArray FilterCallback::getTsRecordEvent(
-        jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events) {
+        jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events,
+                const std::vector<DemuxFilterEventExt::Event>& eventsExt) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TsRecordEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJ)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJ)V");
 
     for (int i = 0; i < events.size(); i++) {
         auto event = events[i];
@@ -537,28 +576,39 @@
 
         jlong byteNumber = static_cast<jlong>(tsRecordEvent.byteNumber);
 
+        jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].tsRecord().pts)
+                : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+
         jobject obj =
-                env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber);
+                env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts);
         env->SetObjectArrayElement(arr, i, obj);
     }
     return arr;
 }
 
 jobjectArray FilterCallback::getMmtpRecordEvent(
-        jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events) {
+        jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events,
+                const std::vector<DemuxFilterEventExt::Event>& eventsExt) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJ)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJ)V");
 
     for (int i = 0; i < events.size(); i++) {
         auto event = events[i];
+
         DemuxFilterMmtpRecordEvent mmtpRecordEvent = event.mmtpRecord();
 
         jint scHevcIndexMask = static_cast<jint>(mmtpRecordEvent.scHevcIndexMask);
         jlong byteNumber = static_cast<jlong>(mmtpRecordEvent.byteNumber);
+        jint mpuSequenceNumber = (eventsExt.size() > i)
+                ? static_cast<jint>(eventsExt[i].mmtpRecord().mpuSequenceNumber)
+                : static_cast<jint>(Constant::INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
+        jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].mmtpRecord().pts)
+                : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
 
         jobject obj =
-                env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber);
+                env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
+                        mpuSequenceNumber, pts);
         env->SetObjectArrayElement(arr, i, obj);
     }
     return arr;
@@ -627,12 +677,14 @@
     return arr;
 }
 
-Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& filterEvent) {
-    ALOGD("FilterCallback::onFilterEvent");
+Return<void> FilterCallback::onFilterEvent_1_1(const DemuxFilterEvent& filterEvent,
+        const DemuxFilterEventExt& filterEventExt) {
+    ALOGD("FilterCallback::onFilterEvent_1_1");
 
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
     std::vector<DemuxFilterEvent::Event> events = filterEvent.events;
+    std::vector<DemuxFilterEventExt::Event> eventsExt = filterEventExt.events;
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/FilterEvent");
     jobjectArray array = env->NewObjectArray(events.size(), eventClazz, NULL);
 
@@ -652,11 +704,11 @@
                 break;
             }
             case DemuxFilterEvent::Event::hidl_discriminator::tsRecord: {
-                array = getTsRecordEvent(array, events);
+                array = getTsRecordEvent(array, events, eventsExt);
                 break;
             }
             case DemuxFilterEvent::Event::hidl_discriminator::mmtpRecord: {
-                array = getMmtpRecordEvent(array, events);
+                array = getMmtpRecordEvent(array, events, eventsExt);
                 break;
             }
             case DemuxFilterEvent::Event::hidl_discriminator::download: {
@@ -677,18 +729,26 @@
         }
     }
     env->CallVoidMethod(
-            mFilter,
+            mFilter->mFilterObj,
             gFields.onFilterEventID,
             array);
     return Void();
 }
 
+Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& filterEvent) {
+    ALOGD("FilterCallback::onFilterEvent");
+    std::vector<DemuxFilterEventExt::Event> emptyEventsExt;
+    DemuxFilterEventExt emptyFilterEventExt {
+            .events = emptyEventsExt,
+    };
+    return onFilterEvent_1_1(filterEvent, emptyFilterEventExt);
+}
 
 Return<void> FilterCallback::onFilterStatus(const DemuxFilterStatus status) {
     ALOGD("FilterCallback::onFilterStatus");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     env->CallVoidMethod(
-            mFilter,
+            mFilter->mFilterObj,
             gFields.onFilterStatusID,
             (jint)status);
     return Void();
@@ -696,17 +756,11 @@
 
 void FilterCallback::setFilter(const sp<Filter> filter) {
     ALOGD("FilterCallback::setFilter");
-    mFilter = filter->mFilterObj;
-    mIFilter = filter->mFilterSp;
+    // JNI Object
+    mFilter = filter;
 }
 
-FilterCallback::~FilterCallback() {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    if (mFilter != NULL) {
-        env->DeleteWeakGlobalRef(mFilter);
-        mFilter = NULL;
-    }
-}
+FilterCallback::~FilterCallback() {}
 
 /////////////// Filter ///////////////////////
 
@@ -919,6 +973,8 @@
 /////////////// Tuner ///////////////////////
 
 sp<ITuner> JTuner::mTuner;
+sp<::android::hardware::tv::tuner::V1_1::ITuner> JTuner::mTuner_1_1;
+int JTuner::mTunerVersion = 0;
 
 JTuner::JTuner(JNIEnv *env, jobject thiz)
     : mClass(NULL) {
@@ -950,13 +1006,28 @@
 
 sp<ITuner> JTuner::getTunerService() {
     if (mTuner == nullptr) {
-        mTuner = ITuner::getService();
+        mTunerVersion = 0;
+        mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
 
-        if (mTuner == nullptr) {
-            ALOGW("Failed to get tuner service.");
-        }
-    }
-    return mTuner;
+        if (mTuner_1_1 == nullptr) {
+            ALOGW("Failed to get tuner 1.1 service.");
+            mTuner = ITuner::getService();
+            if (mTuner == nullptr) {
+                ALOGW("Failed to get tuner 1.0 service.");
+            } else {
+                mTunerVersion = 1 << 16;
+            }
+        } else {
+            mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
+            mTunerVersion = ((1 << 16) | 1);
+         }
+     }
+     return mTuner;
+}
+
+jint JTuner::getTunerVersion() {
+    ALOGD("JTuner::getTunerVersion()");
+    return (jint) mTunerVersion;
 }
 
 jobject JTuner::getFrontendIds() {
@@ -996,6 +1067,7 @@
         return NULL;
     }
     mFe = fe;
+    mFe_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFe);
     mFeId = id;
     if (mDemux != NULL) {
         mDemux->setFrontendDataSource(mFeId);
@@ -1455,6 +1527,27 @@
     return (int) r;
 }
 
+int JTuner::linkCiCam(int id) {
+    if (mFe_1_1 == NULL) {
+        ALOGE("frontend 1.1 is not initialized");
+        return (int)Constant::INVALID_LTS_ID;
+    }
+
+    Result res;
+    uint32_t ltsId;
+    mFe_1_1->linkCiCam(static_cast<uint32_t>(id),
+            [&](Result r, uint32_t id) {
+                res = r;
+                ltsId = id;
+            });
+
+    if (res != Result::SUCCESS) {
+        return (int)Constant::INVALID_LTS_ID;
+    }
+
+    return (int) ltsId;
+}
+
 int JTuner::disconnectCiCam() {
     if (mDemux == NULL) {
         Result r = openDemux();
@@ -1466,6 +1559,18 @@
     return (int) r;
 }
 
+
+int JTuner::unlinkCiCam(int id) {
+    if (mFe_1_1 == NULL) {
+        ALOGE("frontend 1.1 is not initialized");
+        return (int)Result::INVALID_STATE;
+    }
+
+    Result r = mFe_1_1->unlinkCiCam(static_cast<uint32_t>(id));
+
+    return (int) r;
+}
+
 jobject JTuner::openDescrambler() {
     ALOGD("JTuner::openDescrambler");
     if (mTuner == nullptr || mDemux == nullptr) {
@@ -1504,6 +1609,7 @@
     }
 
     sp<IFilter> iFilterSp;
+    sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
     sp<FilterCallback> callback = new FilterCallback();
     Result res;
     mDemux->openFilter(type, bufferSize, callback,
@@ -1515,24 +1621,54 @@
         ALOGD("Failed to open filter, type = %d", type.mainType);
         return NULL;
     }
-    int fId;
+    uint64_t fId;
     iFilterSp->getId([&](Result, uint32_t filterId) {
         fId = filterId;
     });
+    iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+    if (iFilterSp_1_1 != NULL) {
+        iFilterSp_1_1->getId64Bit([&](Result, uint64_t filterId64Bit) {
+            fId = filterId64Bit;
+        });
+    }
 
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jobject filterObj =
             env->NewObject(
                     env->FindClass("android/media/tv/tuner/filter/Filter"),
                     gFields.filterInitID,
-                    (jint) fId);
+                    (jlong) fId);
 
     sp<Filter> filterSp = new Filter(iFilterSp, filterObj);
     filterSp->incStrong(filterObj);
     env->SetLongField(filterObj, gFields.filterContext, (jlong)filterSp.get());
-
+    filterSp->mIsMediaFilter = false;
+    filterSp->mAvSharedHandle = NULL;
     callback->setFilter(filterSp);
 
+    if (type.mainType == DemuxFilterMainType::MMTP) {
+        if (type.subType.mmtpFilterType() == DemuxMmtpFilterType::AUDIO ||
+                type.subType.mmtpFilterType() == DemuxMmtpFilterType::VIDEO) {
+            filterSp->mIsMediaFilter = true;
+        }
+    }
+
+    if (type.mainType == DemuxFilterMainType::TS) {
+        if (type.subType.tsFilterType() == DemuxTsFilterType::AUDIO ||
+                type.subType.tsFilterType() == DemuxTsFilterType::VIDEO) {
+            filterSp->mIsMediaFilter = true;
+        }
+    }
+
+    if (iFilterSp_1_1 != NULL && filterSp->mIsMediaFilter) {
+        iFilterSp_1_1->getAvSharedHandle([&](Result r, hidl_handle avMemory, uint64_t avMemSize) {
+            if (r == Result::SUCCESS) {
+                filterSp->mAvSharedHandle = native_handle_clone(avMemory.getNativeHandle());
+                filterSp->mAvSharedMemSize = avMemSize;
+            }
+        });
+    }
+
     return filterObj;
 }
 
@@ -1902,6 +2038,10 @@
     if (mFe != NULL) {
         r = mFe->close();
     }
+    if (r == Result::SUCCESS) {
+        mFe = NULL;
+        mFe_1_1 = NULL;
+    }
     return (jint) r;
 }
 
@@ -1910,6 +2050,9 @@
     if (mDemux != NULL) {
         r = mDemux->close();
     }
+    if (r == Result::SUCCESS) {
+        mDemux = NULL;
+    }
     return (jint) r;
 }
 
@@ -2460,7 +2603,7 @@
     jclass filterClazz = env->FindClass("android/media/tv/tuner/filter/Filter");
     gFields.filterContext = env->GetFieldID(filterClazz, "mNativeContext", "J");
     gFields.filterInitID =
-            env->GetMethodID(filterClazz, "<init>", "(I)V");
+            env->GetMethodID(filterClazz, "<init>", "(J)V");
     gFields.onFilterStatusID =
             env->GetMethodID(filterClazz, "onFilterStatus", "(I)V");
     gFields.onFilterEventID =
@@ -2501,6 +2644,11 @@
     setTuner(env,thiz, tuner);
 }
 
+static jint android_media_tv_Tuner_native_get_tuner_version(JNIEnv *env, jobject thiz) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getTunerVersion();
+}
+
 static jobject android_media_tv_Tuner_get_frontend_ids(JNIEnv *env, jobject thiz) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->getFrontendIds();
@@ -2579,11 +2727,21 @@
     return tuner->connectCiCam(id);
 }
 
+static int android_media_tv_Tuner_link_cicam(JNIEnv *env, jobject thiz, jint id) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->linkCiCam(id);
+}
+
 static int android_media_tv_Tuner_disconnect_cicam(JNIEnv *env, jobject thiz) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->disconnectCiCam();
 }
 
+static int android_media_tv_Tuner_unlink_cicam(JNIEnv *env, jobject thiz, jint id) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->unlinkCiCam(id);
+}
+
 static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thiz, jint id) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->getFrontendInfo(id);
@@ -2991,6 +3149,29 @@
     return filterSettings;
 }
 
+static Result configureIpFilterContextId(
+        JNIEnv *env, sp<IFilter> iFilterSp, jobject ipFilterConfigObj) {
+    jclass clazz = env->FindClass(
+            "android/media/tv/tuner/filter/IpFilterConfiguration");
+    uint32_t cid = env->GetIntField(ipFilterConfigObj, env->GetFieldID(
+            clazz, "mIpFilterContextId", "I"));
+    Result res = Result::SUCCESS;
+    if (cid != static_cast<uint32_t>(Constant::INVALID_IP_FILTER_CONTEXT_ID)) {
+        sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
+        iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+
+        if (iFilterSp_1_1 != NULL) {
+            res = iFilterSp_1_1->configureIpCid(cid);
+            if (res != Result::SUCCESS) {
+                return res;
+            }
+        } else {
+            ALOGW("configureIpCid is not supported with the current HAL implementation.");
+        }
+    }
+    return res;
+}
+
 static jint copyData(JNIEnv *env, std::unique_ptr<MQ>& mq, EventFlag* flag, jbyteArray buffer,
         jlong offset, jlong size) {
     ALOGD("copyData, size=%ld, offset=%ld", (long) size, (long) offset);
@@ -3034,6 +3215,13 @@
         return (jint) res;
     }
 
+    if (static_cast<DemuxFilterMainType>(type) == DemuxFilterMainType::IP) {
+        res = configureIpFilterContextId(env, iFilterSp, settings);
+        if (res != Result::SUCCESS) {
+            return (jint) res;
+        }
+    }
+
     MQDescriptorSync<uint8_t> filterMQDesc;
     Result getQueueDescResult = Result::UNKNOWN_ERROR;
     if (filterSp->mFilterMQ == NULL) {
@@ -3071,6 +3259,36 @@
     return (jint) id;
 }
 
+static jlong android_media_tv_Tuner_get_filter_64bit_id(JNIEnv* env, jobject filter) {
+    sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
+    if (iFilterSp == NULL) {
+        ALOGD("Failed to get filter ID: filter not found");
+        return static_cast<jlong>(
+                ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
+    }
+
+    sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
+    iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+    Result res;
+    uint64_t id;
+
+    if (iFilterSp_1_1 != NULL) {
+        iFilterSp_1_1->getId64Bit(
+            [&](Result r, uint64_t filterId64Bit) {
+                res = r;
+                id = filterId64Bit;
+        });
+    } else {
+        ALOGW("getId64Bit is not supported with the current HAL implementation.");
+        return static_cast<jlong>(
+                ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
+    }
+
+    return (res == Result::SUCCESS) ?
+            static_cast<jlong>(id) : static_cast<jlong>(
+                    ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
+}
+
 static jint android_media_tv_Tuner_set_filter_data_source(
         JNIEnv* env, jobject filter, jobject srcFilter) {
     sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
@@ -3684,6 +3902,7 @@
 static const JNINativeMethod gTunerMethods[] = {
     { "nativeInit", "()V", (void *)android_media_tv_Tuner_native_init },
     { "nativeSetup", "()V", (void *)android_media_tv_Tuner_native_setup },
+    { "nativeGetTunerVersion", "()I", (void *)android_media_tv_Tuner_native_get_tuner_version },
     { "nativeGetFrontendIds", "()Ljava/util/List;",
             (void *)android_media_tv_Tuner_get_frontend_ids },
     { "nativeOpenFrontendByHandle", "(I)Landroid/media/tv/tuner/Tuner$Frontend;",
@@ -3705,6 +3924,10 @@
     { "nativeGetAvSyncTime", "(I)Ljava/lang/Long;",
             (void *)android_media_tv_Tuner_get_av_sync_time },
     { "nativeConnectCiCam", "(I)I", (void *)android_media_tv_Tuner_connect_cicam },
+    { "nativeLinkCiCam", "(I)I",
+            (void *)android_media_tv_Tuner_link_cicam },
+    { "nativeUnlinkCiCam", "(I)I",
+            (void *)android_media_tv_Tuner_unlink_cicam },
     { "nativeDisconnectCiCam", "()I", (void *)android_media_tv_Tuner_disconnect_cicam },
     { "nativeGetFrontendInfo", "(I)Landroid/media/tv/tuner/frontend/FrontendInfo;",
             (void *)android_media_tv_Tuner_get_frontend_info },
@@ -3735,6 +3958,8 @@
     { "nativeConfigureFilter", "(IILandroid/media/tv/tuner/filter/FilterConfiguration;)I",
             (void *)android_media_tv_Tuner_configure_filter },
     { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
+    { "nativeGetId64Bit", "()J",
+            (void *)android_media_tv_Tuner_get_filter_64bit_id },
     { "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
             (void *)android_media_tv_Tuner_set_filter_data_source },
     { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index c4deeaf..5a59a58 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -17,7 +17,12 @@
 #ifndef _ANDROID_MEDIA_TV_TUNER_H_
 #define _ANDROID_MEDIA_TV_TUNER_H_
 
-#include <android/hardware/tv/tuner/1.0/ITuner.h>
+#include <android/hardware/tv/tuner/1.1/IFilter.h>
+#include <android/hardware/tv/tuner/1.1/IFilterCallback.h>
+#include <android/hardware/tv/tuner/1.1/IFrontend.h>
+#include <android/hardware/tv/tuner/1.1/ITuner.h>
+#include <android/hardware/tv/tuner/1.1/types.h>
+
 #include <C2BlockInternal.h>
 #include <C2HandleIonInternal.h>
 #include <C2ParamDef.h>
@@ -38,6 +43,7 @@
 using ::android::hardware::hidl_vec;
 using ::android::hardware::kSynchronizedReadWrite;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterEventExt;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
 using ::android::hardware::tv::tuner::V1_0::DemuxPid;
@@ -54,7 +60,7 @@
 using ::android::hardware::tv::tuner::V1_0::IDvr;
 using ::android::hardware::tv::tuner::V1_0::IDvrCallback;
 using ::android::hardware::tv::tuner::V1_0::IFilter;
-using ::android::hardware::tv::tuner::V1_0::IFilterCallback;
+using ::android::hardware::tv::tuner::V1_1::IFilterCallback;
 using ::android::hardware::tv::tuner::V1_0::IFrontend;
 using ::android::hardware::tv::tuner::V1_0::IFrontendCallback;
 using ::android::hardware::tv::tuner::V1_0::ILnb;
@@ -112,18 +118,32 @@
     int mFd;
 };
 
+struct Filter : public RefBase {
+    Filter(sp<IFilter> sp, jobject obj);
+    ~Filter();
+    int close();
+    sp<IFilter> getIFilter();
+    sp<IFilter> mFilterSp;
+    std::unique_ptr<MQ> mFilterMQ;
+    EventFlag* mFilterMQEventFlag;
+    jweak mFilterObj;
+    native_handle_t* mAvSharedHandle;
+    uint64_t mAvSharedMemSize;
+    bool mIsMediaFilter;
+};
+
 struct MediaEvent : public RefBase {
-    MediaEvent(sp<IFilter> iFilter, hidl_handle avHandle, uint64_t dataId,
-        uint64_t dataLength, jobject obj);
+    MediaEvent(sp<Filter> filter, hidl_handle avHandle, uint64_t dataId,
+        uint64_t dataSize, jobject obj);
     ~MediaEvent();
     jobject getLinearBlock();
     uint64_t getAudioHandle();
     void finalize();
 
-    sp<IFilter> mIFilter;
+    sp<Filter> mFilter;
     native_handle_t* mAvHandle;
     uint64_t mDataId;
-    uint64_t mDataLength;
+    uint64_t mDataSize;
     uint8_t* mBuffer;
     android::Mutex mLock;
     int mDataIdRefCnt;
@@ -134,26 +154,16 @@
     std::weak_ptr<C2Buffer> mC2Buffer;
 };
 
-struct Filter : public RefBase {
-    Filter(sp<IFilter> sp, jobject obj);
-    ~Filter();
-    int close();
-    sp<IFilter> getIFilter();
-    sp<IFilter> mFilterSp;
-    std::unique_ptr<MQ> mFilterMQ;
-    EventFlag* mFilterMQEventFlag;
-    jweak mFilterObj;
-};
-
 struct FilterCallback : public IFilterCallback {
     ~FilterCallback();
+    virtual Return<void> onFilterEvent_1_1(const DemuxFilterEvent& filterEvent,
+            const DemuxFilterEventExt& filterEventExt);
     virtual Return<void> onFilterEvent(const DemuxFilterEvent& filterEvent);
     virtual Return<void> onFilterStatus(const DemuxFilterStatus status);
 
     void setFilter(const sp<Filter> filter);
 private:
-    jweak mFilter;
-    sp<IFilter> mIFilter;
+    sp<Filter> mFilter;
     jobjectArray getSectionEvent(
             jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
     jobjectArray getMediaEvent(
@@ -161,9 +171,11 @@
     jobjectArray getPesEvent(
             jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
     jobjectArray getTsRecordEvent(
-            jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
+            jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>&events,
+                    const std::vector<DemuxFilterEventExt::Event>& eventsExt);
     jobjectArray getMmtpRecordEvent(
-            jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
+            jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>&events,
+                    const std::vector<DemuxFilterEventExt::Event>& eventsExt);
     jobjectArray getDownloadEvent(
             jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
     jobjectArray getIpPayloadEvent(
@@ -194,10 +206,13 @@
 struct JTuner : public RefBase {
     JTuner(JNIEnv *env, jobject thiz);
     sp<ITuner> getTunerService();
+    int getTunerVersion();
     jobject getAvSyncHwId(sp<Filter> filter);
     jobject getAvSyncTime(jint id);
     int connectCiCam(jint id);
+    int linkCiCam(jint id);
     int disconnectCiCam();
+    int unlinkCiCam(jint id);
     jobject getFrontendIds();
     jobject openFrontendById(int id);
     jint closeFrontendById(int id);
@@ -229,8 +244,13 @@
     jclass mClass;
     jweak mObject;
     static sp<ITuner> mTuner;
+    static sp<::android::hardware::tv::tuner::V1_1::ITuner> mTuner_1_1;
+    // An integer that carries the Tuner version. The high 16 bits are the major version number
+    // while the low 16 bits are the minor version. Default value is unknown version 0.
+    static int mTunerVersion;
     hidl_vec<FrontendId> mFeIds;
     sp<IFrontend> mFe;
+    sp<::android::hardware::tv::tuner::V1_1::IFrontend> mFe_1_1;
     int mFeId;
     hidl_vec<LnbId> mLnbIds;
     sp<ILnb> mLnb;
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 45c49e5..0d53ab1 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -331,7 +331,7 @@
     }
 
     if (deviceType != AUDIO_DEVICE_NONE) {
-        device.mType = deviceType;
+        device.mType = (audio_devices_t)deviceType;
         ScopedUtfChars address(env, deviceAddress);
         device.setAddress(address.c_str());
     }
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index ca3cc855..26725f8 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -200,7 +200,7 @@
     paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
     paa->content_type =
             (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
-    paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
+    paa->flags = (audio_flags_mask_t) env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
 
     ALOGV("android_media_SoundPool_native_setup");
     auto *ap = new SoundPool(maxChannels, paa);
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index 5a0a50c..4d0c258 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -11,7 +11,8 @@
     static_libs: [
         "android-support-test",
         "mockito-target-minus-junit4",
-        "testng"
+        "testng",
+        "truth-prebuilt",
     ],
 
     platform_apis: true,
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouteInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouteInfoTest.java
new file mode 100644
index 0000000..92e4c95
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouteInfoTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.mediaroutertest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.display.DisplayManagerGlobal;
+import android.media.MediaRouter;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaRouteInfoTest {
+    private TestableRouteInfo mRouteInfo;
+    private static Display sWifiDisplay;
+    private static Display sExternalDisplay;
+    private static Display sInternalDisplay;
+    private static final String FAKE_MAC_ADDRESS = "fake MAC address";
+
+    @BeforeClass
+    public static void setUpOnce() {
+        final DisplayManagerGlobal global = DisplayManagerGlobal.getInstance();
+        final DisplayInfo wifiInfo = new DisplayInfo();
+        wifiInfo.flags = Display.FLAG_PRESENTATION;
+        wifiInfo.type = Display.TYPE_WIFI;
+        wifiInfo.address = DisplayAddress.fromMacAddress(FAKE_MAC_ADDRESS);
+        sWifiDisplay = new Display(global, 2, wifiInfo, new DisplayAdjustments());
+
+        final DisplayInfo externalInfo = new DisplayInfo();
+        externalInfo.flags = Display.FLAG_PRESENTATION;
+        externalInfo.type = Display.TYPE_EXTERNAL;
+        sExternalDisplay = new Display(global, 3, externalInfo,  new DisplayAdjustments());
+
+        final DisplayInfo internalInfo = new DisplayInfo();
+        internalInfo.flags = Display.FLAG_PRESENTATION;
+        internalInfo.type = Display.TYPE_INTERNAL;
+        sInternalDisplay = new Display(global, 4, internalInfo,  new DisplayAdjustments());
+    }
+
+    @Before
+    public void setUp() {
+        mRouteInfo = new TestableRouteInfo();
+    }
+
+    @Test
+    public void testGetPresentationDisplay_notLiveVideo() {
+        mRouteInfo.setPresentationDisplays(sWifiDisplay);
+        mRouteInfo.mSupportedType = MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isNull();
+    }
+
+    @Test
+    public void testGetPresentationDisplay_includesLiveVideo() {
+        mRouteInfo.setPresentationDisplays(sWifiDisplay);
+        mRouteInfo.mSupportedType |= MediaRouter.ROUTE_TYPE_LIVE_AUDIO;
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+    }
+
+    @Test
+    public void testGetPresentationDisplay_noPresentationDisplay() {
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isNull();
+    }
+
+    @Test
+    public void testGetPresentationDisplay_wifiDisplayOnly() {
+        mRouteInfo.setPresentationDisplays(sWifiDisplay);
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+    }
+
+    @Test
+    public void testGetPresentationDisplay_externalDisplayOnly() {
+        mRouteInfo.setPresentationDisplays(sExternalDisplay);
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sExternalDisplay);
+    }
+
+    @Test
+    public void testGetPresentationDisplay_internalDisplayOnly() {
+        mRouteInfo.setPresentationDisplays(sInternalDisplay);
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sInternalDisplay);
+    }
+
+    @Test
+    public void testGetPresentationDisplay_addressNotMatch() {
+        mRouteInfo.setPresentationDisplays(sWifiDisplay);
+        mRouteInfo.mDeviceAddress = "Not match";
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isNull();
+    }
+
+    @Test
+    public void testGetPresentationDisplay_containsWifiAndExternalDisplays_returnWifiDisplay() {
+        mRouteInfo.setPresentationDisplays(sExternalDisplay, sWifiDisplay);
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+    }
+
+    @Test
+    public void testGetPresentationDisplay_containsExternalAndInternalDisplays_returnExternal() {
+        mRouteInfo.setPresentationDisplays(sExternalDisplay, sInternalDisplay);
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sExternalDisplay);
+    }
+
+    @Test
+    public void testGetPresentationDisplay_containsAllDisplays_returnWifiDisplay() {
+        mRouteInfo.setPresentationDisplays(sExternalDisplay, sInternalDisplay, sWifiDisplay);
+
+        mRouteInfo.updatePresentationDisplay();
+
+        assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+    }
+
+    private static class TestableRouteInfo extends MediaRouter.RouteInfo {
+        private Display[] mDisplays = new Display[0];
+        private int mSupportedType = MediaRouter.ROUTE_TYPE_LIVE_VIDEO;
+        private String mDeviceAddress = FAKE_MAC_ADDRESS;
+        private MediaRouter.RouteInfo mDefaultRouteInfo = null;
+
+        private TestableRouteInfo() {
+            super(null);
+        }
+
+        private void setPresentationDisplays(Display ...displays) {
+            mDisplays = new Display[displays.length];
+            System.arraycopy(displays, 0, mDisplays, 0, displays.length);
+        }
+
+        @Override
+        public Display[] getAllPresentationDisplays() {
+            return mDisplays;
+        }
+
+        @Override
+        public int getSupportedTypes() {
+            return mSupportedType;
+        }
+
+        @Override
+        public String getDeviceAddress() {
+            return mDeviceAddress;
+        }
+
+        @Override
+        public MediaRouter.RouteInfo getDefaultAudioVideo() {
+            return mDefaultRouteInfo;
+        }
+    }
+}
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index 56f3906..ac4c16a 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -346,3 +346,25 @@
 void AImageDecoder_delete(AImageDecoder* decoder) {
     delete toDecoder(decoder);
 }
+
+bool AImageDecoder_isAnimated(AImageDecoder* decoder) {
+    if (!decoder) return false;
+
+    ImageDecoder* imageDecoder = toDecoder(decoder);
+    return imageDecoder->mCodec->codec()->getFrameCount() > 1;
+}
+
+int32_t AImageDecoder_getRepeatCount(AImageDecoder* decoder) {
+    if (!decoder) return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+
+    ImageDecoder* imageDecoder = toDecoder(decoder);
+    const int count = imageDecoder->mCodec->codec()->getRepetitionCount();
+
+    // Skia should not report anything out of range, but defensively treat
+    // negative and too big as INFINITE.
+    if (count == SkCodec::kRepetitionCountInfinite || count < 0
+        || count > std::numeric_limits<int32_t>::max()) {
+        return ANDROID_IMAGE_DECODER_INFINITE;
+    }
+    return count;
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index 01c1477..a184ab9 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -13,6 +13,8 @@
     AImageDecoder_setTargetSize; # introduced=30
     AImageDecoder_computeSampledSize; # introduced=30
     AImageDecoder_setCrop; # introduced=30
+    AImageDecoder_isAnimated; # introduced=31
+    AImageDecoder_getRepeatCount; # introduced=31
     AImageDecoderHeaderInfo_getWidth; # introduced=30
     AImageDecoderHeaderInfo_getHeight; # introduced=30
     AImageDecoderHeaderInfo_getMimeType; # introduced=30
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index a0aa0e0..648ad56 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -2889,6 +2889,7 @@
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
     field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
     field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
+    field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b
     field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
     field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
     field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
@@ -2897,11 +2898,13 @@
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
     field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
     field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
+    field public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44; // 0x2c
     field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
     field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
     field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20
     field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
     field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
+    field public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45; // 0x2d
     field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26
     field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a
     field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
@@ -10664,12 +10667,15 @@
     field public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
     field public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
     field public static final String ACTION_PACKAGE_FIRST_LAUNCH = "android.intent.action.PACKAGE_FIRST_LAUNCH";
+    field public static final String ACTION_PACKAGE_FULLY_LOADED = "android.intent.action.PACKAGE_FULLY_LOADED";
     field public static final String ACTION_PACKAGE_FULLY_REMOVED = "android.intent.action.PACKAGE_FULLY_REMOVED";
     field @Deprecated public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL";
     field public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION";
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+    field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
+    field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -10834,6 +10840,7 @@
     field public static final String EXTRA_TIMEZONE = "time-zone";
     field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
     field public static final String EXTRA_UID = "android.intent.extra.UID";
+    field public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
     field public static final int FILL_IN_ACTION = 1; // 0x1
     field public static final int FILL_IN_CATEGORIES = 4; // 0x4
@@ -12293,6 +12300,9 @@
     field public static final int SYNCHRONOUS = 2; // 0x2
     field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
     field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
+    field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
+    field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
+    field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
@@ -25438,6 +25448,7 @@
 
   public static final class MediaCodec.CryptoInfo {
     ctor public MediaCodec.CryptoInfo();
+    method @NonNull public android.media.MediaCodec.CryptoInfo.Pattern getPattern();
     method public void set(int, @NonNull int[], @NonNull int[], @NonNull byte[], @NonNull byte[], int);
     method public void setPattern(android.media.MediaCodec.CryptoInfo.Pattern);
     field public byte[] iv;
@@ -26366,7 +26377,7 @@
     method public boolean containsKey(String);
     method public int describeContents();
     method public android.graphics.Bitmap getBitmap(String);
-    method @IntRange(from=0) public int getBitmapDimensionLimit();
+    method @IntRange(from=1) public int getBitmapDimensionLimit();
     method @NonNull public android.media.MediaDescription getDescription();
     method public long getLong(String);
     method public android.media.Rating getRating(String);
@@ -26416,7 +26427,7 @@
     method public android.media.MediaMetadata.Builder putRating(String, android.media.Rating);
     method public android.media.MediaMetadata.Builder putString(String, String);
     method public android.media.MediaMetadata.Builder putText(String, CharSequence);
-    method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(int);
+    method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(@IntRange(from=1) int);
   }
 
   @Deprecated public abstract class MediaMetadataEditor {
@@ -42234,11 +42245,13 @@
     method public long getLastAudiblyAlertedMillis();
     method public String getOverrideGroupKey();
     method public int getRank();
+    method @Nullable public android.content.pm.ShortcutInfo getShortcutInfo();
     method @NonNull public java.util.List<android.app.Notification.Action> getSmartActions();
     method @NonNull public java.util.List<java.lang.CharSequence> getSmartReplies();
     method public int getSuppressedVisualEffects();
     method public int getUserSentiment();
     method public boolean isAmbient();
+    method public boolean isConversation();
     method public boolean isSuspended();
     method public boolean matchesInterruptionFilter();
     field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
@@ -46574,6 +46587,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String[] getForbiddenPlmns();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getGroupIdLevel1();
     method public String getIccAuthentication(int, int, String);
@@ -46598,7 +46612,7 @@
     method @Deprecated public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
-    method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
     method @Nullable public android.telephony.SignalStrength getSignalStrength();
     method public int getSimCarrierId();
     method @Nullable public CharSequence getSimCarrierIdName();
@@ -46621,7 +46635,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType();
-    method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
+    method @Nullable public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
     method public boolean hasCarrierPrivileges();
     method public boolean hasIccCard();
     method @Deprecated public boolean iccCloseLogicalChannel(int);
diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt
index 6e3cec6..e135a3b 100644
--- a/non-updatable-api/module-lib-current.txt
+++ b/non-updatable-api/module-lib-current.txt
@@ -1,10 +1,20 @@
 // Signature format: 2.0
 package android.app {
 
+  public class ActivityManager {
+    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
+    method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
+  }
+
   public class AppOpsManager {
     field public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
   }
 
+  public abstract class HomeVisibilityListener {
+    ctor public HomeVisibilityListener();
+    method public abstract void onHomeVisibilityChanged(boolean);
+  }
+
   public class NotificationManager {
     method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
     field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
@@ -53,6 +63,10 @@
     field public static final int FLAG_FROM_KEY = 4096; // 0x1000
   }
 
+  public class MediaMetadataRetriever implements java.lang.AutoCloseable {
+    field public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40; // 0x28
+  }
+
 }
 
 package android.media.session {
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 3550ef9..e0395d4 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -4979,6 +4979,7 @@
     method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
     method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+    method public int linkFrontendToCiCam(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
     method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
     method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
@@ -4992,9 +4993,13 @@
     method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
     method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
     method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
+    method public int unlinkFrontendToCiCam(int);
     method public void updateResourcePriority(int, int);
     field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
     field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
+    field public static final long INVALID_FILTER_ID_64BIT = -1L; // 0xffffffffffffffffL
+    field public static final int INVALID_LTS_ID = -1; // 0xffffffff
+    field public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM = -1; // 0xffffffff
     field public static final int INVALID_STREAM_ID = 65535; // 0xffff
     field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
     field public static final int INVALID_TS_PID = 65535; // 0xffff
@@ -5014,6 +5019,13 @@
     method public void onResourceLost(@NonNull android.media.tv.tuner.Tuner);
   }
 
+  public final class TunerVersionChecker {
+    method public static int getTunerVersion();
+    field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
+    field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
+    field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
+  }
+
 }
 
 package android.media.tv.tuner.dvr {
@@ -5147,6 +5159,7 @@
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
     method public int flush();
     method public int getId();
+    method public long getId64Bit();
     method public int read(@NonNull byte[], long, long);
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
     method public int start();
@@ -5198,16 +5211,19 @@
     method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
     method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
     method public int getDstPort();
+    method public int getIpFilterContextId();
     method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
     method public int getSrcPort();
     method public int getType();
     method public boolean isPassthrough();
+    field public static final int INVALID_IP_FILTER_CONTEXT_ID = -1; // 0xffffffff
   }
 
   public static final class IpFilterConfiguration.Builder {
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration build();
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstIpAddress(@NonNull byte[]);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstPort(int);
+    method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setIpFilterContextId(int);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setPassthrough(boolean);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSettings(@Nullable android.media.tv.tuner.filter.Settings);
     method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSrcIpAddress(@NonNull byte[]);
@@ -5247,6 +5263,8 @@
 
   public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
+    method public int getMpuSequenceNumber();
+    method public long getPts();
     method public int getScHevcIndexMask();
   }
 
@@ -5409,6 +5427,7 @@
   public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
     method public int getPacketId();
+    method public long getPts();
     method public int getScIndexMask();
     method public int getTsIndexMask();
   }
@@ -9889,6 +9908,25 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
@@ -10382,7 +10420,6 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDeviceSoftwareVersion(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 023b5b4..6d63e31 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -567,6 +567,9 @@
                         Log.e(TAG, "Failed to switch to new user: " + user.id);
                     }
                 }
+                if (mAddUserView != null) {
+                    mAddUserView.setEnabled(true);
+                }
             }
         }
 
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
new file mode 100644
index 0000000..5b96da0
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
@@ -0,0 +1,366 @@
+# 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.
+
+#
+# Turkish F keyboard layout.
+#
+
+type OVERLAY
+
+map key 12 SLASH
+map key 13 MINUS
+map key 43 COMMA
+map key 51 EQUALS
+map key 52 BACKSLASH
+map key 53 PERIOD
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+    label:                              '+'
+    base:                               '+'
+    shift:                              '*'
+    ralt:                               '\u00ac'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+    ralt:                               '\u00b9'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '"'
+    ralt:                               '\u00b2'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '^'
+    ralt:                               '#'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+    ralt:                               '\u00bc'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+    ralt:                               '\u00bd'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '&'
+    ralt:                               '\u00be'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '\''
+    ralt:                               '{'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '('
+    ralt:                               '['
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              ')'
+    ralt:                               ']'
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              '='
+    ralt:                               '}'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+    ralt:                               '\\'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+    ralt:                               '|'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+    ralt:                               '@'
+}
+
+key W {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+}
+
+key E {
+    label:                              '\u011f'
+    base:                               '\u011f'
+    shift, capslock:                    '\u011e'
+}
+
+key R {
+    label:                              '\u0131'
+    base:                               '\u0131'
+    shift, capslock:                    'I'
+    ralt:                               '\u00b6'
+    ralt+shift, ralt+capslock:          '\u00ae'
+}
+
+key T {
+    label:                              'O'
+    base:                               'o'
+    shift, capslock:                    'O'
+}
+
+key Y {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+    ralt:                               '\u00a5'
+}
+
+key U {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+}
+
+key I {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+}
+
+key O {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+    ralt:                               '\u00f8'
+    ralt+shift, ralt+capslock:          '\u00d8'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+    ralt:                               '\u00a3'
+}
+
+key LEFT_BRACKET {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+    ralt:                               '"'
+}
+
+key RIGHT_BRACKET {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+    ralt:                               '~'
+}
+
+### ROW 3
+
+key A {
+    label:                              '\u0075'
+    base:                               '\u0075'
+    shift, capslock:                    '\u0055'
+    ralt:                               '\u00e6'
+    ralt+shift, ralt+capslock:          '\u00c6'
+}
+
+key S {
+    label:                              'i'
+    base:                               'i'
+    shift, capslock:                    '\u0130'
+    ralt:                               '\u00df'
+    ralt+shift, ralt+capslock:          '\u00a7'
+}
+
+key D {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+    ralt:                               '\u20ac'
+}
+
+key F {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+    ralt:                               '\u00aa'
+}
+
+key G {
+    label:                              '\u00fc'
+    base:                               '\u00fc'
+    shift, capslock:                    '\u00dc'
+}
+
+key H {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+    ralt:                               '\u20ba'
+}
+
+key J {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+}
+
+key K {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+}
+
+key SEMICOLON {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+    ralt:                               '\u00b4'
+}
+
+key APOSTROPHE {
+    label:                              '\u015f'
+    base:                               '\u015f'
+    shift, capslock:                    '\u015e'
+}
+
+key COMMA {
+    label:                              'X'
+    base:                               'x'
+    shift:                              'X'
+    ralt:                               '\u0060'
+}
+
+### ROW 4
+
+key PLUS {
+    label:                              '<'
+    base:                               '<'
+    shift:                              '>'
+    ralt:                               '|'
+    ralt+shift, ralt+capslock:          '\u00a6'
+}
+
+key Z {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+    ralt:                               '\u00ab'
+    ralt+shift, ralt+capslock:          '<'
+}
+
+key X {
+    label:                              '\u00f6'
+    base:                               '\u00f6'
+    shift, capslock:                    '\u00d6'
+    ralt:                               '\u00bb'
+    ralt+shift, ralt+capslock:          '>'
+}
+
+key C {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+    ralt:                               '\u00a2'
+    ralt+shift, ralt+capslock:          '\u00a9'
+}
+
+key V {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+}
+
+key B {
+    label:                              '\u00e7'
+    base:                               '\u00e7'
+    shift, capslock:                    '\u00c7'
+}
+
+key N {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+}
+
+key M {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+    ralt:                               '\u00b5'
+    ralt+shift, ralt+capslock:          '\u00ba'
+}
+
+key EQUALS {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+    ralt:                               '\u00d7'
+}
+
+key BACKSLASH {
+    label:                              '.'
+    base:                               '.'
+    shift, capslock:                    ':'
+    ralt:                               '\u00f7'
+}
+
+key PERIOD {
+    label:                              ','
+    base:                               ','
+    shift:                              ';'
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 9d068fe..1e13940 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -105,6 +105,9 @@
     <!-- Turkish keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_turkish">Turkish</string>
 
+    <!-- Turkish keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_turkish_f">Turkish F</string>
+
     <!-- Ukrainian keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_ukrainian">Ukrainian</string>
 
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 6b78b68..976a279 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -132,6 +132,10 @@
             android:label="@string/keyboard_layout_turkish"
             android:keyboardLayout="@raw/keyboard_layout_turkish" />
 
+    <keyboard-layout android:name="keyboard_layout_turkish_f"
+            android:label="@string/keyboard_layout_turkish_f"
+            android:keyboardLayout="@raw/keyboard_layout_turkish_f" />
+
     <keyboard-layout android:name="keyboard_layout_ukrainian"
             android:label="@string/keyboard_layout_ukrainian"
             android:keyboardLayout="@raw/keyboard_layout_ukrainian" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 81cf118..b0a9136 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -1090,7 +1090,7 @@
 
         // Verify second update AP is the same object as the first update AP
         assertThat(passpointAccessPointsFirstUpdate.get(0))
-                .isSameAs(passpointAccessPointsSecondUpdate.get(0));
+                .isSameInstanceAs(passpointAccessPointsSecondUpdate.get(0));
         // Verify second update AP has the average of the first and second update RSSIs
         assertThat(passpointAccessPointsSecondUpdate.get(0).getRssi())
                 .isEqualTo((prevRssi + newRssi) / 2);
@@ -1210,7 +1210,8 @@
                 providersAndScans, cachedAccessPoints);
 
         // Verify second update AP is the same object as the first update AP
-        assertThat(osuAccessPointsFirstUpdate.get(0)).isSameAs(osuAccessPointsSecondUpdate.get(0));
+        assertThat(osuAccessPointsFirstUpdate.get(0))
+                .isSameInstanceAs(osuAccessPointsSecondUpdate.get(0));
         // Verify second update AP has the average of the first and second update RSSIs
         assertThat(osuAccessPointsSecondUpdate.get(0).getRssi())
                 .isEqualTo((prevRssi + newRssi) / 2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
index a83d7e0..b392c5e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
@@ -69,7 +69,7 @@
 
         assertWithMessage("Intent filter should contain expected intents")
                 .that(ipAddressPreferenceController.getConnectivityIntents())
-                .asList().containsAllIn(expectedIntents);
+                .asList().containsAtLeastElementsIn(expectedIntents);
     }
 
     private static class ConcreteIpAddressPreferenceController extends
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
index 40b9b13..3705267 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
@@ -90,7 +90,7 @@
 
         assertWithMessage("Intent filter should contain expected intents")
                 .that(mController.getConnectivityIntents())
-                .asList().containsAllIn(expectedIntents);
+                .asList().containsAtLeastElementsIn(expectedIntents);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 1769053..906e06e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -284,7 +284,7 @@
 
         assertThat(outTiles).hasSize(1);
         final Bundle newMetaData = outTiles.get(0).getMetaData();
-        assertThat(newMetaData).isNotSameAs(oldMetadata);
+        assertThat(newMetaData).isNotSameInstanceAs(oldMetadata);
     }
 
     @Test
diff --git a/packages/SoundPicker/res/values-uz/strings.xml b/packages/SoundPicker/res/values-uz/strings.xml
index 9018e66..a617733 100644
--- a/packages/SoundPicker/res/values-uz/strings.xml
+++ b/packages/SoundPicker/res/values-uz/strings.xml
@@ -21,7 +21,7 @@
     <string name="alarm_sound_default" msgid="4787646764557462649">"Standart signal tovushi"</string>
     <string name="add_ringtone_text" msgid="6642389991738337529">"Rington qo‘shish"</string>
     <string name="add_alarm_text" msgid="3545497316166999225">"Signal qo‘shish"</string>
-    <string name="add_notification_text" msgid="4431129543300614788">"Bildirishnoma qo‘shish"</string>
+    <string name="add_notification_text" msgid="4431129543300614788">"Bildirishnoma kiritish"</string>
     <string name="delete_ringtone_text" msgid="201443984070732499">"O‘chirish"</string>
     <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Maxsus rington qo‘shib bo‘lmadi"</string>
     <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Maxsus ringtonni o‘chirib bo‘lmadi"</string>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 31faedf..b50be52 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Skuif af"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Beweeg links"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Beweeg regs"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Vergrotingwisselaar"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Vergroot die hele skerm"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Vergroot \'n deel van die skerm"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Wissel"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Toestelkontroles"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Voeg kontroles vir jou gekoppelde toestelle by"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Stel toestelkontroles op"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 41f6bed..9e8c8ac 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"ወደ ታች ውሰድ"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ወደ ግራ ውሰድ"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"ወደ ቀኝ ውሰድ"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"የማጉላት ማብሪያ/ማጥፊያ"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ሙሉውን ማያ ገጽ አጉላ"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"የማያ ገጹን ክፍል አጉላ"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ማብሪያ/ማጥፊያ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"የመሣሪያ መቆጣጠሪያዎች"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"ለእርስዎ የተገናኙ መሣሪያዎች መቆጣጠሪያዎችን ያክሉ"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"የመሣሪያ መቆጣጠሪያዎችን ያቀናብሩ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index d1f98f0..caae9fb 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -1038,14 +1038,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"نقل للأسفل"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"نقل لليسار"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"نقل لليمين"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"مفتاح تبديل وضع التكبير"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"تكبير الشاشة بالكامل"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"تكبير جزء من الشاشة"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"تبديل"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"أدوات التحكم بالأجهزة"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"إضافة عناصر تحكّم لأجهزتك المتصلة"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"إعداد أدوات التحكم بالجهاز"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 52b6c5a..f5f109e 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"তললৈ নিয়ক"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"বাওঁফাললৈ নিয়ক"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"সোঁফাললৈ নিয়ক"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"বিবৰ্ধনৰ ছুইচ"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"গোটেই স্ক্ৰীনখন বিবৰ্ধন কৰক"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্ৰীনৰ কিছু অংশ বিবৰ্ধন কৰক"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ছুইচ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইচৰ নিয়ন্ত্ৰণসমূহ"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"আপোনাৰ সংযোজিত ডিভাইচসমূহৰ বাবে নিয়ন্ত্ৰণসমূহ যোগ কৰক"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"ডিভাইচৰ নিয়ন্ত্ৰণসমূহ ছেট আপ কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index b446718..89149f0 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Aşağı köçürün"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sola köçürün"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sağa köçürün"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Böyütmə dəyişdiricisi"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Bütün ekranı böyüdün"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekranın bir hissəsini böyüdün"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Dəyişdirici"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Cihaz idarəetmələri"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Qoşulmuş cihazlarınız üçün nizamlayıcılar əlavə edin"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Cihaz idarəetmələrini ayarlayın"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 3cc10d8..b3b722c9 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1023,14 +1023,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pomerite nadole"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pomerite nalevo"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pomerite nadesno"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prelazak na drugi režim uvećanja"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Uvećajte ceo ekran"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećajte deo ekrana"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Pređi"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodajte kontrole za povezane uređaje"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Podesite kontrole uređaja"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 07e7f8a..f68b69c 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Перамясціць ніжэй"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Перамясціць улева"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Перамясціць управа"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Пераключальнік павелічэння"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Павялічыць на ўвесь экран"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Павялічыць частку экрана"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Пераключальнік"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Элементы кіравання прыладай"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Дадайце элементы кіравання для падключаных прылад"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Наладзіць элементы кіравання прыладай"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index f33c5cf..7a4b957 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Преместване надолу"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Преместване наляво"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Преместване надясно"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Превключване на увеличението"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Увеличаване на целия екран"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличаване на част от екрана"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Превключване"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Контроли за устройството"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Добавяне на контроли за свързаните ви устройства"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Настройване на контролите за устройството"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index ac68620..1d67443 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1023,14 +1023,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pomjeranje prema dolje"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pomjeranje lijevo"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pomjeranje desno"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prekidač za uvećavanje"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Uvećavanje cijelog ekrana"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećavanje dijela ekrana"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prekidač"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodajte kontrole za povezane uređaje"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Postavite kontrole uređaja"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 5e64aee..5e06f55 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mou cap avall"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mou cap a l\'esquerra"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mou cap a la dreta"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Canvia al mode d\'ampliació"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Amplia tota la pantalla"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplia una part de la pantalla"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Canvia"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controls de dispositius"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Afegeix controls per als teus dispositius connectats"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Configura els controls de dispositius"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 5ca5df6..1b03762 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Přesunout dolů"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Přesunout doleva"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Přesunout doprava"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Přepínač zvětšení"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Zvětšit celou obrazovku"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zvětšit část obrazovky"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Přepnout"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Ovládání zařízení"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Přidejte ovládací prvky pro připojená zařízení"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Nastavení ovládání zařízení"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 419740e..fce888e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Flyt ned"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Flyt til venstre"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Flyt til højre"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Skift forstørrelsestilstand"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Forstør hele skærmen"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstør en del af skærmen"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Skift"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Enhedsstyring"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Tilføj styring af dine tilsluttede enheder"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurer enhedsstyring"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 4b1be3b..a6d2dfd 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Nach unten bewegen"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Nach links bewegen"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Nach rechts bewegen"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Vergrößerungsschalter"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ganzen Bildschirm vergrößern"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Teil des Bildschirms vergrößern"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Schalter"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Gerätesteuerung"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Steuerelemente für verbundene Geräte hinzufügen"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Gerätesteuerung einrichten"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 51c6d0d..2003a7d 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover hacia abajo"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover hacia la izquierda"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover hacia la derecha"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Botón para cambiar el modo de ampliación"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ampliar toda la pantalla"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Cambiar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Añade controles para tus dispositivos conectados"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurar control de dispositivos"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 6d616b8..286a42b 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Teisalda alla"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Teisalda vasakule"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Teisalda paremale"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Suurenduse lüliti"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Kogu ekraanikuva suurendamine"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekraanikuva osa suurendamine"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Lüliti"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Seadmete juhikud"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Lisage juhtelemendid ühendatud seadmete jaoks"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Seadmete juhtimisvidinate seadistamine"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 05e52f4..b6beec0 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Eraman behera"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Eraman ezkerrera"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Eraman eskuinera"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Lupa aplikatzeko botoia"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Handitu pantaila osoa"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Handitu pantailaren zati bat"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Botoia"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Gailuak kontrolatzeko widgetak"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Gehitu konektatutako gailuak kontrolatzeko widgetak"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfiguratu gailuak kontrolatzeko widgetak"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 1c16b33..7849ea4 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"انتقال به پایین"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"انتقال به راست"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"انتقال به چپ"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"کلید درشت‌نمایی"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"درشت‌نمایی تمام صفحه"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"درشت‌نمایی بخشی از صفحه"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"کلید"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"کنترل‌های دستگاه"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"افزودن کنترل‌ها برای دستگاه‌های متصل"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"تنظیم کنترل‌های دستگاه"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 1e900fb..3e5afc7 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Siirrä alas"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Siirrä vasemmalle"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Siirrä oikealle"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Suurennusvalinta"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Suurenna koko näyttö"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Suurenna osa näytöstä"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Vaihda"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Laitteiden hallinta"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Lisää ohjaimia yhdistettyjä laitteita varten"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Laitteiden hallinnan käyttöönotto"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index f390693..250183e 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Déplacer vers le bas"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Déplacer vers la gauche"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Déplacer vers la droite"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Commutateur d\'agrandissement"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Agrandir l\'écran entier"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Commutateur"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Ajoutez des commandes pour vos appareils connectés"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurer les commandes des appareils"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index df542bc..2a4c95a 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Déplacer vers le bas"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Déplacer vers la gauche"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Déplacer vers la droite"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Changer de mode d\'agrandissement"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Agrandir tout l\'écran"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Changer"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Ajouter des commandes pour vos appareils connectés"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurer les commandes des appareils"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 08149fb..181ae74 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1020,14 +1020,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"नीचे ले जाएं"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"बाईं ओर ले जाएं"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"दाईं ओर ले जाएं"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ज़ूम करने की सुविधा वाला स्विच"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"पूरी स्क्रीन को ज़ूम करें"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीन के किसी हिस्से को ज़ूम करें"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"स्विच"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"डिवाइस कंट्रोल"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"कनेक्ट किए गए डिवाइस के लिए कंट्रोल जोड़ें"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"डिवाइस कंट्रोल सेट अप करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index b01f58e..f8318d5 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1023,14 +1023,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Premjesti dolje"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Premjesti ulijevo"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Premjesti udesno"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prebacivanje povećavanja"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Povećaj cijeli zaslon"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povećaj dio zaslona"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prebaci"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodavanje kontrola za povezane uređaje"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Postavljanje kontrola uređaja"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 1fcabcf..690508a 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mozgatás lefelé"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mozgatás balra"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mozgatás jobbra"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Nagyításváltó"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Teljes képernyő nagyítása"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Képernyő bizonyos részének nagyítása"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Váltás"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Eszközvezérlők"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Vezérlők hozzáadása a csatlakoztatott eszközökhöz"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Eszközvezérlők beállítása"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 63737d2..0a966a1 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Տեղափոխել ներքև"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Տեղափոխել ձախ"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Տեղափոխել աջ"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Խոշորացման փոփոխություն"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Խոշորացնել ամբողջ էկրանը"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Խոշորացնել էկրանի որոշակի հատվածը"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Փոխել"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Սարքերի կառավարման տարրեր"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Ավելացրեք կառավարման տարրեր ձեր միացված սարքերի համար"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Սարքերի կառավարման տարրերի կարգավորում"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index da9af02..750a38f 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pindahkan ke bawah"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pindahkan ke kiri"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pindahkan ke kanan"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Tombol pembesaran"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Perbesar seluruh layar"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Perbesar sebagian layar"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Alihkan"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrol perangkat"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Tambahkan kontrol untuk perangkat terhubung"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Siapkan kontrol perangkat"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 64ad21f..944e13f 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Færa niður"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Færa til vinstri"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Færa til hægri"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Stækkunarrofi"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Stækka allan skjáinn"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Stækka hluta skjásins"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Rofi"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Tækjastjórnun"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Bæta við stýringum fyrir tengd tæki"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Setja upp tækjastjórnun"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 90f68d9..68c64f0 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Sposta giù"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sposta a sinistra"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sposta a destra"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Opzione Ingrandimento"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ingrandisci l\'intero schermo"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ingrandisci parte dello schermo"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Opzione"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controllo dei dispositivi"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Aggiungi controlli per i dispositivi connessi"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Configura il controllo dei dispositivi"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 14b19c3..e1ad35d 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"הזזה למטה"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"הזזה שמאלה"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"הזזה ימינה"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"מעבר למצב הגדלה"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"הגדלת כל המסך"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"הגדלת חלק מהמסך"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"מעבר"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"פקדי מכשירים"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"יש להוסיף פקדים למכשירים המחוברים"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"הגדרה של פקדי מכשירים"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 98e6984..763a80e 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"下に移動"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"左に移動"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"右に移動"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"拡大スイッチ"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"画面全体を拡大します"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"画面の一部を拡大します"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"スイッチ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"デバイス コントロール"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"接続済みデバイスのコントロールを追加します"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"デバイス コントロールの設定"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 606350c..f1c0121 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Төмен қарай жылжыту"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Солға жылжыту"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Оңға жылжыту"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Ұлғайту режиміне ауыстырғыш"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Толық экранды ұлғайту"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экранның бөлігін ұлғайту"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Ауысу"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Құрылғыны басқару элементтері"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Жалғанған құрылғылар үшін басқару виджеттерін қосу"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Құрылғыны басқару элементтерін реттеу"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9ed01c5..92d7cbf 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"ផ្លាស់ទី​ចុះ​ក្រោម"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ផ្លាស់ទី​ទៅ​ឆ្វេង"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"ផ្លាស់ទីទៅ​ស្តាំ"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ប៊ូតុងបិទបើកការ​ពង្រីក"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ពង្រីក​អេក្រង់​ទាំងមូល"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ពង្រីក​ផ្នែកនៃ​អេក្រង់"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ប៊ូតុងបិទបើក"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ផ្ទាំងគ្រប់គ្រងឧបករណ៍"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"បញ្ចូល​ផ្ទាំងគ្រប់គ្រង​សម្រាប់​ឧបករណ៍​ដែលអ្នកបានភ្ជាប់"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"រៀបចំ​ផ្ទាំងគ្រប់គ្រងឧបករណ៍"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 116241c..2e47e9e 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"ಕೆಳಗೆ ಸರಿಸಿ"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ಎಡಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"ಬಲಕ್ಕೆ ಸರಿಸಿ"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ಝೂಮ್ ಮಾಡುವ ಸ್ವಿಚ್"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ಸ್ಕ್ರೀನ್‌ನ ಸಂಪೂರ್ಣ ಭಾಗವನ್ನು ಝೂಮ್ ಮಾಡಿ"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ಸ್ಕ್ರೀನ್‌ನ ಅರ್ಧಭಾಗವನ್ನು ಝೂಮ್ ಮಾಡಿ"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ಸ್ವಿಚ್"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ಸಾಧನ ನಿಯಂತ್ರಣಗಳು"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"ನಿಮ್ಮ ಸಂಪರ್ಕಿತ ಸಾಧನಗಳಿಗೆ ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಿ"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"ಸಾಧನ ನಿಯಂತ್ರಣಗಳನ್ನು ಸೆಟಪ್ ಮಾಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 5bb0d13..fc533be 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"아래로 이동"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"왼쪽으로 이동"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"오른쪽으로 이동"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"확대 전환"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"전체 화면 확대"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"화면 일부 확대"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"전환"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"기기 컨트롤"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"연결된 기기의 컨트롤을 추가하세요."</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"기기 컨트롤 설정"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index dd85504..a4e47b3 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Төмөн жылдыруу"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Солго жылдыруу"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Оңго жылдыруу"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Чоңойтуу режимине которулуу"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Толук экранды чоңойтуу"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экрандын бир бөлүгүн чоңойтуу"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Которулуу"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Түзмөктү башкаруу элементтери"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Байланышкан түзмөктөрүңүздү башкаруу элементтерин кошосуз"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Түзмөктү башкаруу элементтерин жөндөө"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 3553704..78fdf09 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"ຍ້າຍລົງ"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ຍ້າຍໄປຊ້າຍ"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"ຍ້າຍໄປຂວາ"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ສະຫຼັບການຂະຫຍາຍ"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ຂະຫຍາຍທັງໜ້າຈໍ"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ຂະຫຍາຍບາງສ່ວນຂອງໜ້າຈໍ"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ສະຫຼັບ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ການຄວບຄຸມອຸປະກອນ"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"ເພີ່ມການຄວບຄຸມສຳລັບອຸປະກອນທີ່ເຊື່ອມຕໍ່ແລ້ວຂອງທ່ານ"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"ຕັ້ງຄ່າການຄວບຄຸມອຸປະກອນ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 9a2a8f2..6db8969 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Perkelti žemyn"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Perkelti kairėn"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Perkelti dešinėn"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Didinimo jungiklis"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Didinti visą ekraną"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Didinti ekrano dalį"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Perjungti"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Įrenginio valdikliai"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Pridėkite prijungtų įrenginių valdiklių"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Įrenginio valdiklių nustatymas"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 94b66f3..c7807de 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1023,14 +1023,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pārvietot uz leju"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pārvietot pa kreisi"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pārvietot pa labi"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Palielinājuma slēdzis"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Palielināt visu ekrānu"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Palielināt ekrāna daļu"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Pārslēgt"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Ierīču vadīklas"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Pievienojiet vadīklas pievienotajām ierīcēm"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Ierīču vadīklu iestatīšana"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 567afc0..203f6c2 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Премести надолу"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Премести налево"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Премести надесно"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Прекинувач за зголемување"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Зголеми цел екран"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Зголеми дел од екранот"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Префрли"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Контроли за уредите"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Додајте контроли за поврзаните уреди"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Поставете ги контролите за уредите"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index dc6fe4c..1076eac 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"താഴേക്ക് നീക്കുക"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ഇടത്തേക്ക് നീക്കുക"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"വലത്തേക്ക് നീക്കുക"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"മാഗ്നിഫിക്കേഷൻ മോഡ് മാറുക"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"മുഴുവൻ സ്‌ക്രീനും മാഗ്നിഫൈ ചെയ്യുക"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"സ്‌ക്രീനിന്റെ ഭാഗം മാഗ്നിഫൈ ചെയ്യുക"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"മാറുക"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ഉപകരണ നിയന്ത്രണങ്ങൾ"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"കണക്റ്റ് ചെയ്ത ഉപകരണങ്ങൾക്ക് നിയന്ത്രണങ്ങൾ ചേർക്കുക"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"ഉപകരണ നിയന്ത്രണങ്ങൾ സജ്ജീകരിക്കുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index b12c4e0..c1ec99e 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -549,29 +549,29 @@
     <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Таны байгууллага таны ажлын профайлд сертификатын зөвшөөрөл суулгасан байна. Таны аюулгүй сүлжээний ачааллыг өөрчлөх эсвэл хянах боломжтой."</string>
     <string name="monitoring_description_ca_certificate" msgid="448923057059097497">"Сертификатын зөвшөөрлийг энэ төхөөрөмжид суулгасан байна. Таны аюулгүй сүлжээний ачааллыг өөрчлөх эсвэл хянах боломжтой."</string>
     <string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Таны админ төхөөрөмжийн ачааллыг хянадаг сүлжээний логийг асаасан байна."</string>
-    <string name="monitoring_description_named_vpn" msgid="5749932930634037027">"Та имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
-    <string name="monitoring_description_two_named_vpns" msgid="3516830755681229463">"Та имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP_0">%1$s</xliff:g>, <xliff:g id="VPN_APP_1">%2$s</xliff:g>-д холбогдсон байна."</string>
-    <string name="monitoring_description_managed_profile_named_vpn" msgid="368812367182387320">"Таны ажлын профайл <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна. Энэ нь таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
-    <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"Таны хувийн профайлыг имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбосон байна."</string>
+    <string name="monitoring_description_named_vpn" msgid="5749932930634037027">"Та имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
+    <string name="monitoring_description_two_named_vpns" msgid="3516830755681229463">"Та имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP_0">%1$s</xliff:g>, <xliff:g id="VPN_APP_1">%2$s</xliff:g>-д холбогдсон байна."</string>
+    <string name="monitoring_description_managed_profile_named_vpn" msgid="368812367182387320">"Таны ажлын профайл <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна. Энэ нь таны имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
+    <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"Таны хувийн профайлыг имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбосон байна."</string>
     <string name="monitoring_description_do_header_generic" msgid="6130190408164834986">"Таны төхөөрөмжийг <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> удирддаг."</string>
     <string name="monitoring_description_do_header_with_name" msgid="2696255132542779511">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> таны төхөөрөмжийг удирдахын тулд <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>-г ашигладаг."</string>
     <string name="monitoring_description_do_body" msgid="7700878065625769970">"Таны админ тохиргоо, байгууллагын хандалт, апп, төхөөрөмжтэй холбоотой өгөгдөл болон таны төхөөрөмжийн байршлын мэдээллийг хянах, удирдах боломжтой."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="1467280496376492558">" "</string>
     <string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Дэлгэрэнгүй үзэх"</string>
-    <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"Таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
+    <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"Таны имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"VPN тохиргоог нээх"</string>
     <string name="monitoring_description_ca_cert_settings_separator" msgid="7107390013344435439">" "</string>
     <string name="monitoring_description_ca_cert_settings" msgid="8329781950135541003">"Итгэмжлэгдсэн мандат үнэмлэхийг нээх"</string>
     <string name="monitoring_description_network_logging" msgid="577305979174002252">"Таны админ төхөөрөмжийн ачааллыг хянадаг сүлжээний логийг асаасан байна.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу."</string>
-    <string name="monitoring_description_vpn" msgid="1685428000684586870">"Та апп-д VPN холболт хийхийг зөвшөөрсөн байна.\n\nЭнэхүү апп нь таны имэйл, апп, вэбсайт зэрэг төхөөрөмж болон сүлжээний үйл ажиллагааг хянах боломжтой."</string>
-    <string name="monitoring_description_vpn_profile_owned" msgid="4964237035412372751">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> таны ажлын профайлыг удирддаг.\n\nТаны админ имэйл, апп болон вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу.\n\nТа сүлжээний үйл ажиллагааг хянах боломжтой VPN-д холбогдсон байна."</string>
+    <string name="monitoring_description_vpn" msgid="1685428000684586870">"Та апп-д VPN холболт хийхийг зөвшөөрсөн байна.\n\nЭнэхүү апп нь таны имэйл, апп, вебсайт зэрэг төхөөрөмж болон сүлжээний үйл ажиллагааг хянах боломжтой."</string>
+    <string name="monitoring_description_vpn_profile_owned" msgid="4964237035412372751">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> таны ажлын профайлыг удирддаг.\n\nТаны админ имэйл, апп болон веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу.\n\nТа сүлжээний үйл ажиллагааг хянах боломжтой VPN-д холбогдсон байна."</string>
     <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
-    <string name="monitoring_description_app" msgid="376868879287922929">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вэбсайт зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
-    <string name="monitoring_description_app_personal" msgid="1970094872688265987">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вэбсайт зэрэг сүлжээний хувийн үйл ажиллагааг хянах боломжтой."</string>
-    <string name="branded_monitoring_description_app_personal" msgid="1703511985892688885">"Та имэйл, апп, вэб хуудас зэрэг хувийн сүлжээнийхээ үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон байна."</string>
-    <string name="monitoring_description_app_work" msgid="3713084153786663662">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, вэб хуудас зэрэг ажлын сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%2$s</xliff:g>-д холбогдсон. \n\nДэлгэрэнгүй мэдээллийг авахын тулд админтай холбогдоно уу."</string>
-    <string name="monitoring_description_app_personal_work" msgid="6175816356939166101">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-тай холбогдсон. \n\nМөн таны сүлжээний хувийн үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-д холбогдсон байна."</string>
+    <string name="monitoring_description_app" msgid="376868879287922929">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вебсайт зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
+    <string name="monitoring_description_app_personal" msgid="1970094872688265987">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вебсайт зэрэг сүлжээний хувийн үйл ажиллагааг хянах боломжтой."</string>
+    <string name="branded_monitoring_description_app_personal" msgid="1703511985892688885">"Та имэйл, апп, веб хуудас зэрэг хувийн сүлжээнийхээ үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон байна."</string>
+    <string name="monitoring_description_app_work" msgid="3713084153786663662">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, веб хуудас зэрэг ажлын сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%2$s</xliff:g>-д холбогдсон. \n\nДэлгэрэнгүй мэдээллийг авахын тулд админтай холбогдоно уу."</string>
+    <string name="monitoring_description_app_personal_work" msgid="6175816356939166101">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-тай холбогдсон. \n\nМөн таны сүлжээний хувийн үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-д холбогдсон байна."</string>
     <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"TrustAgent-р түгжээгүй байлгасан"</string>
     <string name="keyguard_indication_trust_disabled" msgid="6820793704816727918">"Таныг гараар нээх хүртэл төхөөрөмж түгжээтэй байх болно"</string>
     <string name="keyguard_indication_trust_unlocked_plugged_in" msgid="2323452175329362855">"<xliff:g id="KEYGUARD_INDICATION">%1$s</xliff:g>\n<xliff:g id="POWER_INDICATION">%2$s</xliff:g>"</string>
@@ -653,8 +653,8 @@
     <string name="status_bar_alarm" msgid="87160847643623352">"Сэрүүлэг"</string>
     <string name="status_bar_work" msgid="5238641949837091056">"Ажлын профайл"</string>
     <string name="status_bar_airplane" msgid="4848702508684541009">"Нислэгийн горим"</string>
-    <string name="add_tile" msgid="6239678623873086686">"Вэбсайтын цонх нэмэх"</string>
-    <string name="broadcast_tile" msgid="5224010633596487481">"Вэбсайтын цонх дамжуулах"</string>
+    <string name="add_tile" msgid="6239678623873086686">"Вебсайтын цонх нэмэх"</string>
+    <string name="broadcast_tile" msgid="5224010633596487481">"Вебсайтын цонх дамжуулах"</string>
     <string name="zen_alarm_warning_indef" msgid="5252866591716504287">"Та өмнө нь унтраагаагүй тохиолдолд <xliff:g id="WHEN">%1$s</xliff:g>-т сэрүүлгээ сонсохгүй"</string>
     <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-т та дараагийн сэрүүлгээ сонсохгүй"</string>
     <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> цагт"</string>
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Доош зөөх"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Зүүн тийш зөөх"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Баруун тийш зөөх"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Томруулах сэлгэлт"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Дэлгэцийг бүхэлд нь томруулах"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Дэлгэцийн нэг хэсгийг томруулах"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Сэлгэх"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Төхөөрөмжийн хяналт"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Холбогдсон төхөөрөмжүүд дээрээ хяналт нэмэх"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Төхөөрөмжийн хяналтыг тохируулах"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index aa302a9..ac22f4c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Alih ke bawah"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Alih ke kiri"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Alih ke kanan"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Suis pembesaran"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Besarkan seluruh skrin"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Besarkan sebahagian skrin"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Tukar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kawalan peranti"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Tambah kawalan untuk peranti yang disambungkan"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Sediakan kawalan peranti"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index a296b46..c92900c 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"အောက်သို့ရွှေ့ရန်"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ဘယ်ဘက်သို့ရွှေ့ရန်"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"ညာဘက်သို့ရွှေ့ရန်"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ချဲ့ရန် ခလုတ်"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ဖန်သားပြင် တစ်ခုလုံးကို ချဲ့ပါ"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ဖန်သားပြင် တစ်စိတ်တစ်ပိုင်းကို ချဲ့ပါ"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ခလုတ်"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"စက်ထိန်းစနစ်"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"ချိတ်ဆက်စက်များအတွက် ထိန်းချုပ်မှုများထည့်ပါ"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"စက်ထိန်းစနစ် ထည့်သွင်းခြင်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 7ebed82..2920af3 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Flytt ned"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Flytt til venstre"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Flytt til høyre"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Forstørringsbryter"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Forstørr hele skjermen"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstørr en del av skjermen"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Bytt"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyring"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Legg til kontroller for de tilkoblede enhetene dine"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurer enhetsstyring"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 19e1537..b90cd30 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Omlaag verplaatsen"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Naar links verplaatsen"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Naar rechts verplaatsen"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Vergrotingsschakelaar"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Hele scherm vergroten"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Deel van het scherm vergroten"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Schakelen"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Apparaatbediening"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Bedieningselementen voor je gekoppelde apparaten toevoegen"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Apparaatbediening instellen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index d4e723f..fd7692a 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"ତଳକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ସ୍ୱିଚ୍"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍ ମାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ସ୍କ୍ରିନର ଅଂଶ ମାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ସ୍ୱିଚ୍ କରନ୍ତୁ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ଡିଭାଇସ୍ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"ଆପଣଙ୍କ ସଂଯୁକ୍ତ ଡିଭାଇସଗୁଡ଼ିକ ପାଇଁ ନିୟନ୍ତ୍ରଣ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"ଡିଭାଇସ୍ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ସେଟ୍ ଅପ୍ କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 810ea37..19cd0e9 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Przesuń w dół"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Przesuń w lewo"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Przesuń w prawo"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Przełączanie powiększenia"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Powiększ cały ekran"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Powiększ część ekranu"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Przełącz"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Sterowanie urządzeniami"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodaj elementy sterujące połączonymi urządzeniami"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurowanie sterowania urządzeniami"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 966f15c..0b1c5e3 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover para baixo"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover para a esquerda"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover para a direita"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Interruptor de ampliação"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ampliar o ecrã inteiro"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte do ecrã"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Mudar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controlos de dispositivos"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Adicione controlos para os dispositivos associados."</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Configure os controlos de dispositivos"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 07653f1..3502161 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1023,14 +1023,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Deplasați în jos"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Deplasați spre stânga"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Deplasați spre dreapta"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Comutator de mărire"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Măriți întregul ecran"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Măriți o parte a ecranului"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Comutator"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Comenzile dispozitivelor"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Adăugați comenzi pentru dispozitivele conectate"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurați comenzile dispozitivelor"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 5f9f115..7f257a1 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Переместить вниз"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Переместить влево"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Переместить вправо"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Переключатель режима увеличения"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Увеличить весь экран"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличить часть экрана"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Переключить"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Управление устройствами"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Добавьте виджеты для управления устройствами."</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Настройте виджеты управления устройствами"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 64e24be..ac743d3 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"පහළට ගෙන යන්න"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"වමට ගෙන යන්න"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"දකුණට ගෙන යන්න"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"විශාලන ස්විචය"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"සම්පූර්ණ තිරය විශාලනය කරන්න"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"තිරයේ කොටසක් විශාලනය කරන්න"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ස්විචය"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"උපාංග පාලන"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"ඔබේ සම්බන්ධිත උපාංග සඳහා පාලන එක් කරන්න"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"උපාංග පාලන පිහිටුවන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index bb75f8f..d29d1e9 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Posunúť nadol"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Posunúť doľava"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Posunúť doprava"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prepínač zväčenia"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Zväčšiť celú obrazovku"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zväčšiť časť obrazovky"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prepnúť"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Ovládanie zariadení"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Pridajte si ovládače pripojených zariadení"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Nastavenie ovládania zariadení"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 98abf37..6cb0ccd 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Premakni navzdol"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Premakni levo"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Premakni desno"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Stikalo za povečavo"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Povečava celotnega zaslona"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povečava dela zaslona"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Stikalo"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrolniki naprave"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodajte kontrolnike za povezane naprave"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Nastavitev kontrolnikov naprave"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index e59c0bb..687a1bd 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1023,14 +1023,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Померите надоле"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Померите налево"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Померите надесно"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Прелазак на други режим увећања"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Увећајте цео екран"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увећајте део екрана"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Пређи"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Контроле уређаја"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Додајте контроле за повезане уређаје"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Подесите контроле уређаја"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 8213118..3dc89d8 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Flytta nedåt"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Flytta åt vänster"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Flytta åt höger"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Förstoringsreglage"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Förstora hela skärmen"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Förstora en del av skärmen"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Reglage"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyrning"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Lägg till snabbkontroller för anslutna enheter"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurera enhetsstyrning"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 0a0e91a..76c1cd4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Sogeza chini"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sogeza kushoto"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sogeza kulia"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Swichi ya ukuzaji"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Kuza skrini yote"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Kuza sehemu ya skrini"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Swichi"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Vidhibiti vya vifaa"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Weka vidhibiti vya vifaa ulivyounganisha"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Weka mipangilio ya vidhibiti vya vifaa"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 27a9d06..7e4ed35 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"கீழே நகர்த்து"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"இடப்புறம் நகர்த்து"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"வலப்புறம் நகர்த்து"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"பெரிதாக்கல் ஸ்விட்ச்"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"முழுத்திரையைப் பெரிதாக்கும்"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"திரையின் ஒரு பகுதியைப் பெரிதாக்கும்"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ஸ்விட்ச்"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"சாதனக் கட்டுப்பாடுகள்"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"இணைக்கப்பட்ட சாதனங்களில் கட்டுப்பாடுகளைச் சேர்க்கலாம்"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"சாதனக் கட்டுப்பாடுகளை அமைத்தல்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 5e35a5c..f26c8b4 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"కిందకి పంపండి"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ఎడమవైపుగా జరపండి"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"కుడివైపుగా జరపండి"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"మాగ్నిఫికేషన్ స్విచ్"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"స్క్రీన్ మొత్తాన్ని మాగ్నిఫై చేయండి"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"స్క్రీన్‌లో భాగాన్ని మాగ్నిఫై చేయండి"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"స్విచ్ చేయి"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"పరికరం నియంత్రణలు"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"మీ కనెక్ట్ అయిన పరికరాలకు నియంత్రణలను జోడించండి"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"పరికరం నియంత్రణలను సెటప్ చేయడం"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 3f7e0da..9c61e5e 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"ย้ายลง"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"ย้ายไปทางซ้าย"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"ย้ายไปทางขวา"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"เปลี่ยนการขยาย"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ขยายทั้งหน้าจอ"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ขยายบางส่วนของหน้าจอ"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"เปลี่ยน"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ระบบควบคุมอุปกรณ์"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"เพิ่มตัวควบคุมของอุปกรณ์ที่เชื่อมต่อ"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"ตั้งค่าระบบควบคุมอุปกรณ์"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index d03f0f8..be7bd06 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Ibaba"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Ilipat pakaliwa"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Ilipat pakanan"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Switch ng pag-magnify"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"I-magnify ang buong screen"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"I-magnify ang isang bahagi ng screen"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Mga kontrol ng device"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Magdagdag ng kontrol para sa mga nakakonektang device"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"I-set up ang mga kontrol ng device"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 62f3671..5d488365 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Aşağı taşı"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sola taşı"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sağa taşı"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Büyütme moduna geçin"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ekranın tamamını büyütün"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekranın bir parçasını büyütün"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Geç"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Cihaz denetimleri"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Bağlı cihazlarınız için denetimler ekleyin"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Cihaz denetimlerini kur"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 069577f..3b5c44b 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1028,14 +1028,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Перемістити вниз"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Перемістити ліворуч"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Перемістити праворуч"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Перемикач режиму збільшення"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Збільшити весь екран"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Збільшити частину екрана"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Перемкнути"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Керування пристроями"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Додайте елементи керування для підключених пристроїв"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Налаштувати елементи керування пристроями"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 433eadb..228b80d 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pastga siljitish"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Chapga siljitish"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Oʻngga siljitish"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Kattalashtirishni almashtirish"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Butun ekranni kattalashtirish"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran qismini kattalashtirish"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Almashtirish"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Qurilmalarni boshqarish"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Ulangan qurilmalar uchun boshqaruv elementlari"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Qurilma boshqaruv elementlarini sozlash"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index e337c42..8492236 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Di chuyển xuống"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Di chuyển sang trái"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Di chuyển sang phải"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Nút chuyển phóng to"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Phóng to toàn bộ màn hình"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Phóng to một phần màn hình"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Chuyển"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Điều khiển thiết bị"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Thêm các tùy chọn điều khiển cho các thiết bị đã kết nối của bạn"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Thiết lập các tùy chọn điều khiển thiết bị"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 254d974..72e7640 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"下移"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"左移"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"右移"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"切换放大模式"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"放大整个屏幕"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分屏幕"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切换"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"设备控制器"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"为您所连接的设备添加控件"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"设置设备控件"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index e8afc0e..c3dc8d2 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"向下移"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"向左移"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"向右移"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"放大開關"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"放大整個螢幕畫面"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分螢幕畫面"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切換"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"為連接的裝置新增控制選項"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"設定裝置控制"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 0fc7db6..02fc24f 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"向下移"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"向左移"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"向右移"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"切換放大模式"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"放大整個螢幕畫面"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大局部螢幕畫面"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切換"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"新增已連結裝置的控制項"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"設定裝置控制"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 3ada5cc..8eb86bc 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1018,14 +1018,10 @@
     <string name="accessibility_control_move_down" msgid="5390922476900974512">"Yehlisa"</string>
     <string name="accessibility_control_move_left" msgid="8156206978511401995">"Yisa kwesokunxele"</string>
     <string name="accessibility_control_move_right" msgid="8926821093629582888">"Yisa kwesokudla"</string>
-    <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
-    <skip />
-    <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
-    <skip />
+    <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Iswishi yokukhulisa"</string>
+    <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Khulisa sonke isikrini"</string>
+    <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Khulisa ingxenye eyesikrini"</string>
+    <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Iswishi"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Izilawuli zezinsiza"</string>
     <string name="quick_controls_subtitle" msgid="1667408093326318053">"Engeza izilawuli zedivayisi yakho exhunyiwe"</string>
     <string name="quick_controls_setup_title" msgid="8901436655997849822">"Setha izilawuli zezinsiza"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7cbbaf9..26b3ab8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1217,7 +1217,7 @@
     <!-- Interior padding of the message bubble -->
     <dimen name="bubble_message_padding">4dp</dimen>
     <!-- Offset between bubbles in their stacked position. -->
-    <dimen name="bubble_stack_offset">5dp</dimen>
+    <dimen name="bubble_stack_offset">10dp</dimen>
     <!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
     <dimen name="bubble_stack_offscreen">9dp</dimen>
     <!-- How far down the screen the stack starts. -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index b526a92..b81ffb7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -46,7 +46,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -82,13 +81,11 @@
 
     private final PackageManager mPackageManager;
     private final BackgroundExecutor mBackgroundExecutor;
-    private final TaskStackChangeListeners mTaskStackChangeListeners;
 
     private ActivityManagerWrapper() {
         final Context context = AppGlobals.getInitialApplication();
         mPackageManager = context.getPackageManager();
         mBackgroundExecutor = BackgroundExecutor.get();
-        mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
     }
 
     public static ActivityManagerWrapper getInstance() {
@@ -360,23 +357,17 @@
     }
 
     /**
-     * Registers a task stack listener with the system.
-     * This should be called on the main thread.
+     * @deprecated use {@link TaskStackChangeListeners#registerTaskStackListener}
      */
     public void registerTaskStackListener(TaskStackChangeListener listener) {
-        synchronized (mTaskStackChangeListeners) {
-            mTaskStackChangeListeners.addListener(ActivityManager.getService(), listener);
-        }
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(listener);
     }
 
     /**
-     * Unregisters a task stack listener with the system.
-     * This should be called on the main thread.
+     * @deprecated use {@link TaskStackChangeListeners#unregisterTaskStackListener}
      */
     public void unregisterTaskStackListener(TaskStackChangeListener listener) {
-        synchronized (mTaskStackChangeListeners) {
-            mTaskStackChangeListeners.removeListener(listener);
-        }
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(listener);
     }
 
     /**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index f214648..765cd3d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -19,14 +19,12 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
 import android.app.TaskStackListener;
 import android.content.ComponentName;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Log;
 
@@ -42,208 +40,40 @@
 public class TaskStackChangeListeners extends TaskStackListener {
 
     private static final String TAG = TaskStackChangeListeners.class.getSimpleName();
+    private static final TaskStackChangeListeners INSTANCE = new TaskStackChangeListeners();
+
+    private final Impl mImpl;
+
+    private TaskStackChangeListeners() {
+        mImpl = new Impl(Looper.getMainLooper());
+    }
+
+    public static TaskStackChangeListeners getInstance() {
+        return INSTANCE;
+    }
 
     /**
-     * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
+     * Registers a task stack listener with the system.
+     * This should be called on the main thread.
      */
-    private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>();
-    private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
-
-    private final Handler mHandler;
-    private boolean mRegistered;
-
-    public TaskStackChangeListeners(Looper looper) {
-        mHandler = new H(looper);
-    }
-
-    public void addListener(IActivityManager am, TaskStackChangeListener listener) {
-        synchronized (mTaskStackListeners) {
-            mTaskStackListeners.add(listener);
-        }
-        if (!mRegistered) {
-            // Register mTaskStackListener to IActivityManager only once if needed.
-            try {
-                ActivityTaskManager.getService().registerTaskStackListener(this);
-                mRegistered = true;
-            } catch (Exception e) {
-                Log.w(TAG, "Failed to call registerTaskStackListener", e);
-            }
+    public void registerTaskStackListener(TaskStackChangeListener listener) {
+        synchronized (mImpl) {
+            mImpl.addListener(listener);
         }
     }
 
-    public void removeListener(TaskStackChangeListener listener) {
-        boolean isEmpty;
-        synchronized (mTaskStackListeners) {
-            mTaskStackListeners.remove(listener);
-            isEmpty = mTaskStackListeners.isEmpty();
-        }
-        if (isEmpty && mRegistered) {
-            // Unregister mTaskStackListener once we have no more listeners
-            try {
-                ActivityTaskManager.getService().unregisterTaskStackListener(this);
-                mRegistered = false;
-            } catch (Exception e) {
-                Log.w(TAG, "Failed to call unregisterTaskStackListener", e);
-            }
+    /**
+     * Unregisters a task stack listener with the system.
+     * This should be called on the main thread.
+     */
+    public void unregisterTaskStackListener(TaskStackChangeListener listener) {
+        synchronized (mImpl) {
+            mImpl.removeListener(listener);
         }
     }
 
-    @Override
-    public void onTaskStackChanged() throws RemoteException {
-        // Call the task changed callback for the non-ui thread listeners first. Copy to a set of
-        // temp listeners so that we don't lock on mTaskStackListeners while calling all the
-        // callbacks. This call is always on the same binder thread, so we can just synchronize
-        // on the copying of the listener list.
-        synchronized (mTaskStackListeners) {
-            mTmpListeners.addAll(mTaskStackListeners);
-        }
-        for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
-            mTmpListeners.get(i).onTaskStackChangedBackground();
-        }
-        mTmpListeners.clear();
+    private static class Impl extends TaskStackListener implements Handler.Callback {
 
-        mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
-        mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
-    }
-
-    @Override
-    public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
-            throws RemoteException {
-        mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
-        mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
-                new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
-    }
-
-    @Override
-    public void onActivityUnpinned() throws RemoteException {
-        mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
-        mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
-    }
-
-    @Override
-    public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
-            boolean clearedTask, boolean wasVisible) throws RemoteException {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg1 = task;
-        args.argi1 = homeTaskVisible ? 1 : 0;
-        args.argi2 = clearedTask ? 1 : 0;
-        args.argi3 = wasVisible ? 1 : 0;
-        mHandler.removeMessages(H.ON_ACTIVITY_RESTART_ATTEMPT);
-        mHandler.obtainMessage(H.ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
-    }
-
-    @Override
-    public void onActivityForcedResizable(String packageName, int taskId, int reason)
-            throws RemoteException {
-        mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
-                .sendToTarget();
-    }
-
-    @Override
-    public void onActivityDismissingDockedStack() throws RemoteException {
-        mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
-    }
-
-    @Override
-    public void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo,
-            int requestedDisplayId) throws RemoteException {
-        mHandler.obtainMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED, requestedDisplayId,
-                0 /* unused */,
-                taskInfo).sendToTarget();
-    }
-
-    @Override
-    public void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo,
-            int requestedDisplayId) throws RemoteException {
-        mHandler.obtainMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED,
-                requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget();
-    }
-
-    @Override
-    public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
-        mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
-    }
-
-    @Override
-    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException {
-        mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
-    }
-
-    @Override
-    public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
-        mHandler.obtainMessage(H.ON_TASK_CREATED, taskId, 0, componentName).sendToTarget();
-    }
-
-    @Override
-    public void onTaskRemoved(int taskId) throws RemoteException {
-        mHandler.obtainMessage(H.ON_TASK_REMOVED, taskId, 0).sendToTarget();
-    }
-
-    @Override
-    public void onTaskMovedToFront(RunningTaskInfo taskInfo)
-            throws RemoteException {
-        mHandler.obtainMessage(H.ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget();
-    }
-
-    @Override
-    public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) throws RemoteException {
-        mHandler.obtainMessage(H.ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget();
-    }
-
-    @Override
-    public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation)
-            throws RemoteException {
-        mHandler.obtainMessage(H.ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
-                requestedOrientation).sendToTarget();
-    }
-
-    @Override
-    public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken)
-            throws RemoteException {
-        mHandler.obtainMessage(H.ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId, 0 /* unused */,
-                activityToken).sendToTarget();
-    }
-
-    @Override
-    public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
-        mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_DRAWN, displayId,
-                0 /* unused */).sendToTarget();
-    }
-
-    @Override
-    public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
-        mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
-                0 /* unused */).sendToTarget();
-    }
-
-    @Override
-    public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
-        mHandler.obtainMessage(H.ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
-    }
-
-    @Override
-    public void onRecentTaskListUpdated() throws RemoteException {
-        mHandler.obtainMessage(H.ON_TASK_LIST_UPDATED).sendToTarget();
-    }
-
-    @Override
-    public void onRecentTaskListFrozenChanged(boolean frozen) {
-        mHandler.obtainMessage(H.ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */)
-                .sendToTarget();
-    }
-
-    @Override
-    public void onTaskDescriptionChanged(RunningTaskInfo taskInfo) {
-        mHandler.obtainMessage(H.ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget();
-    }
-
-    @Override
-    public void onActivityRotation(int displayId) {
-        mHandler.obtainMessage(H.ON_ACTIVITY_ROTATION, displayId, 0 /* unused */)
-                .sendToTarget();
-    }
-
-    private final class H extends Handler {
         private static final int ON_TASK_STACK_CHANGED = 1;
         private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
         private static final int ON_ACTIVITY_PINNED = 3;
@@ -268,13 +98,205 @@
         private static final int ON_TASK_DESCRIPTION_CHANGED = 24;
         private static final int ON_ACTIVITY_ROTATION = 25;
 
+        /**
+         * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
+         */
+        private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>();
+        private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
 
-        public H(Looper looper) {
-            super(looper);
+        private final Handler mHandler;
+        private boolean mRegistered;
+
+        Impl(Looper looper) {
+            mHandler = new Handler(looper, this);
+        }
+
+        public void addListener(TaskStackChangeListener listener) {
+            synchronized (mTaskStackListeners) {
+                mTaskStackListeners.add(listener);
+            }
+            if (!mRegistered) {
+                // Register mTaskStackListener to IActivityManager only once if needed.
+                try {
+                    ActivityTaskManager.getService().registerTaskStackListener(this);
+                    mRegistered = true;
+                } catch (Exception e) {
+                    Log.w(TAG, "Failed to call registerTaskStackListener", e);
+                }
+            }
+        }
+
+        public void removeListener(TaskStackChangeListener listener) {
+            boolean isEmpty;
+            synchronized (mTaskStackListeners) {
+                mTaskStackListeners.remove(listener);
+                isEmpty = mTaskStackListeners.isEmpty();
+            }
+            if (isEmpty && mRegistered) {
+                // Unregister mTaskStackListener once we have no more listeners
+                try {
+                    ActivityTaskManager.getService().unregisterTaskStackListener(this);
+                    mRegistered = false;
+                } catch (Exception e) {
+                    Log.w(TAG, "Failed to call unregisterTaskStackListener", e);
+                }
+            }
         }
 
         @Override
-        public void handleMessage(Message msg) {
+        public void onTaskStackChanged() {
+            // Call the task changed callback for the non-ui thread listeners first. Copy to a set
+            // of temp listeners so that we don't lock on mTaskStackListeners while calling all the
+            // callbacks. This call is always on the same binder thread, so we can just synchronize
+            // on the copying of the listener list.
+            synchronized (mTaskStackListeners) {
+                mTmpListeners.addAll(mTaskStackListeners);
+            }
+            for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
+                mTmpListeners.get(i).onTaskStackChangedBackground();
+            }
+            mTmpListeners.clear();
+
+            mHandler.removeMessages(ON_TASK_STACK_CHANGED);
+            mHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED);
+        }
+
+        @Override
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+            mHandler.removeMessages(ON_ACTIVITY_PINNED);
+            mHandler.obtainMessage(ON_ACTIVITY_PINNED,
+                    new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
+        }
+
+        @Override
+        public void onActivityUnpinned() {
+            mHandler.removeMessages(ON_ACTIVITY_UNPINNED);
+            mHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED);
+        }
+
+        @Override
+        public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
+                boolean clearedTask, boolean wasVisible) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = task;
+            args.argi1 = homeTaskVisible ? 1 : 0;
+            args.argi2 = clearedTask ? 1 : 0;
+            args.argi3 = wasVisible ? 1 : 0;
+            mHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT);
+            mHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
+        }
+
+        @Override
+        public void onActivityForcedResizable(String packageName, int taskId, int reason) {
+            mHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onActivityDismissingDockedStack() {
+            mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK);
+        }
+
+        @Override
+        public void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo,
+                int requestedDisplayId) {
+            mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED,
+                    requestedDisplayId,
+                    0 /* unused */,
+                    taskInfo).sendToTarget();
+        }
+
+        @Override
+        public void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo,
+                int requestedDisplayId) {
+            mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED,
+                    requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget();
+        }
+
+        @Override
+        public void onTaskProfileLocked(int taskId, int userId) {
+            mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+        }
+
+        @Override
+        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+            mHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) {
+            mHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget();
+        }
+
+        @Override
+        public void onTaskRemoved(int taskId) {
+            mHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget();
+        }
+
+        @Override
+        public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
+            mHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget();
+        }
+
+        @Override
+        public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+            mHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget();
+        }
+
+        @Override
+        public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+            mHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
+                    requestedOrientation).sendToTarget();
+        }
+
+        @Override
+        public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
+            mHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
+                    0 /* unused */,
+                    activityToken).sendToTarget();
+        }
+
+        @Override
+        public void onSingleTaskDisplayDrawn(int displayId) {
+            mHandler.obtainMessage(ON_SINGLE_TASK_DISPLAY_DRAWN, displayId,
+                    0 /* unused */).sendToTarget();
+        }
+
+        @Override
+        public void onSingleTaskDisplayEmpty(int displayId) {
+            mHandler.obtainMessage(ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
+                    0 /* unused */).sendToTarget();
+        }
+
+        @Override
+        public void onTaskDisplayChanged(int taskId, int newDisplayId) {
+            mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
+        }
+
+        @Override
+        public void onRecentTaskListUpdated() {
+            mHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget();
+        }
+
+        @Override
+        public void onRecentTaskListFrozenChanged(boolean frozen) {
+            mHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onTaskDescriptionChanged(RunningTaskInfo taskInfo) {
+            mHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget();
+        }
+
+        @Override
+        public void onActivityRotation(int displayId) {
+            mHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */)
+                    .sendToTarget();
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
             synchronized (mTaskStackListeners) {
                 switch (msg.what) {
                     case ON_TASK_STACK_CHANGED: {
@@ -298,7 +320,8 @@
                         final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             mTaskStackListeners.get(i).onActivityPinned(
-                                    info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
+                                    info.mPackageName, info.mUserId, info.mTaskId,
+                                    info.mStackId);
                         }
                         break;
                     }
@@ -404,8 +427,7 @@
                     }
                     case ON_SINGLE_TASK_DISPLAY_EMPTY: {
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(
-                                    msg.arg1);
+                            mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(msg.arg1);
                         }
                         break;
                     }
@@ -423,7 +445,8 @@
                     }
                     case ON_TASK_LIST_FROZEN_UNFROZEN: {
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onRecentTaskListFrozenChanged(msg.arg1 != 0);
+                            mTaskStackListeners.get(i).onRecentTaskListFrozenChanged(
+                                    msg.arg1 != 0);
                         }
                         break;
                     }
@@ -445,6 +468,7 @@
             if (msg.obj instanceof SomeArgs) {
                 ((SomeArgs) msg.obj).recycle();
             }
+            return true;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 5ad8cad..272954d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -104,6 +104,8 @@
     private boolean mSupportsDarkText;
     private int[] mColorPalette;
 
+    private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+
     public KeyguardClockSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -128,6 +130,35 @@
         return mClockPlugin != null;
     }
 
+    /**
+      * Update lock screen mode for testing different layouts
+      */
+    public void updateLockScreenMode(int mode) {
+        mLockScreenMode = mode;
+        RelativeLayout.LayoutParams statusAreaLP = (RelativeLayout.LayoutParams)
+                mKeyguardStatusArea.getLayoutParams();
+        RelativeLayout.LayoutParams clockLP = (RelativeLayout.LayoutParams)
+                mSmallClockFrame.getLayoutParams();
+
+        if (mode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+            statusAreaLP.removeRule(RelativeLayout.BELOW);
+            statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.clock_view);
+            statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START);
+
+            clockLP.addRule(RelativeLayout.ALIGN_PARENT_END);
+            clockLP.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+        } else {
+            statusAreaLP.removeRule(RelativeLayout.LEFT_OF);
+            statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
+            statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
+
+            clockLP.removeRule(RelativeLayout.ALIGN_PARENT_END);
+            clockLP.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        }
+
+        requestLayout();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -363,6 +394,10 @@
      * these cases.
      */
     void setKeyguardShowingHeader(boolean hasHeader) {
+        if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+            hasHeader = false;
+        }
+
         if (mShowingHeader == hasHeader) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 6e11174..9ef2def 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -79,6 +79,11 @@
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
         @Override
+        public void onLockScreenModeChanged(int mode) {
+            updateLockScreenMode(mode);
+        }
+
+        @Override
         public void onTimeChanged() {
             refreshTime();
         }
@@ -255,6 +260,10 @@
         mClockView.refresh();
     }
 
+    private void updateLockScreenMode(int mode) {
+        mClockView.updateLockScreenMode(mode);
+    }
+
     private void updateTimeZone(TimeZone timeZone) {
         mClockView.onTimeZoneChanged(timeZone);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bdb34bb..1a98c20 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -100,8 +100,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.Assert;
@@ -180,6 +180,10 @@
     private static final int MSG_USER_STOPPED = 340;
     private static final int MSG_USER_REMOVED = 341;
     private static final int MSG_KEYGUARD_GOING_AWAY = 342;
+    private static final int MSG_LOCK_SCREEN_MODE = 343;
+
+    public static final int LOCK_SCREEN_MODE_NORMAL = 0;
+    public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1;
 
     /** Biometric authentication state: Not listening. */
     private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -263,6 +267,7 @@
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
     private ContentObserver mDeviceProvisionedObserver;
+    private ContentObserver mLockScreenModeObserver;
 
     private boolean mSwitchingUser;
 
@@ -286,6 +291,7 @@
     private boolean mLockIconPressed;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private final Executor mBackgroundExecutor;
+    private int mLockScreenMode;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -1694,6 +1700,9 @@
                     case MSG_KEYGUARD_GOING_AWAY:
                         handleKeyguardGoingAway((boolean) msg.obj);
                         break;
+                    case MSG_LOCK_SCREEN_MODE:
+                        handleLockScreenMode();
+                        break;
                     default:
                         super.handleMessage(msg);
                         break;
@@ -1796,7 +1805,7 @@
 
         mIsAutomotive = isAutomotive();
 
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
         mUserManager = context.getSystemService(UserManager.class);
         mIsPrimaryUser = mUserManager.isPrimaryUser();
         int user = ActivityManager.getCurrentUser();
@@ -1828,6 +1837,23 @@
                 }
             }
         }
+
+        updateLockScreenMode();
+        mLockScreenModeObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                updateLockScreenMode();
+                mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE);
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.SHOW_NEW_LOCKSCREEN),
+                false, mLockScreenModeObserver);
+    }
+
+    private void updateLockScreenMode() {
+        mLockScreenMode = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_NEW_LOCKSCREEN, 0);
     }
 
     private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
@@ -2350,6 +2376,20 @@
     }
 
     /**
+     * Handle {@link #MSG_LOCK_SCREEN_MODE}
+     */
+    private void handleLockScreenMode() {
+        Assert.isMainThread();
+        if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")");
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onLockScreenModeChanged(mLockScreenMode);
+            }
+        }
+    }
+
+    /**
      * Handle (@line #MSG_TIMEZONE_UPDATE}
      */
     private void handleTimeZoneUpdate(String timeZone) {
@@ -2668,6 +2708,8 @@
         callback.onClockVisibilityChanged();
         callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
         callback.onTelephonyCapable(mTelephonyCapable);
+        callback.onLockScreenModeChanged(mLockScreenMode);
+
         for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
             final SimData state = data.getValue();
             callback.onSimStateChanged(state.subId, state.slotId, state.simState);
@@ -2959,13 +3001,17 @@
             mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
         }
 
+        if (mLockScreenModeObserver != null) {
+            mContext.getContentResolver().unregisterContentObserver(mLockScreenModeObserver);
+        }
+
         try {
             ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
         } catch (RemoteException e) {
             Log.d(TAG, "RemoteException onDestroy. cannot unregister userSwitchObserver");
         }
 
-        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
 
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 12e0ecd..3c5eceb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -317,4 +317,9 @@
      */
     public void onSecondaryLockscreenRequirementChanged(int userId) { }
 
+    /**
+     * Called to switch lock screen layout/clock layouts
+     */
+    public void onLockScreenModeChanged(int mode) { }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index f24644b..9f28e09 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -47,6 +47,7 @@
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -344,6 +345,7 @@
     @Inject Lazy<DisplayImeController> mDisplayImeController;
     @Inject Lazy<RecordingController> mRecordingController;
     @Inject Lazy<ProtoTracer> mProtoTracer;
+    @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
 
     @Inject
     public Dependency() {
@@ -541,6 +543,8 @@
 
         mProviders.put(RecordingController.class, mRecordingController::get);
 
+        mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get);
+
         Dependency.setInstance(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 4657b06..f210d50 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -72,8 +72,6 @@
             Key.QS_HAS_TURNED_OFF_MOBILE_DATA,
             Key.TOUCHED_RINGER_TOGGLE,
             Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
-            Key.HAS_SEEN_BUBBLES_EDUCATION,
-            Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION,
             Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
             Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
             Key.HAS_SEEN_PRIORITY_ONBOARDING
@@ -123,8 +121,6 @@
         String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData";
         String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle";
         String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip";
-        String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding";
-        String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding";
         String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet";
         String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
         /** Tracks whether the user has seen the onboarding screen for priority conversations */
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 34efa35..02f34ac 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -42,8 +42,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.lang.ref.WeakReference;
@@ -66,11 +66,11 @@
 
     @VisibleForTesting
     @Inject
-    SizeCompatModeActivityController(Context context, ActivityManagerWrapper am,
+    SizeCompatModeActivityController(Context context, TaskStackChangeListeners listeners,
             CommandQueue commandQueue) {
         super(context);
         mCommandQueue = commandQueue;
-        am.registerTaskStackListener(new TaskStackChangeListener() {
+        listeners.registerTaskStackListener(new TaskStackChangeListener() {
             @Override
             public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
                 // Note the callback already runs on main thread.
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 2365f12..47adffc 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -111,9 +111,7 @@
         final String providerPkg = getIntent().getStringExtra("provider_pkg");
         if (providerPkg == null || mProviderPkg.equals(providerPkg)) return;
         final String callingPkg = getCallingPkg();
-        EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg), String.format(
-                "pkg %s (disguised as %s) attempted to request permission to show %s slices in %s",
-                callingPkg, providerPkg, mProviderPkg, mCallingPkg));
+        EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg));
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 02a672b..c1c2de1 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -47,6 +47,7 @@
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
 
 import java.io.PrintWriter;
@@ -171,6 +172,7 @@
     private final DeviceConfigHelper mDeviceConfigHelper;
     private final Lazy<StatusBarStateController> mStatusBarStateController;
     private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
+    private final Lazy<TaskStackChangeListeners> mTaskStackChangeListeners;
     private final Lazy<OverviewProxyService> mOverviewProxyService;
     private final Lazy<SysUiState> mSysUiFlagContainer;
     private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
@@ -207,6 +209,7 @@
             DeviceConfigHelper deviceConfigHelper,
             Lazy<StatusBarStateController> statusBarStateController,
             Lazy<ActivityManagerWrapper> activityManagerWrapper,
+            Lazy<TaskStackChangeListeners> taskStackChangeListeners,
             Lazy<OverviewProxyService> overviewProxyService,
             Lazy<SysUiState> sysUiFlagContainer,
             Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
@@ -218,6 +221,7 @@
         mDeviceConfigHelper = deviceConfigHelper;
         mStatusBarStateController = statusBarStateController;
         mActivityManagerWrapper = activityManagerWrapper;
+        mTaskStackChangeListeners = taskStackChangeListeners;
         mOverviewProxyService = overviewProxyService;
         mSysUiFlagContainer = sysUiFlagContainer;
         mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -245,7 +249,7 @@
         ActivityManager.RunningTaskInfo runningTaskInfo =
                 mActivityManagerWrapper.get().getRunningTask();
         mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId;
-        mActivityManagerWrapper.get().registerTaskStackListener(mTaskStackChangeListener);
+        mTaskStackChangeListeners.get().registerTaskStackListener(mTaskStackChangeListener);
         mOverviewProxyService.get().addCallback(mOverviewProxyListener);
         mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
         mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
@@ -300,7 +304,7 @@
             mContext = null;
         }
         mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
-        mActivityManagerWrapper.get().unregisterTaskStackListener(mTaskStackChangeListener);
+        mTaskStackChangeListeners.get().unregisterTaskStackListener(mTaskStackChangeListener);
         mOverviewProxyService.get().removeCallback(mOverviewProxyListener);
         mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
         mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index 50d559b..61951cc 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -35,6 +35,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.StatusBar;
 
@@ -82,7 +83,6 @@
         mStatusBarOptionalLazy = statusBarOptionalLazy;
         mStatusBarStateController = Dependency.get(StatusBarStateController.class);
 
-        ActivityManagerWrapper activityManagerWrapper = ActivityManagerWrapper.getInstance();
         mDefaultHome = getCurrentDefaultHome();
         bootCompleteCache.addListener(() -> mDefaultHome = getCurrentDefaultHome());
         IntentFilter intentFilter = new IntentFilter();
@@ -95,12 +95,13 @@
                 mDefaultHome = getCurrentDefaultHome();
             }
         }, intentFilter);
-        mLauncherShowing = isLauncherShowing(activityManagerWrapper.getRunningTask());
-        activityManagerWrapper.registerTaskStackListener(new TaskStackChangeListener() {
-            @Override
-            public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
-                mLauncherShowing = isLauncherShowing(taskInfo);
-            }
+        mLauncherShowing = isLauncherShowing(ActivityManagerWrapper.getInstance().getRunningTask());
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(
+                new TaskStackChangeListener() {
+                    @Override
+                    public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+                        mLauncherShowing = isLauncherShowing(taskInfo);
+                    }
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 529af22..57d8dc7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.bubbles;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.os.AsyncTask.Status.FINISHED;
-import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
@@ -256,7 +256,8 @@
     }
 
     /**
-     * Cleanup expanded view for bubbles going into overflow.
+     * Call this to clean up the task for the bubble. Ensure this is always called when done with
+     * the bubble.
      */
     void cleanupExpandedView() {
         if (mExpandedView != null) {
@@ -270,8 +271,7 @@
     }
 
     /**
-     * Call when the views should be removed, ensure this is called to clean up ActivityView
-     * content.
+     * Call when all the views should be removed/cleaned up.
      */
     void cleanupViews() {
         cleanupExpandedView();
@@ -468,14 +468,6 @@
         return mIntentActive;
     }
 
-    /**
-     * @return the display id of the virtual display on which bubble contents is drawn.
-     */
-    @Override
-    public int getDisplayId() {
-        return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
-    }
-
     public InstanceId getInstanceId() {
         return mInstanceId;
     }
@@ -490,6 +482,14 @@
     }
 
     /**
+     * @return the task id of the task in which bubble contents is drawn.
+     */
+    @Override
+    public int getTaskId() {
+        return mExpandedView != null ? mExpandedView.getTaskId() : INVALID_TASK_ID;
+    }
+
+    /**
      * Should be invoked whenever a Bubble is accessed (selected while expanded).
      */
     void markAsAccessedAt(long lastAccessedMillis) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 2372529..3f94b00 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -27,8 +28,6 @@
 import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -46,6 +45,7 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -70,7 +70,6 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseSetArray;
-import android.view.Display;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -80,15 +79,18 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
 import com.android.systemui.bubbles.dagger.BubbleModule;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
@@ -111,6 +113,7 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
@@ -168,8 +171,8 @@
     private final ShadeController mShadeController;
     private final FloatingContentCoordinator mFloatingContentCoordinator;
     private final BubbleDataRepository mDataRepository;
-    private BubbleLogger mLogger = new BubbleLoggerImpl();
-
+    private BubbleLogger mLogger;
+    private final Handler mMainHandler;
     private BubbleData mBubbleData;
     private ScrimView mBubbleScrim;
     @Nullable private BubbleStackView mStackView;
@@ -241,6 +244,8 @@
 
     private boolean mInflateSynchronously;
 
+    private MultiWindowTaskListener mTaskListener;
+
     // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
     private final List<NotifCallback> mCallbacks = new ArrayList<>();
 
@@ -365,20 +370,24 @@
             FeatureFlags featureFlags,
             DumpManager dumpManager,
             FloatingContentCoordinator floatingContentCoordinator,
-            BubbleDataRepository dataRepository,
             SysUiState sysUiState,
             INotificationManager notificationManager,
             @Nullable IStatusBarService statusBarService,
             WindowManager windowManager,
             WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps) {
+            LauncherApps launcherApps,
+            UiEventLogger uiEventLogger,
+            @Main Handler mainHandler,
+            ShellTaskOrganizer organizer) {
+        BubbleLogger logger = new BubbleLogger(uiEventLogger);
         return new BubbleController(context, notificationShadeWindowController,
-                statusBarStateController, shadeController, new BubbleData(context), synchronizer,
-                configurationController, interruptionStateProvider, zenModeController,
+                statusBarStateController, shadeController, new BubbleData(context, logger),
+                synchronizer, configurationController, interruptionStateProvider, zenModeController,
                 notifUserManager, groupManager, entryManager, notifPipeline, featureFlags,
-                dumpManager, floatingContentCoordinator, dataRepository, sysUiState,
-                notificationManager, statusBarService, windowManager, windowManagerShellWrapper,
-                launcherApps);
+                dumpManager, floatingContentCoordinator,
+                new BubbleDataRepository(context, launcherApps), sysUiState, notificationManager,
+                statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger,
+                mainHandler, organizer);
     }
 
     /**
@@ -407,7 +416,10 @@
             @Nullable IStatusBarService statusBarService,
             WindowManager windowManager,
             WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps) {
+            LauncherApps launcherApps,
+            BubbleLogger bubbleLogger,
+            Handler mainHandler,
+            ShellTaskOrganizer organizer) {
         dumpManager.registerDumpable(TAG, this);
         mContext = context;
         mShadeController = shadeController;
@@ -417,6 +429,8 @@
         mFloatingContentCoordinator = floatingContentCoordinator;
         mDataRepository = dataRepository;
         mINotificationManager = notificationManager;
+        mLogger = bubbleLogger;
+        mMainHandler = mainHandler;
         mZenModeController.addCallback(new ZenModeController.Callback() {
             @Override
             public void onZenChanged(int zen) {
@@ -480,7 +494,7 @@
         statusBarStateController.addCallback(mStatusBarStateListener);
 
         mTaskStackListener = new BubbleTaskStackListener();
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
 
         try {
             windowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener());
@@ -515,6 +529,7 @@
                 });
 
         mBubbleIconFactory = new BubbleIconFactory(context);
+        mTaskListener = new MultiWindowTaskListener(mMainHandler, organizer);
 
         launcherApps.registerCallback(new LauncherApps.Callback() {
             @Override
@@ -577,6 +592,12 @@
         }
     }
 
+    private void onBubbleExpandChanged(boolean shouldExpand) {
+        mSysUiState
+                .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
+                .commitUpdate(mContext.getDisplayId());
+    }
+
     private void setupNEM() {
         mNotificationEntryManager.addNotificationEntryListener(
                 new NotificationEntryListener() {
@@ -783,6 +804,11 @@
         return mBubbleData.getOverflowBubbles();
     }
 
+    @Override
+    public MultiWindowTaskListener getTaskManager() {
+        return mTaskListener;
+    }
+
     /**
      * BubbleStackView is lazily created by this method the first time a Bubble is added. This
      * method initializes the stack view and adds it to the StatusBar just above the scrim.
@@ -791,8 +817,8 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(
                     mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
-                    mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
-                    this::hideCurrentInputMethod);
+                    this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
+                    this::hideCurrentInputMethod, this::onBubbleExpandChanged);
             mStackView.setStackStartPosition(mPositionFromRemovedStack);
             mStackView.addView(mBubbleScrim);
             if (mExpandListener != null) {
@@ -825,9 +851,8 @@
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                // Start not focusable - we'll become focusable when expanded so the ActivityView
-                // can use the IME.
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                 PixelFormat.TRANSLUCENT);
 
@@ -843,16 +868,13 @@
             mAddedToWindowManager = true;
             mWindowManager.addView(mStackView, mWmLayoutParams);
         } catch (IllegalStateException e) {
-            // This means the stack has already been added. This shouldn't happen, since we keep
-            // track of that, but just in case, update the previously added view's layout params.
+            // This means the stack has already been added. This shouldn't happen...
             e.printStackTrace();
-            updateWmFlags();
         }
     }
 
     private void onImeVisibilityChanged(boolean imeVisible) {
         mImeVisible = imeVisible;
-        updateWmFlags();
     }
 
     /** Removes the BubbleStackView from the WindowManager if it's there. */
@@ -879,35 +901,6 @@
     }
 
     /**
-     * Updates the BubbleStackView's WindowManager.LayoutParams, and updates the WindowManager with
-     * the new params if the stack has been added.
-     */
-    private void updateWmFlags() {
-        if (mStackView == null) {
-            return;
-        }
-        if (isStackExpanded() && !mImeVisible) {
-            // If we're expanded, and the IME isn't visible, we want to be focusable. This ensures
-            // that any taps within Bubbles (including on the ActivityView) results in Bubbles
-            // receiving focus and clearing it from any other windows that might have it.
-            mWmLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        } else {
-            // If we're collapsed, we don't want to be focusable since tapping on the stack would
-            // steal focus from apps. We also don't want to be focusable if the IME is visible,
-            mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        }
-
-        if (mAddedToWindowManager) {
-            try {
-                mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
-            } catch (IllegalArgumentException e) {
-                // If the stack is somehow not there, ignore the attempt to update it.
-                e.printStackTrace();
-            }
-        }
-    }
-
-    /**
      * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
      * added in the meantime.
      */
@@ -1015,8 +1008,6 @@
             if (listener != null) {
                 listener.onBubbleExpandChanged(isExpanding, key);
             }
-
-            updateWmFlags();
         });
         if (mStackView != null) {
             mStackView.setExpandListener(mExpandListener);
@@ -1060,7 +1051,7 @@
     @Override
     public boolean isBubbleExpanded(NotificationEntry entry) {
         return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null
-                && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey()) ? true : false;
+                && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey());
     }
 
     @Override
@@ -1115,13 +1106,6 @@
         }
     }
 
-    @Override
-    public void performBackPressIfNeeded() {
-        if (mStackView != null) {
-            mStackView.performBackPressIfNeeded();
-        }
-    }
-
     /**
      * Adds or updates a bubble associated with the provided notification entry.
      *
@@ -1602,19 +1586,21 @@
         mStackView.updateContentDescription();
     }
 
-    @Override
-    public int getExpandedDisplayId(Context context) {
+    /**
+     * The task id of the expanded view, if the stack is expanded and not occluded by the
+     * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
+     */
+    private int getExpandedTaskId() {
         if (mStackView == null) {
-            return INVALID_DISPLAY;
+            return INVALID_TASK_ID;
         }
-        final boolean defaultDisplay = context.getDisplay() != null
-                && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
         final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
-        if (defaultDisplay && expandedViewProvider != null && isStackExpanded()
+        if (expandedViewProvider != null && isStackExpanded()
+                && !mStackView.isExpansionAnimating()
                 && !mNotificationShadeWindowController.getPanelExpanded()) {
-            return expandedViewProvider.getDisplayId();
+            return expandedViewProvider.getTaskId();
         }
-        return INVALID_DISPLAY;
+        return INVALID_TASK_ID;
     }
 
     @VisibleForTesting
@@ -1648,18 +1634,17 @@
 
         @Override
         public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
-            if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
-                if (!mStackView.isExpansionAnimating()) {
-                    mBubbleData.setExpanded(false);
-                }
+            int expandedId = getExpandedTaskId();
+            if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
+                mBubbleData.setExpanded(false);
             }
         }
 
         @Override
-        public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
+        public void onActivityRestartAttempt(RunningTaskInfo taskInfo, boolean homeTaskVisible,
                 boolean clearedTask, boolean wasVisible) {
             for (Bubble b : mBubbleData.getBubbles()) {
-                if (b.getDisplayId() == task.displayId) {
+                if (taskInfo.taskId == b.getTaskId()) {
                     mBubbleData.setSelectedBubble(b);
                     mBubbleData.setExpanded(true);
                     return;
@@ -1667,43 +1652,6 @@
             }
         }
 
-        @Override
-        public void onActivityLaunchOnSecondaryDisplayRerouted() {
-            if (mStackView != null) {
-                mBubbleData.setExpanded(false);
-            }
-        }
-
-        @Override
-        public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
-            if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
-                if (mImeVisible) {
-                    hideCurrentInputMethod();
-                } else {
-                    mBubbleData.setExpanded(false);
-                }
-            }
-        }
-
-        @Override
-        public void onSingleTaskDisplayDrawn(int displayId) {
-            if (mStackView == null) {
-                return;
-            }
-            mStackView.showExpandedViewContents(displayId);
-        }
-
-        @Override
-        public void onSingleTaskDisplayEmpty(int displayId) {
-            final BubbleViewProvider expandedBubble = mStackView != null
-                    ? mStackView.getExpandedBubble()
-                    : null;
-            int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
-            if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
-                mBubbleData.setExpanded(false);
-            }
-            mBubbleData.notifyDisplayEmpty(displayId);
-        }
     }
 
     /**
@@ -1747,6 +1695,7 @@
     }
 
     /** PinnedStackListener that dispatches IME visibility updates to the stack. */
+    //TODO(b/170442945): Better way to do this / insets listener?
     private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 55ecb22..b4626f2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -32,10 +32,9 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController.DismissReason;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -53,10 +52,9 @@
 /**
  * Keeps track of active bubbles.
  */
-@SysUISingleton
 public class BubbleData {
 
-    private BubbleLoggerImpl mLogger = new BubbleLoggerImpl();
+    private BubbleLogger mLogger;
 
     private int mCurrentUserId;
 
@@ -155,8 +153,9 @@
      */
     private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
 
-    public BubbleData(Context context) {
+    public BubbleData(Context context, BubbleLogger bubbleLogger) {
         mContext = context;
+        mLogger = bubbleLogger;
         mBubbles = new ArrayList<>();
         mOverflowBubbles = new ArrayList<>();
         mPendingBubbles = new HashMap<>();
@@ -552,22 +551,6 @@
         dispatchPendingChanges();
     }
 
-    /**
-     * Indicates that the provided display is no longer in use and should be cleaned up.
-     *
-     * @param displayId the id of the display to clean up.
-     */
-    void notifyDisplayEmpty(int displayId) {
-        for (Bubble b : mBubbles) {
-            if (b.getDisplayId() == displayId) {
-                if (b.getExpandedView() != null) {
-                    b.getExpandedView().notifyDisplayEmpty();
-                }
-                return;
-            }
-        }
-    }
-
     private void dispatchPendingChanges() {
         if (mListener != null && mStateChange.anythingChanged()) {
             mListener.applyUpdate(mStateChange);
@@ -618,12 +601,12 @@
      * @param normalX Normalized x position of the stack
      * @param normalY Normalized y position of the stack
      */
-     void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
+    void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
             int bubbleCount, int bubbleIndex, float normalX, float normalY) {
         if (provider == null) {
             mLogger.logStackUiChanged(packageName, action, bubbleCount, normalX, normalY);
         } else if (provider.getKey().equals(BubbleOverflow.KEY)) {
-            if (action == SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED) {
+            if (action == FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED) {
                 mLogger.logShowOverflow(packageName, mCurrentUserId);
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index f129d31..2ab9e87 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -17,6 +17,7 @@
 
 import android.annotation.SuppressLint
 import android.annotation.UserIdInt
+import android.content.Context
 import android.content.pm.LauncherApps
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
@@ -26,21 +27,16 @@
 import com.android.systemui.bubbles.storage.BubbleEntity
 import com.android.systemui.bubbles.storage.BubblePersistentRepository
 import com.android.systemui.bubbles.storage.BubbleVolatileRepository
-import com.android.systemui.dagger.SysUISingleton
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
-import javax.inject.Inject
 
-@SysUISingleton
-internal class BubbleDataRepository @Inject constructor(
-    private val volatileRepository: BubbleVolatileRepository,
-    private val persistentRepository: BubblePersistentRepository,
-    private val launcherApps: LauncherApps
-) {
+internal class BubbleDataRepository(context: Context, private val launcherApps: LauncherApps) {
+    private val volatileRepository = BubbleVolatileRepository(launcherApps)
+    private val persistentRepository = BubblePersistentRepository(context)
 
     private val ioScope = CoroutineScope(Dispatchers.IO)
     private val uiScope = CoroutineScope(Dispatchers.Main)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 3af36a9..98a2257 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -16,16 +16,10 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static android.graphics.PixelFormat.TRANSPARENT;
-import static android.view.Display.INVALID_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
 import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -34,10 +28,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.ActivityView;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -50,19 +41,13 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
-import android.hardware.display.VirtualDisplay;
-import android.os.Binder;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.Gravity;
 import android.view.SurfaceControl;
-import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
-import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
@@ -84,18 +69,6 @@
  */
 public class BubbleExpandedView extends LinearLayout {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
-    private static final String WINDOW_TITLE = "ImeInsetsWindowWithoutContent";
-
-    private enum ActivityViewStatus {
-        // ActivityView is being initialized, cannot start an activity yet.
-        INITIALIZING,
-        // ActivityView is initialized, and ready to start an activity.
-        INITIALIZED,
-        // Activity runs in the ActivityView.
-        ACTIVITY_STARTED,
-        // ActivityView is released, so activity launching will no longer be permitted.
-        RELEASED,
-    }
 
     // The triangle pointing to the expanded view
     private View mPointerView;
@@ -103,16 +76,11 @@
     @Nullable private int[] mExpandedViewContainerLocation;
 
     private AlphaOptimizedButton mSettingsIcon;
+    private TaskView mTaskView;
 
-    // Views for expanded state
-    private ActivityView mActivityView;
+    private int mTaskId = INVALID_TASK_ID;
 
-    private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
-    private int mTaskId = -1;
-
-    private PendingIntent mPendingIntent;
-
-    private boolean mKeyboardVisible;
+    private boolean mImeVisible;
     private boolean mNeedsNewHeight;
 
     private Point mDisplaySize;
@@ -123,123 +91,104 @@
     private int mPointerHeight;
     private ShapeDrawable mPointerDrawable;
     private int mExpandedViewPadding;
-
+    private float mCornerRadius = 0f;
 
     @Nullable private Bubble mBubble;
+    private PendingIntent mPendingIntent;
 
     private boolean mIsOverflow;
 
     private Bubbles mBubbles = Dependency.get(Bubbles.class);
     private WindowManager mWindowManager;
-    private ActivityManager mActivityManager;
-
     private BubbleStackView mStackView;
-    private View mVirtualImeView;
-    private WindowManager mVirtualDisplayWindowManager;
-    private boolean mImeShowing = false;
-    private float mCornerRadius = 0f;
 
     /**
      * Container for the ActivityView that has a solid, round-rect background that shows if the
      * ActivityView hasn't loaded.
      */
-    private FrameLayout mActivityViewContainer = new FrameLayout(getContext());
+    private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext());
 
-    /** The SurfaceView that the ActivityView draws to. */
-    @Nullable private SurfaceView mActivitySurface;
+    private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+        private boolean mInitialized = false;
+        private boolean mDestroyed = false;
 
-    private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
         @Override
-        public void onActivityViewReady(ActivityView view) {
+        public void onInitialized() {
             if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus
+                Log.d(TAG, "onActivityViewReady: destroyed=" + mDestroyed
+                        + " initialized=" + mInitialized
                         + " bubble=" + getBubbleKey());
             }
-            switch (mActivityViewStatus) {
-                case INITIALIZING:
-                case INITIALIZED:
-                    // Custom options so there is no activity transition animation
-                    ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
-                            0 /* enterResId */, 0 /* exitResId */);
-                    options.setTaskAlwaysOnTop(true);
-                    // Soptions.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-                    // Post to keep the lifecycle normal
-                    post(() -> {
-                        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                            Log.d(TAG, "onActivityViewReady: calling startActivity, "
-                                    + "bubble=" + getBubbleKey());
-                        }
-                        if (mActivityView == null) {
-                            mBubbles.removeBubble(getBubbleKey(),
-                                    BubbleController.DISMISS_INVALID_INTENT);
-                            return;
-                        }
-                        try {
-                            if (!mIsOverflow && mBubble.hasMetadataShortcutId()
-                                    && mBubble.getShortcutInfo() != null) {
-                                options.setApplyActivityFlagsForBubbles(true);
-                                mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),
-                                        options, null /* sourceBounds */);
-                            } else {
-                                Intent fillInIntent = new Intent();
-                                // Apply flags to make behaviour match documentLaunchMode=always.
-                                fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
-                                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-                                if (mBubble != null) {
-                                    mBubble.setIntentActive();
-                                }
-                                mActivityView.startActivity(mPendingIntent, fillInIntent, options);
-                            }
-                        } catch (RuntimeException e) {
-                            // If there's a runtime exception here then there's something
-                            // wrong with the intent, we can't really recover / try to populate
-                            // the bubble again so we'll just remove it.
-                            Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
-                                    + ", " + e.getMessage() + "; removing bubble");
-                            mBubbles.removeBubble(getBubbleKey(),
-                                    BubbleController.DISMISS_INVALID_INTENT);
-                        }
-                    });
-                    mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
-                    break;
-                case ACTIVITY_STARTED:
-                    post(() -> mActivityManager.moveTaskToFront(mTaskId, 0));
-                    break;
+
+            if (mDestroyed || mInitialized) {
+                return;
             }
+            // Custom options so there is no activity transition animation
+            ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+                    0 /* enterResId */, 0 /* exitResId */);
+
+            // TODO: I notice inconsistencies in lifecycle
+            // Post to keep the lifecycle normal
+            post(() -> {
+                if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                    Log.d(TAG, "onActivityViewReady: calling startActivity, bubble="
+                            + getBubbleKey());
+                }
+                try {
+                    if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
+                        mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+                                options, null /* sourceBounds */);
+                    } else {
+                        Intent fillInIntent = new Intent();
+                        // Apply flags to make behaviour match documentLaunchMode=always.
+                        fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+                        fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                        if (mBubble != null) {
+                            mBubble.setIntentActive();
+                        }
+                        mTaskView.startActivity(mPendingIntent, fillInIntent, options);
+                    }
+                } catch (RuntimeException e) {
+                    // If there's a runtime exception here then there's something
+                    // wrong with the intent, we can't really recover / try to populate
+                    // the bubble again so we'll just remove it.
+                    Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+                            + ", " + e.getMessage() + "; removing bubble");
+                    mBubbles.removeBubble(getBubbleKey(),
+                            BubbleController.DISMISS_INVALID_INTENT);
+                }
+            });
+            mInitialized = true;
         }
 
         @Override
-        public void onActivityViewDestroyed(ActivityView view) {
-            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-                Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus
-                        + " bubble=" + getBubbleKey());
-            }
-            mActivityViewStatus = ActivityViewStatus.RELEASED;
+        public void onReleased() {
+            mDestroyed = true;
         }
 
         @Override
-        public void onTaskCreated(int taskId, ComponentName componentName) {
+        public void onTaskCreated(int taskId, ComponentName name) {
             if (DEBUG_BUBBLE_EXPANDED_VIEW) {
                 Log.d(TAG, "onTaskCreated: taskId=" + taskId
                         + " bubble=" + getBubbleKey());
             }
-            // Since Bubble ActivityView applies singleTaskDisplay this is
-            // guaranteed to only be called once per ActivityView. The taskId is
-            // saved to use for removeTask, preventing appearance in recent tasks.
+            // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
             mTaskId = taskId;
+
+            // With the task org, the taskAppeared callback will only happen once the task has
+            // already drawn
+            setContentVisibility(true);
         }
 
-        /**
-         * This is only called for tasks on this ActivityView, which is also set to
-         * single-task mode -- meaning never more than one task on this display. If a task
-         * is being removed, it's the top Activity finishing and this bubble should
-         * be removed or collapsed.
-         */
+        @Override
+        public void onTaskVisibilityChanged(int taskId, boolean visible) {
+            setContentVisibility(visible);
+        }
+
         @Override
         public void onTaskRemovalStarted(int taskId) {
             if (DEBUG_BUBBLE_EXPANDED_VIEW) {
                 Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
-                        + " mActivityViewStatus=" + mActivityViewStatus
                         + " bubble=" + getBubbleKey());
             }
             if (mBubble != null) {
@@ -248,6 +197,13 @@
                         BubbleController.DISMISS_TASK_FINISHED));
             }
         }
+
+        @Override
+        public void onBackPressedOnTaskRoot(int taskId) {
+            if (mTaskId == taskId && mStackView.isExpanded()) {
+                mBubbles.collapseStack();
+            }
+        }
     };
 
     public BubbleExpandedView(Context context) {
@@ -266,7 +222,6 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         updateDimensions();
-        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
     }
 
     void updateDimensions() {
@@ -284,9 +239,6 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey());
-        }
 
         Resources res = getResources();
         mPointerView = findViewById(R.id.pointer_view);
@@ -301,35 +253,21 @@
                 R.dimen.bubble_manage_button_height);
         mSettingsIcon = findViewById(R.id.settings_button);
 
-        mActivityView = new ActivityView.Builder(mContext)
-                .setSingleInstance(true)
-                .setDisableSurfaceViewBackgroundLayer(true)
-                .setUseTrustedDisplay(true)
-                .build();
-
+        mTaskView = new TaskView(mContext, mBubbles.getTaskManager());
         // Set ActivityView's alpha value as zero, since there is no view content to be shown.
         setContentVisibility(false);
 
-        mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
+        mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
                 outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
             }
         });
-        mActivityViewContainer.setClipToOutline(true);
-        mActivityViewContainer.addView(mActivityView);
-        mActivityViewContainer.setLayoutParams(
+        mExpandedViewContainer.setClipToOutline(true);
+        mExpandedViewContainer.addView(mTaskView);
+        mExpandedViewContainer.setLayoutParams(
                 new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-        addView(mActivityViewContainer);
-
-        if (mActivityView != null
-                && mActivityView.getChildCount() > 0
-                && mActivityView.getChildAt(0) instanceof SurfaceView) {
-            // Retrieve the surface from the ActivityView so we can screenshot it and change its
-            // z-ordering. This should always be possible, since ActivityView's constructor adds the
-            // SurfaceView as its first child.
-            mActivitySurface = (SurfaceView) mActivityView.getChildAt(0);
-        }
+        addView(mExpandedViewContainer);
 
         // Expanded stack layout, top to bottom:
         // Expanded view container
@@ -337,33 +275,22 @@
         // ==> expanded view
         //   ==> activity view
         //   ==> manage button
-        bringChildToFront(mActivityView);
+        bringChildToFront(mTaskView);
         bringChildToFront(mSettingsIcon);
+        mTaskView.setListener(mTaskViewListener);
 
         applyThemeAttrs();
 
-        setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
-            // Keep track of IME displaying because we should not make any adjustments that might
-            // cause a config change while the IME is displayed otherwise it'll loose focus.
-            final int keyboardHeight = insets.getSystemWindowInsetBottom()
-                    - insets.getStableInsetBottom();
-            mKeyboardVisible = keyboardHeight != 0;
-            if (!mKeyboardVisible && mNeedsNewHeight) {
-                updateHeight();
-            }
-            return view.onApplyWindowInsets(insets);
-        });
-
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         setPadding(mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding,
                 mExpandedViewPadding);
         setOnTouchListener((view, motionEvent) -> {
-            if (!usingActivityView()) {
+            if (mTaskView == null) {
                 return false;
             }
 
             final Rect avBounds = new Rect();
-            mActivityView.getBoundsOnScreen(avBounds);
+            mTaskView.getBoundsOnScreen(avBounds);
 
             // Consume and ignore events on the expanded view padding that are within the
             // ActivityView's vertical bounds. These events are part of a back gesture, and so they
@@ -389,51 +316,58 @@
     }
 
     /**
-     * Asks the ActivityView's surface to draw on top of all other views in the window. This is
-     * useful for ordering surfaces during animations, but should otherwise be set to false so that
-     * bubbles and menus can draw over the ActivityView.
+     * Sets whether the surface displaying app content should sit on top. This is useful for
+     * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
+     * being dragged out, the manage menu) this is set to false, otherwise it should be true.
      */
     void setSurfaceZOrderedOnTop(boolean onTop) {
-        if (mActivitySurface == null) {
+        if (mTaskView == null) {
             return;
         }
-
-        mActivitySurface.setZOrderedOnTop(onTop, true);
+        mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */);
     }
 
-    /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */
+    void setImeVisible(boolean visible) {
+        mImeVisible = visible;
+        if (!mImeVisible && mNeedsNewHeight) {
+            updateHeight();
+        }
+    }
+
+    /** Return a GraphicBuffer with the contents of the task view surface. */
     @Nullable
     SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
-        if (mActivitySurface == null) {
+        if (mTaskView == null) {
             return null;
         }
-
         return SurfaceControl.captureLayers(
-                mActivitySurface.getSurfaceControl(),
-                new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()),
+                mTaskView.getSurfaceControl(),
+                new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()),
                 1 /* scale */);
     }
 
-    int[] getActivityViewLocationOnScreen() {
-        if (mActivityView != null) {
-            return mActivityView.getLocationOnScreen();
+    int[] getTaskViewLocationOnScreen() {
+        if (mTaskView != null) {
+            return mTaskView.getLocationOnScreen();
         } else {
             return new int[]{0, 0};
         }
     }
 
+    // TODO: Could listener be passed when we pass StackView / can we avoid setting this like this
     void setManageClickListener(OnClickListener manageClickListener) {
-        findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
+        mSettingsIcon.setOnClickListener(manageClickListener);
     }
 
     /**
-     * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which
-     * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful
-     * if a view has been added or removed from on top of the ActivityView, such as the manage menu.
+     * Updates the obscured touchable region for the task surface. This calls onLocationChanged,
+     * which results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is
+     * useful if a view has been added or removed from on top of the ActivityView, such as the
+     * manage menu.
      */
     void updateObscuredTouchableRegion() {
-        if (mActivityView != null) {
-            mActivityView.onLocationChanged();
+        if (mTaskView != null) {
+            mTaskView.onLocationChanged();
         }
     }
 
@@ -442,12 +376,12 @@
                 android.R.attr.dialogCornerRadius,
                 android.R.attr.colorBackgroundFloating});
         mCornerRadius = ta.getDimensionPixelSize(0, 0);
-        mActivityViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
+        mExpandedViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
         ta.recycle();
 
-        if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+        if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                 mContext.getResources())) {
-            mActivityView.setCornerRadius(mCornerRadius);
+            mTaskView.setCornerRadius(mCornerRadius);
         }
 
         final int mode =
@@ -466,11 +400,8 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mKeyboardVisible = false;
+        mImeVisible = false;
         mNeedsNewHeight = false;
-        if (mActivityView != null) {
-            setImeWindowToDisplay(0, 0);
-        }
         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
             Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
         }
@@ -492,84 +423,23 @@
         final float alpha = visibility ? 1f : 0f;
 
         mPointerView.setAlpha(alpha);
-
-        if (mActivityView != null && alpha != mActivityView.getAlpha()) {
-            mActivityView.setAlpha(alpha);
-            mActivityView.bringToFront();
+        if (mTaskView == null) {
+            return;
+        }
+        if (alpha != mTaskView.getAlpha()) {
+            mTaskView.setAlpha(alpha);
         }
     }
 
-    @Nullable ActivityView getActivityView() {
-        return mActivityView;
+    @Nullable
+    View getTaskView() {
+        return mTaskView;
     }
 
     int getTaskId() {
         return mTaskId;
     }
 
-    /**
-     * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
-     * This should be done post-move and post-animation.
-     */
-    void updateInsets(WindowInsets insets) {
-        if (usingActivityView()) {
-            int[] screenLoc = mActivityView.getLocationOnScreen();
-            final int activityViewBottom = screenLoc[1] + mActivityView.getHeight();
-            final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(),
-                    insets.getDisplayCutout() != null
-                            ? insets.getDisplayCutout().getSafeInsetBottom()
-                            : 0);
-            setImeWindowToDisplay(getWidth(), Math.max(activityViewBottom - keyboardTop, 0));
-        }
-    }
-
-    private void setImeWindowToDisplay(int w, int h) {
-        if (getVirtualDisplayId() == INVALID_DISPLAY) {
-            return;
-        }
-        if (h == 0 || w == 0) {
-            if (mImeShowing) {
-                mVirtualImeView.setVisibility(GONE);
-                mImeShowing = false;
-            }
-            return;
-        }
-        final Context virtualDisplayContext = mContext.createDisplayContext(
-                getVirtualDisplay().getDisplay());
-
-        if (mVirtualDisplayWindowManager == null) {
-            mVirtualDisplayWindowManager =
-                    (WindowManager) virtualDisplayContext.getSystemService(Context.WINDOW_SERVICE);
-        }
-        if (mVirtualImeView == null) {
-            mVirtualImeView = new View(virtualDisplayContext);
-            mVirtualImeView.setVisibility(VISIBLE);
-            mVirtualDisplayWindowManager.addView(mVirtualImeView,
-                    getVirtualImeViewAttrs(w, h));
-        } else {
-            mVirtualDisplayWindowManager.updateViewLayout(mVirtualImeView,
-                    getVirtualImeViewAttrs(w, h));
-            mVirtualImeView.setVisibility(VISIBLE);
-        }
-
-        mImeShowing = true;
-    }
-
-    private WindowManager.LayoutParams getVirtualImeViewAttrs(int w, int h) {
-        // To use TYPE_NAVIGATION_BAR_PANEL instead of TYPE_IME_BAR to bypass the IME window type
-        // token check when adding the window.
-        final WindowManager.LayoutParams attrs =
-                new WindowManager.LayoutParams(w, h, TYPE_NAVIGATION_BAR_PANEL,
-                        FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE,
-                        TRANSPARENT);
-        attrs.gravity = Gravity.BOTTOM;
-        attrs.setTitle(WINDOW_TITLE);
-        attrs.token = new Binder();
-        attrs.providesInsetsTypes = new int[]{ITYPE_IME};
-        attrs.alpha = 0.0f;
-        return attrs;
-    }
-
     void setStackView(BubbleStackView stackView) {
         mStackView = stackView;
     }
@@ -581,7 +451,7 @@
         Bundle extras = new Bundle();
         extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mBubbles));
         target.putExtras(extras);
-        mPendingIntent = PendingIntent.getActivity(mContext, /* requestCode */ 0,
+        mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
                 target, PendingIntent.FLAG_UPDATE_CURRENT);
         mSettingsIcon.setVisibility(GONE);
     }
@@ -619,7 +489,7 @@
                 mPendingIntent = mBubble.getBubbleIntent();
                 if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) {
                     setContentVisibility(false);
-                    mActivityView.setVisibility(VISIBLE);
+                    mTaskView.setVisibility(VISIBLE);
                 }
             }
             applyThemeAttrs();
@@ -629,59 +499,42 @@
         }
     }
 
+    /**
+     * Bubbles are backed by a pending intent or a shortcut, once the activity is
+     * started we never change it / restart it on notification updates -- unless the bubbles'
+     * backing data switches.
+     *
+     * This indicates if the new bubble is backed by a different data source than what was
+     * previously shown here (e.g. previously a pending intent & now a shortcut).
+     *
+     * @param newBubble the bubble this view is being updated with.
+     * @return true if the backing content has changed.
+     */
     private boolean didBackingContentChange(Bubble newBubble) {
         boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
         boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
         return prevWasIntentBased != newIsIntentBased;
     }
 
-    /**
-     * Lets activity view know it should be shown / populated with activity content.
-     */
-    void populateExpandedView() {
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "populateExpandedView: "
-                    + "bubble=" + getBubbleKey());
-        }
-
-        if (usingActivityView()) {
-            mActivityView.setCallback(mStateCallback);
-        } else {
-            Log.e(TAG, "Cannot populate expanded view.");
-        }
-    }
-
-    boolean performBackPressIfNeeded() {
-        if (!usingActivityView()) {
-            return false;
-        }
-        mActivityView.performBackPress();
-        return true;
-    }
-
     void updateHeight() {
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
-        }
-
         if (mExpandedViewContainerLocation == null) {
             return;
         }
 
-        if (usingActivityView()) {
+        if (mBubble != null || mIsOverflow) {
             float desiredHeight = mOverflowHeight;
             if (!mIsOverflow) {
                 desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
             }
             float height = Math.min(desiredHeight, getMaxExpandedHeight());
-            height = Math.max(height, mMinHeight);
-            ViewGroup.LayoutParams lp = mActivityView.getLayoutParams();
+            height = Math.max(height, mIsOverflow ? mOverflowHeight : mMinHeight);
+            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
             mNeedsNewHeight = lp.height != height;
-            if (!mKeyboardVisible) {
-                // If the keyboard is visible... don't adjust the height because that will cause
-                // a configuration change and the keyboard will be lost.
+            if (!mImeVisible) {
+                // If the ime is visible... don't adjust the height because that will cause
+                // a configuration change and the ime will be lost.
                 lp.height = (int) height;
-                mActivityView.setLayoutParams(lp);
+                mTaskView.setLayoutParams(lp);
                 mNeedsNewHeight = false;
             }
             if (DEBUG_BUBBLE_EXPANDED_VIEW) {
@@ -694,12 +547,15 @@
 
     private int getMaxExpandedHeight() {
         mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
+        int expandedContainerY = mExpandedViewContainerLocation != null
+                ? mExpandedViewContainerLocation[1]
+                : 0;
         int bottomInset = getRootWindowInsets() != null
                 ? getRootWindowInsets().getStableInsetBottom()
                 : 0;
 
         return mDisplaySize.y
-                - mExpandedViewContainerLocation[1]
+                - expandedContainerY
                 - getPaddingTop()
                 - getPaddingBottom()
                 - mSettingsIconHeight
@@ -719,14 +575,12 @@
             Log.d(TAG, "updateView: bubble="
                     + getBubbleKey());
         }
-
         mExpandedViewContainerLocation = containerLocationOnScreen;
-
-        if (usingActivityView()
-                && mActivityView.getVisibility() == VISIBLE
-                && mActivityView.isAttachedToWindow()) {
-            mActivityView.onLocationChanged();
+        if (mTaskView != null
+                && mTaskView.getVisibility() == VISIBLE
+                && mTaskView.isAttachedToWindow()) {
             updateHeight();
+            mTaskView.onLocationChanged();
         }
     }
 
@@ -749,65 +603,19 @@
     }
 
     /**
-     * Removes and releases an ActivityView if one was previously created for this bubble.
+     * Cleans up anything related to the task and TaskView.
      */
     public void cleanUpExpandedState() {
         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus
-                    + ", bubble=" + getBubbleKey());
+            Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
         }
-        if (mActivityView == null) {
-            return;
+        if (mTaskView != null) {
+            mTaskView.release();
         }
-        mActivityView.release();
-        if (mTaskId != -1) {
-            try {
-                ActivityTaskManager.getService().removeTask(mTaskId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to remove taskId " + mTaskId);
-            }
-            mTaskId = -1;
+        if (mTaskView != null) {
+            removeView(mTaskView);
+            mTaskView = null;
         }
-        removeView(mActivityView);
-
-        mActivityView = null;
-    }
-
-    /**
-     * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
-     * which {@link ActivityView} uses.
-     */
-    void notifyDisplayEmpty() {
-        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
-            Log.d(TAG, "notifyDisplayEmpty: bubble="
-                    + getBubbleKey()
-                    + " mActivityViewStatus=" + mActivityViewStatus);
-        }
-        if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
-            mActivityViewStatus = ActivityViewStatus.INITIALIZED;
-        }
-    }
-
-    private boolean usingActivityView() {
-        return (mPendingIntent != null || mBubble.hasMetadataShortcutId())
-                && mActivityView != null;
-    }
-
-    /**
-     * @return the display id of the virtual display.
-     */
-    public int getVirtualDisplayId() {
-        if (usingActivityView()) {
-            return mActivityView.getVirtualDisplayId();
-        }
-        return INVALID_DISPLAY;
-    }
-
-    private VirtualDisplay getVirtualDisplay() {
-        if (usingActivityView()) {
-            return mActivityView.getVirtualDisplay();
-        }
-        return null;
     }
 
     /**
@@ -817,7 +625,6 @@
             @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.print("BubbleExpandedView");
         pw.print("  taskId:               "); pw.println(mTaskId);
-        pw.print("  activityViewStatus:   "); pw.println(mActivityViewStatus);
         pw.print("  stackView:            "); pw.println(mStackView);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 69f7828..009114f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -18,6 +18,8 @@
 
 import static android.graphics.Paint.ANTI_ALIAS_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+import static com.android.systemui.Interpolators.ALPHA_IN;
+import static com.android.systemui.Interpolators.ALPHA_OUT;
 
 import android.animation.ArgbEvaluator;
 import android.content.Context;
@@ -56,6 +58,11 @@
     /** Max width of the flyout, in terms of percent of the screen width. */
     private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
 
+    /** Translation Y of fade animation. */
+    private static final float FLYOUT_FADE_Y = 40f;
+
+    private static final long FLYOUT_FADE_DURATION = 200L;
+
     private final int mFlyoutPadding;
     private final int mFlyoutSpaceFromBubble;
     private final int mPointerSize;
@@ -104,6 +111,9 @@
     /** The bounds of the flyout background, kept up to date as it transitions to the 'new' dot. */
     private final RectF mBgRect = new RectF();
 
+    /** The y position of the flyout, relative to the top of the screen. */
+    private float mFlyoutY = 0f;
+
     /**
      * Percent progress in the transition from flyout to 'new' dot. These two values are the inverse
      * of each other (if we're 40% transitioned to the dot, we're 60% flyout), but it makes the code
@@ -221,18 +231,33 @@
         mSenderText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize);
     }
 
-    /** Configures the flyout, collapsed into to dot form. */
-    void setupFlyoutStartingAsDot(
-            Bubble.FlyoutMessage flyoutMessage,
-            PointF stackPos,
-            float parentWidth,
-            boolean arrowPointingLeft,
-            int dotColor,
-            @Nullable Runnable onLayoutComplete,
-            @Nullable Runnable onHide,
-            float[] dotCenter,
-            boolean hideDot) {
+    /*
+     * Fade animation for consecutive flyouts.
+     */
+    void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, float stackY) {
+        fade(false /* in */);
+        updateFlyoutMessage(flyoutMessage, parentWidth);
+        // Wait for TextViews to layout with updated height.
+        post(() -> {
+            mFlyoutY = stackY + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
+            fade(true /* in */);
+        });
+    }
 
+    private void fade(boolean in) {
+        setAlpha(in ? 0f : 1f);
+        setTranslationY(in ? mFlyoutY : mFlyoutY + FLYOUT_FADE_Y);
+        animate()
+                .alpha(in ? 1f : 0f)
+                .setDuration(FLYOUT_FADE_DURATION)
+                .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
+        animate()
+                .translationY(in ? mFlyoutY : mFlyoutY - FLYOUT_FADE_Y)
+                .setDuration(FLYOUT_FADE_DURATION)
+                .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
+    }
+
+    private void updateFlyoutMessage(Bubble.FlyoutMessage flyoutMessage, float parentWidth) {
         final Drawable senderAvatar = flyoutMessage.senderAvatar;
         if (senderAvatar != null && flyoutMessage.isGroupChat) {
             mSenderAvatar.setVisibility(VISIBLE);
@@ -256,6 +281,27 @@
             mSenderText.setVisibility(GONE);
         }
 
+        // Set the flyout TextView's max width in terms of percent, and then subtract out the
+        // padding so that the entire flyout view will be the desired width (rather than the
+        // TextView being the desired width + extra padding).
+        mMessageText.setMaxWidth(maxTextViewWidth);
+        mMessageText.setText(flyoutMessage.message);
+    }
+
+    /** Configures the flyout, collapsed into dot form. */
+    void setupFlyoutStartingAsDot(
+            Bubble.FlyoutMessage flyoutMessage,
+            PointF stackPos,
+            float parentWidth,
+            boolean arrowPointingLeft,
+            int dotColor,
+            @Nullable Runnable onLayoutComplete,
+            @Nullable Runnable onHide,
+            float[] dotCenter,
+            boolean hideDot)  {
+
+        updateFlyoutMessage(flyoutMessage, parentWidth);
+
         mArrowPointingLeft = arrowPointingLeft;
         mDotColor = dotColor;
         mOnHide = onHide;
@@ -263,24 +309,12 @@
 
         setCollapsePercent(1f);
 
-        // Set the flyout TextView's max width in terms of percent, and then subtract out the
-        // padding so that the entire flyout view will be the desired width (rather than the
-        // TextView being the desired width + extra padding).
-        mMessageText.setMaxWidth(maxTextViewWidth);
-        mMessageText.setText(flyoutMessage.message);
-
-        // Wait for the TextView to lay out so we know its line count.
+        // Wait for TextViews to layout with updated height.
         post(() -> {
-            float restingTranslationY;
-            // Multi line flyouts get top-aligned to the bubble.
-            if (mMessageText.getLineCount() > 1) {
-                restingTranslationY = stackPos.y + mBubbleIconTopPadding;
-            } else {
-                // Single line flyouts are vertically centered with respect to the bubble.
-                restingTranslationY =
-                        stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
-            }
-            setTranslationY(restingTranslationY);
+            // Flyout is vertically centered with respect to the bubble.
+            mFlyoutY =
+                    stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
+            setTranslationY(mFlyoutY);
 
             // Calculate the translation required to position the flyout next to the bubble stack,
             // with the desired padding.
@@ -300,7 +334,7 @@
             final float dotPositionY = stackPos.y + mDotCenter[1] - adjustmentForScaleAway;
 
             final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX;
-            final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY;
+            final float distanceFromLayoutTopToDotCenterY = mFlyoutY - dotPositionY;
 
             mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX;
             mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
index 86ba8c5..48c809d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
@@ -19,17 +19,22 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
 
 /**
- * Interface for handling bubble-specific logging.
+ * Implementation of UiEventLogger for logging bubble UI events.
+ *
+ * See UiEventReported atom in atoms.proto for more context.
  */
-public interface BubbleLogger extends UiEventLogger {
+public class BubbleLogger {
+
+    private final UiEventLogger mUiEventLogger;
 
     /**
      * Bubble UI event.
      */
     @VisibleForTesting
-    enum Event implements UiEventLogger.UiEventEnum {
+    public enum Event implements UiEventLogger.UiEventEnum {
 
         @UiEvent(doc = "User dismissed the bubble via gesture, add bubble to overflow.")
         BUBBLE_OVERFLOW_ADD_USER_GESTURE(483),
@@ -70,23 +75,80 @@
         }
     }
 
+    public BubbleLogger(UiEventLogger uiEventLogger) {
+        mUiEventLogger = uiEventLogger;
+    }
+
     /**
      * @param b Bubble involved in this UI event
      * @param e UI event
      */
-    void log(Bubble b, UiEventEnum e);
+    public void log(Bubble b, UiEventLogger.UiEventEnum e) {
+        mUiEventLogger.logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
+    }
 
     /**
-     *
      * @param b Bubble removed from overflow
-     * @param r Reason that bubble was removed from overflow
+     * @param r Reason that bubble was removed
      */
-    void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r);
+    public void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r) {
+        if (r == BubbleController.DISMISS_NOTIF_CANCEL) {
+            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
+        } else if (r == BubbleController.DISMISS_GROUP_CANCELLED) {
+            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL);
+        } else if (r == BubbleController.DISMISS_NO_LONGER_BUBBLE) {
+            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE);
+        } else if (r == BubbleController.DISMISS_BLOCKED) {
+            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BLOCKED);
+        }
+    }
 
     /**
-     *
      * @param b Bubble added to overflow
      * @param r Reason that bubble was added to overflow
      */
-    void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r);
-}
+    public void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r) {
+        if (r == BubbleController.DISMISS_AGED) {
+            log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
+        } else if (r == BubbleController.DISMISS_USER_GESTURE) {
+            log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
+        }
+    }
+
+    void logStackUiChanged(String packageName, int action, int bubbleCount, float normalX,
+            float normalY) {
+        FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
+                packageName,
+                null /* notification channel */,
+                0 /* notification ID */,
+                0 /* bubble position */,
+                bubbleCount,
+                action,
+                normalX,
+                normalY,
+                false /* unread bubble */,
+                false /* on-going bubble */,
+                false /* isAppForeground (unused) */);
+    }
+
+    void logShowOverflow(String packageName, int currentUserId) {
+        mUiEventLogger.log(BubbleLogger.Event.BUBBLE_OVERFLOW_SELECTED, currentUserId,
+                packageName);
+    }
+
+    void logBubbleUiChanged(Bubble bubble, String packageName, int action, int bubbleCount,
+            float normalX, float normalY, int index) {
+        FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
+                packageName,
+                bubble.getChannelId() /* notification channel */,
+                bubble.getNotificationId() /* notification ID */,
+                index,
+                bubbleCount,
+                action,
+                normalX,
+                normalY,
+                bubble.showInShade() /* isUnread */,
+                false /* isOngoing (unused) */,
+                false /* isAppForeground (unused) */);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
deleted file mode 100644
index ea612af..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
+++ /dev/null
@@ -1,103 +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.systemui.bubbles;
-
-import android.os.UserHandle;
-
-import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.systemui.shared.system.SysUiStatsLog;
-
-/**
- * Implementation of UiEventLogger for logging bubble UI events.
- *
- * See UiEventReported atom in atoms.proto for more context.
- */
-public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger {
-
-    /**
-     * @param b Bubble involved in this UI event
-     * @param e UI event
-     */
-    public void log(Bubble b, UiEventEnum e) {
-        logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
-    }
-
-    /**
-     * @param b Bubble removed from overflow
-     * @param r Reason that bubble was removed
-     */
-    public void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r) {
-        if (r == BubbleController.DISMISS_NOTIF_CANCEL) {
-            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
-        } else if (r == BubbleController.DISMISS_GROUP_CANCELLED) {
-            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL);
-        } else if (r == BubbleController.DISMISS_NO_LONGER_BUBBLE) {
-            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE);
-        } else if (r == BubbleController.DISMISS_BLOCKED) {
-            log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BLOCKED);
-        }
-    }
-
-    /**
-     * @param b Bubble added to overflow
-     * @param r Reason that bubble was added to overflow
-     */
-    public void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r) {
-        if (r == BubbleController.DISMISS_AGED) {
-            log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
-        } else if (r == BubbleController.DISMISS_USER_GESTURE) {
-            log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
-        }
-    }
-
-    void logStackUiChanged(String packageName, int action, int bubbleCount, float normalX,
-            float normalY) {
-        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
-                packageName,
-                null /* notification channel */,
-                0 /* notification ID */,
-                0 /* bubble position */,
-                bubbleCount,
-                action,
-                normalX,
-                normalY,
-                false /* unread bubble */,
-                false /* on-going bubble */,
-                false /* isAppForeground (unused) */);
-    }
-
-    void logShowOverflow(String packageName, int currentUserId) {
-        super.log(BubbleLogger.Event.BUBBLE_OVERFLOW_SELECTED, currentUserId,
-                packageName);
-    }
-
-    void logBubbleUiChanged(Bubble bubble, String packageName, int action, int bubbleCount,
-            float normalX, float normalY, int index) {
-        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
-                packageName,
-                bubble.getChannelId() /* notification channel */,
-                bubble.getNotificationId() /* notification ID */,
-                index,
-                bubbleCount,
-                action,
-                normalX,
-                normalY,
-                bubble.showInShade() /* isUnread */,
-                false /* isOngoing (unused) */,
-                false /* isAppForeground (unused) */);
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
index bf7c860..102055d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bubbles
 
+import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Bitmap
@@ -37,8 +38,8 @@
     private val stack: BubbleStackView
 ) : BubbleViewProvider {
 
-    private lateinit var bitmap : Bitmap
-    private lateinit var dotPath : Path
+    private lateinit var bitmap: Bitmap
+    private lateinit var dotPath: Path
 
     private var bitmapSize = 0
     private var iconBitmapSize = 0
@@ -167,8 +168,8 @@
         return KEY
     }
 
-    override fun getDisplayId(): Int {
-        return expandedView.virtualDisplayId
+    override fun getTaskId(): Int {
+        return if (expandedView != null) expandedView.getTaskId() else INVALID_TASK_ID
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index e83954b..431719f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -27,7 +27,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
-import android.app.ActivityView;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -74,18 +73,13 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.systemui.Interpolators;
-import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.bubbles.animation.AnimatableScaleMatrix;
 import com.android.systemui.bubbles.animation.ExpandedAnimationController;
 import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
 import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.util.RelativeTouchListener;
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -121,6 +115,8 @@
     /** Duration of the flyout alpha animations. */
     private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
 
+    private static final int FADE_IN_DURATION = 320;
+
     /** Percent to darken the bubbles when they're in the dismiss target. */
     private static final float DARKEN_PERCENT = 0.3f;
 
@@ -302,7 +298,7 @@
                 pw.println("  expandedViewAlpha:  " + expandedView.getAlpha());
                 pw.println("  expandedViewTaskId: " + expandedView.getTaskId());
 
-                final ActivityView av = expandedView.getActivityView();
+                final View av = expandedView.getTaskView();
 
                 if (av != null) {
                     pw.println("  activityViewVis:    " + av.getVisibility());
@@ -323,8 +319,6 @@
     /** Callback to run when we want to unbubble the given notification's conversation. */
     private Consumer<String> mUnbubbleConversationCallback;
 
-    private SysUiState mSysUiState;
-
     private boolean mViewUpdatedRequested = false;
     private boolean mIsExpansionAnimating = false;
     private boolean mIsBubbleSwitchAnimating = false;
@@ -332,8 +326,6 @@
     /** The view to desaturate/darken when magneted to the dismiss target. */
     @Nullable private View mDesaturateAndDarkenTargetView;
 
-    private LayoutInflater mInflater;
-
     private Rect mTempRect = new Rect();
 
     private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
@@ -401,6 +393,11 @@
     public final Consumer<Boolean> mOnImeVisibilityChanged;
 
     /**
+     * Callback to run when the bubble expand status changes.
+     */
+    private final Consumer<Boolean> mOnBubbleExpandChanged;
+
+    /**
      * Callback to run to ask BubbleController to hide the current IME.
      */
     private final Runnable mHideCurrentInputMethodCallback;
@@ -660,7 +657,7 @@
                                     viewInitialX + dx, velX, velY) <= 0;
                     updateBubbleIcons();
                     logBubbleEvent(null /* no bubble associated with bubble stack move */,
-                            SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+                            FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
                 }
                 mDismissView.hide();
             }
@@ -742,16 +739,13 @@
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
-            SysUiState sysUiState,
             Runnable allBubblesAnimatedOutAction,
             Consumer<Boolean> onImeVisibilityChanged,
-            Runnable hideCurrentInputMethodCallback) {
+            Runnable hideCurrentInputMethodCallback,
+            Consumer<Boolean> onBubbleExpandChanged) {
         super(context);
 
         mBubbleData = data;
-        mInflater = LayoutInflater.from(context);
-
-        mSysUiState = sysUiState;
 
         Resources res = getResources();
         mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -865,21 +859,13 @@
 
         mOnImeVisibilityChanged = onImeVisibilityChanged;
         mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback;
+        mOnBubbleExpandChanged = onBubbleExpandChanged;
 
         setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
             onImeVisibilityChanged.accept(insets.getInsets(WindowInsets.Type.ime()).bottom > 0);
-
             if (!mIsExpanded || mIsExpansionAnimating) {
                 return view.onApplyWindowInsets(insets);
             }
-            mExpandedAnimationController.updateYPosition(
-                    // Update the insets after we're done translating otherwise position
-                    // calculation for them won't be correct.
-                    () -> {
-                        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-                            mExpandedBubble.getExpandedView().updateInsets(insets);
-                        }
-                    });
             return view.onApplyWindowInsets(insets);
         });
 
@@ -974,7 +960,7 @@
 
         animate()
                 .setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
-                .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION);
+                .setDuration(FADE_IN_DURATION);
     }
 
     /**
@@ -1066,7 +1052,7 @@
                         mBubbleData.setExpanded(false);
                         mContext.startActivityAsUser(intent, bubble.getUser());
                         logBubbleEvent(bubble,
-                                SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+                                FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
                     }
                 });
 
@@ -1082,8 +1068,7 @@
      * Whether the educational view should show for the expanded view "manage" menu.
      */
     private boolean shouldShowManageEdu() {
-        final boolean seen = Prefs.getBoolean(mContext,
-                Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false /* default */);
+        final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
         final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
                 && mExpandedBubble != null;
         if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
@@ -1107,8 +1092,7 @@
      * Whether education view should show for the collapsed stack.
      */
     private boolean shouldShowStackEdu() {
-        final boolean seen = Prefs.getBoolean(getContext(),
-                Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION, false /* default */);
+        final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
         final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
         if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
             Log.d(TAG, "Show stack edu: " + shouldShow);
@@ -1116,6 +1100,11 @@
         return shouldShow;
     }
 
+    private boolean getPrefBoolean(String key) {
+        return mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE)
+                .getBoolean(key, false /* default */);
+    }
+
     /**
      * @return true if education view for collapsed stack should show and was not showing before.
      */
@@ -1254,7 +1243,15 @@
 
         mTempRect.setEmpty();
         getTouchableRegion(mTempRect);
-        inoutInfo.touchableRegion.set(mTempRect);
+        if (mIsExpanded && mExpandedBubble != null
+                && mExpandedBubble.getExpandedView() != null
+                && mExpandedBubble.getExpandedView().getTaskView() != null) {
+            inoutInfo.touchableRegion.set(mTempRect);
+            mExpandedBubble.getExpandedView().getTaskView().getBoundsOnScreen(mTempRect);
+            inoutInfo.touchableRegion.op(mTempRect, Region.Op.DIFFERENCE);
+        } else {
+            inoutInfo.touchableRegion.set(mTempRect);
+        }
     }
 
     @Override
@@ -1442,13 +1439,6 @@
     }
 
     /**
-     * The {@link BadgedImageView} that is expanded, null if one does not exist.
-     */
-    View getExpandedBubbleView() {
-        return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
-    }
-
-    /**
      * The {@link Bubble} that is expanded, null if one does not exist.
      */
     @Nullable
@@ -1488,7 +1478,7 @@
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
         animateInFlyoutForBubble(bubble);
         requestUpdate();
-        logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
+        logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
     }
 
     // via BubbleData.Listener
@@ -1508,7 +1498,7 @@
                     bubble.cleanupViews();
                 }
                 updatePointerPosition();
-                logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
+                logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
                 return;
             }
         }
@@ -1526,7 +1516,7 @@
     void updateBubble(Bubble bubble) {
         animateInFlyoutForBubble(bubble);
         requestUpdate();
-        logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
+        logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
     }
 
     public void updateBubbleOrder(List<Bubble> bubbles) {
@@ -1563,7 +1553,7 @@
             return;
         }
 
-        if (bubbleToSelect.getKey() == BubbleOverflow.KEY) {
+        if (bubbleToSelect.getKey().equals(BubbleOverflow.KEY)) {
             mBubbleData.setShowingOverflow(true);
         } else {
             mBubbleData.setShowingOverflow(false);
@@ -1622,8 +1612,9 @@
                 requestUpdate();
 
                 logBubbleEvent(previouslySelected,
-                        SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
-                logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+                logBubbleEvent(bubbleToSelect,
+                        FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
                 notifyExpansionChanged(previouslySelected, false /* expanded */);
                 notifyExpansionChanged(bubbleToSelect, true /* expanded */);
             });
@@ -1653,30 +1644,21 @@
 
         hideCurrentInputMethod();
 
-        mSysUiState
-                .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
-                .commitUpdate(mContext.getDisplayId());
+        mOnBubbleExpandChanged.accept(shouldExpand);
 
         if (mIsExpanded) {
             animateCollapse();
-            logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+            logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
         } else {
             animateExpansion();
             // TODO: move next line to BubbleData
-            logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
-            logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
+            logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+            logBubbleEvent(mExpandedBubble,
+                    FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
         }
         notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
 
-    void showExpandedViewContents(int displayId) {
-        if (mExpandedBubble != null
-                && mExpandedBubble.getExpandedView() != null
-                && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
-            mExpandedBubble.setContentVisibility(true);
-        }
-    }
-
     /**
      * Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
      * not.
@@ -1711,7 +1693,6 @@
         updateOverflowVisibility();
         updatePointerPosition();
         mExpandedAnimationController.expandFromStack(() -> {
-            afterExpandedViewAnimation();
             if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
                 maybeShowManageEdu();
             }
@@ -1774,11 +1755,10 @@
                                 mExpandedViewContainerMatrix);
                     })
                     .withEndActions(() -> {
+                        afterExpandedViewAnimation();
                         if (mExpandedBubble != null
                                 && mExpandedBubble.getExpandedView() != null) {
                             mExpandedBubble.getExpandedView()
-                                    .setContentVisibility(true);
-                            mExpandedBubble.getExpandedView()
                                     .setSurfaceZOrderedOnTop(false);
                         }
                     })
@@ -1922,7 +1902,6 @@
                     })
                     .withEndActions(() -> {
                         if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-                            mExpandedBubble.getExpandedView().setContentVisibility(true);
                             mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
                         }
 
@@ -1958,13 +1937,6 @@
         }
     }
 
-    /** Return the BubbleView at the given index from the bubble container. */
-    public BadgedImageView getBubbleAt(int i) {
-        return getBubbleCount() > i
-                ? (BadgedImageView) mBubbleContainer.getChildAt(i)
-                : null;
-    }
-
     /** Moves the bubbles out of the way if they're going to be over the keyboard. */
     public void onImeVisibilityChanged(boolean visible, int height) {
         mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
@@ -1986,6 +1958,9 @@
                                 FLYOUT_IME_ANIMATION_SPRING_CONFIG)
                         .start();
             }
+        } else if (mIsExpanded && mExpandedBubble != null
+                && mExpandedBubble.getExpandedView() != null) {
+            mExpandedBubble.getExpandedView().setImeVisible(visible);
         }
     }
 
@@ -2196,11 +2171,7 @@
         return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop;
     }
 
-    /**
-     * Animates in the flyout for the given bubble, if available, and then hides it after some time.
-     */
-    @VisibleForTesting
-    void animateInFlyoutForBubble(Bubble bubble) {
+    private boolean shouldShowFlyout(Bubble bubble) {
         Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage();
         final BadgedImageView bubbleView = bubble.getIconView();
         if (flyoutMessage == null
@@ -2212,11 +2183,22 @@
                 || mIsGestureInProgress
                 || mBubbleToExpandAfterFlyoutCollapse != null
                 || bubbleView == null) {
-            if (bubbleView != null) {
+            if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
                 bubbleView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
             }
             // Skip the message if none exists, we're expanded or animating expansion, or we're
             // about to expand a bubble from the previous tapped flyout, or if bubble view is null.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Animates in the flyout for the given bubble, if available, and then hides it after some time.
+     */
+    @VisibleForTesting
+    void animateInFlyoutForBubble(Bubble bubble) {
+        if (!shouldShowFlyout(bubble)) {
             return;
         }
 
@@ -2234,25 +2216,22 @@
             }
 
             // Stop suppressing the dot now that the flyout has morphed into the dot.
-            bubbleView.removeDotSuppressionFlag(
+            bubble.getIconView().removeDotSuppressionFlag(
                     BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
 
-            mFlyout.setVisibility(INVISIBLE);
-
             // Hide the stack after a delay, if needed.
             updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
         };
-        mFlyout.setVisibility(INVISIBLE);
 
         // Suppress the dot when we are animating the flyout.
-        bubbleView.addDotSuppressionFlag(
+        bubble.getIconView().addDotSuppressionFlag(
                 BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
 
         // Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
         post(() -> {
             // An auto-expanding bubble could have been posted during the time it takes to
             // layout.
-            if (isExpanded()) {
+            if (isExpanded() || bubble.getIconView() == null) {
                 return;
             }
             final Runnable expandFlyoutAfterDelay = () -> {
@@ -2269,23 +2248,26 @@
                 mFlyout.postDelayed(mAnimateInFlyout, 200);
             };
 
-            if (bubble.getIconView() == null) {
-                return;
-            }
 
-            mFlyout.setupFlyoutStartingAsDot(flyoutMessage,
-                    mStackAnimationController.getStackPosition(), getWidth(),
-                    mStackAnimationController.isStackOnLeftSide(),
-                    bubble.getIconView().getDotColor() /* dotColor */,
-                    expandFlyoutAfterDelay /* onLayoutComplete */,
-                    mAfterFlyoutHidden,
-                    bubble.getIconView().getDotCenter(),
-                    !bubble.showDot());
+            if (mFlyout.getVisibility() == View.VISIBLE) {
+                mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(),
+                        mStackAnimationController.getStackPosition().y);
+            } else {
+                mFlyout.setVisibility(INVISIBLE);
+                mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(),
+                        mStackAnimationController.getStackPosition(), getWidth(),
+                        mStackAnimationController.isStackOnLeftSide(),
+                        bubble.getIconView().getDotColor() /* dotColor */,
+                        expandFlyoutAfterDelay /* onLayoutComplete */,
+                        mAfterFlyoutHidden,
+                        bubble.getIconView().getDotCenter(),
+                        !bubble.showDot());
+            }
             mFlyout.bringToFront();
         });
         mFlyout.removeCallbacks(mHideFlyout);
         mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
-        logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+        logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
     }
 
     /** Hide the flyout immediately and cancel any pending hide runnables. */
@@ -2393,7 +2375,6 @@
         final float targetY = mTempRect.bottom - mManageMenu.getHeight();
 
         final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f;
-
         if (show) {
             mManageMenu.setScaleX(0.5f);
             mManageMenu.setScaleY(0.5f);
@@ -2410,6 +2391,8 @@
                     .withEndActions(() -> {
                         View child = mManageMenu.getChildAt(0);
                         child.requestAccessibilityFocus();
+                        // Update the AV's obscured touchable region for the new visibility state.
+                        mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
                     })
                     .start();
 
@@ -2421,12 +2404,15 @@
                     .spring(DynamicAnimation.SCALE_Y, 0.5f)
                     .spring(DynamicAnimation.TRANSLATION_X, targetX - xOffsetForAnimation)
                     .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f)
-                    .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
+                    .withEndActions(() -> {
+                        mManageMenu.setVisibility(View.INVISIBLE);
+                        if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+                            // Update the AV's obscured touchable region for the new state.
+                            mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+                        }
+                    })
                     .start();
         }
-
-        // Update the AV's obscured touchable region for the new menu visibility state.
-        mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
     }
 
     private void updateExpandedBubble() {
@@ -2446,7 +2432,6 @@
             mExpandedViewContainer.setAlpha(0f);
             mExpandedViewContainer.addView(bev);
             bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
-            bev.populateExpandedView();
 
             if (!mIsExpansionAnimating) {
                 mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
@@ -2503,7 +2488,7 @@
         mAnimatingOutSurfaceContainer.setTranslationY(0);
 
         final int[] activityViewLocation =
-                mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen();
+                mExpandedBubble.getExpandedView().getTaskViewLocationOnScreen();
         final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen();
 
         // Translate the surface to overlap the real ActivityView.
@@ -2679,17 +2664,6 @@
                 getNormalizedYPosition());
     }
 
-    /**
-     * Called when a back gesture should be directed to the Bubbles stack. When expanded,
-     * a back key down/up event pair is forwarded to the bubble Activity.
-     */
-    boolean performBackPressIfNeeded() {
-        if (!isExpanded() || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
-            return false;
-        }
-        return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
-    }
-
     /** For debugging only */
     List<Bubble> getBubblesOnScreen() {
         List<Bubble> bubbles = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
deleted file mode 100644
index 06205c5..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
+++ /dev/null
@@ -1,181 +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.systemui.bubbles;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.window.TaskEmbedder;
-import android.window.TaskOrganizerTaskEmbedder;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.IWindow;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import dalvik.system.CloseGuard;
-
-
-public class BubbleTaskView extends SurfaceView implements SurfaceHolder.Callback,
-        TaskEmbedder.Host {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleTaskView" : TAG_BUBBLES;
-
-    private final CloseGuard mGuard = CloseGuard.get();
-    private boolean mOpened; // Protected by mGuard.
-
-    private TaskEmbedder mTaskEmbedder;
-    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
-    private final Rect mTmpRect = new Rect();
-
-    public BubbleTaskView(Context context) {
-        super(context);
-
-        mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
-        setUseAlpha();
-        getHolder().addCallback(this);
-
-        mOpened = true;
-        mGuard.open("release");
-    }
-
-    public void setCallback(TaskEmbedder.Listener callback) {
-        if (callback == null) {
-            mTaskEmbedder.setListener(null);
-            return;
-        }
-        mTaskEmbedder.setListener(callback);
-    }
-
-    public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
-            @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
-        mTaskEmbedder.startShortcutActivity(shortcut, options, sourceBounds);
-    }
-
-    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
-            @NonNull ActivityOptions options) {
-        mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options);
-    }
-
-    public void onLocationChanged() {
-        mTaskEmbedder.notifyBoundsChanged();
-    }
-
-    @Override
-    public Rect getScreenBounds() {
-        getBoundsOnScreen(mTmpRect);
-        return mTmpRect;
-    }
-
-    @Override
-    public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) {
-        setResizeBackgroundColor(bgColor);
-    }
-
-    @Override
-    public Region getTapExcludeRegion() {
-        // Not used
-        return null;
-    }
-
-    @Override
-    public Matrix getScreenToTaskMatrix() {
-        // Not used
-        return null;
-    }
-
-    @Override
-    public IWindow getWindow() {
-        // Not used
-        return null;
-    }
-
-    @Override
-    public Point getPositionInWindow() {
-        // Not used
-        return null;
-    }
-
-    @Override
-    public boolean canReceivePointerEvents() {
-        // Not used
-        return false;
-    }
-
-    public void release() {
-        if (!mTaskEmbedder.isInitialized()) {
-            throw new IllegalStateException(
-                    "Trying to release container that is not initialized.");
-        }
-        performRelease();
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (mGuard != null) {
-                mGuard.warnIfOpen();
-                performRelease();
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private void performRelease() {
-        if (!mOpened) {
-            return;
-        }
-        getHolder().removeCallback(this);
-        mTaskEmbedder.release();
-        mTaskEmbedder.setListener(null);
-
-        mGuard.close();
-        mOpened = false;
-    }
-
-    @Override
-    public void surfaceCreated(SurfaceHolder holder) {
-        if (!mTaskEmbedder.isInitialized()) {
-            mTaskEmbedder.initialize(getSurfaceControl());
-        } else {
-            mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(),
-                    getSurfaceControl()).apply();
-        }
-        mTaskEmbedder.start();
-    }
-
-    @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        mTaskEmbedder.resizeTask(width, height);
-        mTaskEmbedder.notifyBoundsChanged();
-    }
-
-    @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {
-        mTaskEmbedder.stop();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index 916ad18..5cc24ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -48,5 +48,5 @@
 
     boolean showDot();
 
-    int getDisplayId();
+    int getTaskId();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
index 34828b3..39c750d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
@@ -17,8 +17,6 @@
 package com.android.systemui.bubbles;
 
 import android.annotation.NonNull;
-import android.content.Context;
-import android.view.Display;
 
 import androidx.annotation.MainThread;
 
@@ -57,12 +55,6 @@
      */
     ScrimView getScrimForBubble();
 
-    /**
-     * @return the display id of the expanded view, if the stack is expanded and not occluded by the
-     * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
-     */
-    int getExpandedDisplayId(Context context);
-
     /** @return Bubbles for updating overflow. */
     List<Bubble> getOverflowBubbles();
 
@@ -77,13 +69,6 @@
      */
     void expandStackAndSelectBubble(NotificationEntry entry);
 
-
-    /**
-     * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
-     * is forwarded a back key down/up pair.
-     */
-    void performBackPressIfNeeded();
-
     /** Promote the provided bubbles when overflow view. */
     void promoteBubbleFromOverflow(Bubble bubble);
 
@@ -142,4 +127,7 @@
 
     /** Set a listener to be notified of when overflow view update. */
     void setOverflowListener(BubbleData.Listener listener);
+
+    /** The task listener for events in bubble tasks. **/
+    MultiWindowTaskListener getTaskManager();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
index 26a9773..3db07c2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
@@ -25,8 +25,6 @@
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.Interpolators
-import com.android.systemui.Prefs
-import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION
 import com.android.systemui.R
 
 /**
@@ -38,8 +36,8 @@
     private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleManageEducationView"
         else BubbleDebugConfig.TAG_BUBBLES
 
-    private val ANIMATE_DURATION : Long = 200
-    private val ANIMATE_DURATION_SHORT : Long = 40
+    private val ANIMATE_DURATION: Long = 200
+    private val ANIMATE_DURATION_SHORT: Long = 40
 
     private val manageView by lazy { findViewById<View>(R.id.manage_education_view) }
     private val manageButton by lazy { findViewById<Button>(R.id.manage) }
@@ -50,7 +48,7 @@
     private var isHiding = false
 
     init {
-        LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this);
+        LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
         visibility = View.GONE
         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
 
@@ -95,7 +93,7 @@
      *
      * @param show whether the user education view should show or not.
      */
-    fun show(expandedView: BubbleExpandedView, rect : Rect) {
+    fun show(expandedView: BubbleExpandedView, rect: Rect) {
         if (visibility == VISIBLE) return
 
         alpha = 0f
@@ -136,10 +134,13 @@
             .withEndAction {
                 isHiding = false
                 visibility = GONE
-            };
+            }
     }
 
     private fun setShouldShow(shouldShow: Boolean) {
-        Prefs.putBoolean(context, HAS_SEEN_BUBBLES_MANAGE_EDUCATION, !shouldShow)
+        context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+                .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
     }
-}
\ No newline at end of file
+}
+
+const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
new file mode 100644
index 0000000..dff8bec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
@@ -0,0 +1,177 @@
+/*
+ * 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.bubbles;
+
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/**
+ * Manages tasks that are displayed in multi-window (e.g. bubbles). These are displayed in a
+ * {@link TaskView}.
+ *
+ * This class listens on {@link TaskOrganizer} callbacks for events. Once visible, these tasks will
+ * intercept back press events.
+ *
+ * @see android.app.WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW
+ * @see TaskView
+ */
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class MultiWindowTaskListener implements ShellTaskOrganizer.TaskListener {
+    private static final String TAG = MultiWindowTaskListener.class.getSimpleName();
+
+    private static final boolean DEBUG = false;
+
+    //TODO(b/170153209): Have shell listener allow per task registration and remove this.
+    public interface Listener {
+        void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash);
+        void onTaskVanished(RunningTaskInfo taskInfo);
+        void onTaskInfoChanged(RunningTaskInfo taskInfo);
+        void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo);
+    }
+
+    private static class TaskData {
+        final RunningTaskInfo taskInfo;
+        final Listener listener;
+
+        TaskData(RunningTaskInfo info, Listener l) {
+            taskInfo = info;
+            listener = l;
+        }
+    }
+
+    private final Handler mHandler;
+    private final ShellTaskOrganizer mTaskOrganizer;
+    private final ArrayMap<WindowContainerToken, TaskData> mTasks = new ArrayMap<>();
+
+    private MultiWindowTaskListener.Listener mPendingListener;
+
+    /**
+     * Create a listener for tasks in multi-window mode.
+     */
+    public MultiWindowTaskListener(Handler handler, ShellTaskOrganizer organizer) {
+        mHandler = handler;
+        mTaskOrganizer = organizer;
+        mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_MULTI_WINDOW);
+    }
+
+    /**
+     * @return the task organizer that is listened to.
+     */
+    public TaskOrganizer getTaskOrganizer() {
+        return mTaskOrganizer;
+    }
+
+    // TODO(b/129067201): track launches for bubbles
+    // Once we have key in ActivityOptions, match listeners via that key
+    public void setPendingListener(Listener listener) {
+        mPendingListener = listener;
+    }
+
+    /**
+     * Removes a task listener previously registered when starting a new activity.
+     */
+    public void removeListener(Listener listener) {
+        if (DEBUG) {
+            Log.d(TAG, "removeListener: listener=" + listener);
+        }
+        if (mPendingListener == listener) {
+            mPendingListener = null;
+        }
+        for (int i = 0; i < mTasks.size(); i++) {
+            if (mTasks.valueAt(i).listener == listener) {
+                mTasks.removeAt(i);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+        if (DEBUG) {
+            Log.d(TAG, "onTaskAppeared: taskInfo=" + taskInfo
+                    + " mPendingListener=" + mPendingListener);
+        }
+        if (mPendingListener == null) {
+            // If there is no pending listener, then we are either receiving this task as a part of
+            // registering the task org again (ie. after SysUI dies) or the previously started
+            // task is no longer needed (ie. bubble is closed soon after), for now, just finish the
+            // associated task
+            try {
+                ActivityTaskManager.getService().removeTask(taskInfo.taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to remove taskId " + taskInfo.taskId);
+            }
+            return;
+        }
+
+        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true);
+
+        final TaskData data = new TaskData(taskInfo, mPendingListener);
+        mTasks.put(taskInfo.token, data);
+        mHandler.post(() -> data.listener.onTaskAppeared(taskInfo, leash));
+        mPendingListener = null;
+    }
+
+    @Override
+    public void onTaskVanished(RunningTaskInfo taskInfo) {
+        final TaskData data = mTasks.remove(taskInfo.token);
+        if (data == null) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "onTaskVanished: taskInfo=" + taskInfo + " listener=" + data.listener);
+        }
+        mHandler.post(() -> data.listener.onTaskVanished(taskInfo));
+    }
+
+    @Override
+    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+        final TaskData data = mTasks.get(taskInfo.token);
+        if (data == null) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
+        }
+        mHandler.post(() -> data.listener.onTaskInfoChanged(taskInfo));
+    }
+
+    @Override
+    public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+        final TaskData data = mTasks.get(taskInfo.token);
+        if (data == null) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
+        }
+        mHandler.post(() -> data.listener.onBackPressedOnTaskRoot(taskInfo));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
rename to packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
index 8880df9..b1291a5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util
+package com.android.systemui.bubbles
 
 import android.graphics.PointF
 import android.os.Handler
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
index 3e4c729..216df2e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
@@ -24,21 +24,19 @@
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.Interpolators
-import com.android.systemui.Prefs
-import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION
 import com.android.systemui.R
 
 /**
  * User education view to highlight the collapsed stack of bubbles.
  * Shown only the first time a user taps the stack.
  */
-class StackEducationView constructor(context: Context) : LinearLayout(context){
+class StackEducationView constructor(context: Context) : LinearLayout(context) {
 
     private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
         else BubbleDebugConfig.TAG_BUBBLES
 
-    private val ANIMATE_DURATION : Long = 200
-    private val ANIMATE_DURATION_SHORT : Long = 40
+    private val ANIMATE_DURATION: Long = 200
+    private val ANIMATE_DURATION_SHORT: Long = 40
 
     private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
     private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
@@ -47,7 +45,7 @@
     private var isHiding = false
 
     init {
-        LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this);
+        LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
 
         visibility = View.GONE
         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
@@ -93,7 +91,7 @@
      *
      * @return true if user education was shown, false otherwise.
      */
-    fun show(stackPosition: PointF) : Boolean{
+    fun show(stackPosition: PointF): Boolean {
         if (visibility == VISIBLE) return false
 
         setAlpha(0f)
@@ -129,6 +127,9 @@
     }
 
     private fun setShouldShow(shouldShow: Boolean) {
-        Prefs.putBoolean(context, HAS_SEEN_BUBBLES_EDUCATION, !shouldShow)
+        context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+                .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply()
     }
-}
\ No newline at end of file
+}
+
+const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
new file mode 100644
index 0000000..524fa42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
@@ -0,0 +1,308 @@
+/*
+ * 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.bubbles;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import dalvik.system.CloseGuard;
+
+/**
+ * View that can display a task.
+ */
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
+        MultiWindowTaskListener.Listener {
+
+    public interface Listener {
+        /** Called when the container is ready for launching activities. */
+        default void onInitialized() {}
+
+        /** Called when the container can no longer launch activities. */
+        default void onReleased() {}
+
+        /** Called when a task is created inside the container. */
+        default void onTaskCreated(int taskId, ComponentName name) {}
+
+        /** Called when a task visibility changes. */
+        default void onTaskVisibilityChanged(int taskId, boolean visible) {}
+
+        /** Called when a task is about to be removed from the stack inside the container. */
+        default void onTaskRemovalStarted(int taskId) {}
+
+        /** Called when a task is created inside the container. */
+        default void onBackPressedOnTaskRoot(int taskId) {}
+    }
+
+    private final CloseGuard mGuard = CloseGuard.get();
+
+    private final MultiWindowTaskListener mMultiWindowTaskListener;
+
+    private ActivityManager.RunningTaskInfo mTaskInfo;
+    private WindowContainerToken mTaskToken;
+    private SurfaceControl mTaskLeash;
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    private boolean mSurfaceCreated;
+    private boolean mIsInitialized;
+    private Listener mListener;
+
+    private final Rect mTmpRect = new Rect();
+    private final Rect mTmpRootRect = new Rect();
+
+    public TaskView(Context context, MultiWindowTaskListener taskListener) {
+        super(context, null, 0, 0, true /* disableBackgroundLayer */);
+
+        mMultiWindowTaskListener = taskListener;
+        setUseAlpha();
+        getHolder().addCallback(this);
+        mGuard.open("release");
+    }
+
+    /**
+     * Only one listener may be set on the view, throws an exception otherwise.
+     */
+    public void setListener(Listener listener) {
+        if (mListener != null) {
+            throw new IllegalStateException(
+                    "Trying to set a listener when one has already been set");
+        }
+        mListener = listener;
+    }
+
+    /**
+     * Launch an activity represented by {@link ShortcutInfo}.
+     * <p>The owner of this container must be allowed to access the shortcut information,
+     * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+     *
+     * @param shortcut the shortcut used to launch the activity.
+     * @param options options for the activity.
+     * @param sourceBounds the rect containing the source bounds of the clicked icon to open
+     *                     this shortcut.
+     */
+    public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
+            @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
+        mMultiWindowTaskListener.setPendingListener(this);
+        prepareActivityOptions(options);
+        LauncherApps service = mContext.getSystemService(LauncherApps.class);
+        try {
+            service.startShortcut(shortcut, sourceBounds, options.toBundle());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Launch a new activity.
+     *
+     * @param pendingIntent Intent used to launch an activity.
+     * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+     * @param options options for the activity.
+     */
+    public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+            @NonNull ActivityOptions options) {
+        mMultiWindowTaskListener.setPendingListener(this);
+        prepareActivityOptions(options);
+        try {
+            pendingIntent.send(mContext, 0 /* code */, fillInIntent,
+                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
+                    options.toBundle());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void prepareActivityOptions(ActivityOptions options) {
+        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        options.setTaskAlwaysOnTop(true);
+    }
+
+    /**
+     * Call when view position or size has changed. Do not call when animating.
+     */
+    public void onLocationChanged() {
+        if (mTaskToken == null) {
+            return;
+        }
+        // Update based on the screen bounds
+        getBoundsOnScreen(mTmpRect);
+        getRootView().getBoundsOnScreen(mTmpRootRect);
+        if (!mTmpRootRect.contains(mTmpRect)) {
+            mTmpRect.offsetTo(0, 0);
+        }
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(mTaskToken, mTmpRect);
+        // TODO(b/151449487): Enable synchronization
+        mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
+    }
+
+    /**
+     * Release this container if it is initialized.
+     */
+    public void release() {
+        performRelease();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mGuard != null) {
+                mGuard.warnIfOpen();
+                performRelease();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void performRelease() {
+        getHolder().removeCallback(this);
+        mMultiWindowTaskListener.removeListener(this);
+        resetTaskInfo();
+        mGuard.close();
+        if (mListener != null && mIsInitialized) {
+            mListener.onReleased();
+            mIsInitialized = false;
+        }
+    }
+
+    private void resetTaskInfo() {
+        mTaskInfo = null;
+        mTaskToken = null;
+        mTaskLeash = null;
+    }
+
+    private void updateTaskVisibility() {
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+        mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
+        // TODO(b/151449487): Only call callback once we enable synchronization
+        if (mListener != null) {
+            mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
+        }
+    }
+
+    @Override
+    public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
+            SurfaceControl leash) {
+        mTaskInfo = taskInfo;
+        mTaskToken = taskInfo.token;
+        mTaskLeash = leash;
+
+        if (mSurfaceCreated) {
+            // Surface is ready, so just reparent the task to this surface control
+            mTransaction.reparent(mTaskLeash, getSurfaceControl())
+                    .show(mTaskLeash)
+                    .apply();
+        } else {
+            // The surface has already been destroyed before the task has appeared, so go ahead and
+            // hide the task entirely
+            updateTaskVisibility();
+        }
+
+        // TODO: Synchronize show with the resize
+        onLocationChanged();
+        setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+
+        if (mListener != null) {
+            mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
+        }
+    }
+
+    @Override
+    public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
+            if (mListener != null) {
+                mListener.onTaskRemovalStarted(taskInfo.taskId);
+            }
+
+            // Unparent the task when this surface is destroyed
+            mTransaction.reparent(mTaskLeash, null).apply();
+            resetTaskInfo();
+        }
+    }
+
+    @Override
+    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        mTaskInfo.taskDescription = taskInfo.taskDescription;
+        setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+    }
+
+    @Override
+    public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+        if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
+            if (mListener != null) {
+                mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
+            }
+        }
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        mSurfaceCreated = true;
+        if (mListener != null && !mIsInitialized) {
+            mIsInitialized = true;
+            mListener.onInitialized();
+        }
+        if (mTaskToken == null) {
+            // Nothing to update, task is not yet available
+            return;
+        }
+        // Reparent the task when this surface is created
+        mTransaction.reparent(mTaskLeash, getSurfaceControl())
+                .show(mTaskLeash)
+                .apply();
+        updateTaskVisibility();
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        if (mTaskToken == null) {
+            return;
+        }
+        onLocationChanged();
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        mSurfaceCreated = false;
+        if (mTaskToken == null) {
+            // Nothing to update, task is not yet available
+            return;
+        }
+
+        // Unparent the task when this surface is destroyed
+        mTransaction.reparent(mTaskLeash, null).apply();
+        updateTaskVisibility();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 9f88ee5..7fdc019 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -58,6 +58,9 @@
     /** Duration of the expand/collapse target path animation. */
     public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
 
+    /** Damping ratio for expand/collapse spring. */
+    private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
+
     /** Stiffness for the expand/collapse path-following animation. */
     private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
 
@@ -271,16 +274,14 @@
                 // Then, draw a line across the screen to the bubble's resting position.
                 path.lineTo(getBubbleLeft(index), expandedY);
             } else {
-                final float sideMultiplier =
-                        mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
-                final float stackedX = mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx);
+                final float stackedX = mCollapsePoint.x;
 
                 // If we're collapsing, draw a line from the bubble's current position to the side
                 // of the screen where the bubble will be stacked.
                 path.lineTo(stackedX, expandedY);
 
                 // Then, draw a line down to the stack position.
-                path.lineTo(stackedX, mCollapsePoint.y);
+                path.lineTo(stackedX, mCollapsePoint.y + index * mStackOffsetPx);
             }
 
             // The lead bubble should be the bubble with the longest distance to travel when we're
@@ -510,7 +511,7 @@
     @Override
     SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
         return new SpringForce()
-                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                .setDampingRatio(DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
                 .setStiffness(SpringForce.STIFFNESS_LOW);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 4c902b9..1205124 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -744,15 +744,13 @@
 
     @Override
     float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
-        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+        if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
             // If we're in the dismiss target, have the bubbles pile on top of each other with no
             // offset.
             if (isStackStuckToTarget()) {
                 return 0f;
             } else {
-                // Offset to the left if we're on the left, or the right otherwise.
-                return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
-                        ? -mStackOffset : mStackOffset;
+                return mStackOffset;
             }
         } else {
             return 0f;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 00ae3a3..6b5f237 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -19,13 +19,15 @@
 import android.app.INotificationManager;
 import android.content.Context;
 import android.content.pm.LauncherApps;
+import android.os.Handler;
 import android.view.WindowManager;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.BubbleDataRepository;
 import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -39,6 +41,7 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
@@ -68,13 +71,15 @@
             FeatureFlags featureFlags,
             DumpManager dumpManager,
             FloatingContentCoordinator floatingContentCoordinator,
-            BubbleDataRepository bubbleDataRepository,
             SysUiState sysUiState,
             INotificationManager notifManager,
             IStatusBarService statusBarService,
             WindowManager windowManager,
             WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps) {
+            LauncherApps launcherApps,
+            UiEventLogger uiEventLogger,
+            @Main Handler mainHandler,
+            ShellTaskOrganizer organizer) {
         return BubbleController.create(
                 context,
                 notificationShadeWindowController,
@@ -91,12 +96,14 @@
                 featureFlags,
                 dumpManager,
                 floatingContentCoordinator,
-                bubbleDataRepository,
                 sysUiState,
                 notifManager,
                 statusBarService,
                 windowManager,
                 windowManagerShellWrapper,
-                launcherApps);
+                launcherApps,
+                uiEventLogger,
+                mainHandler,
+                organizer);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
index f447965..ce0786d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
@@ -18,16 +18,11 @@
 import android.content.Context
 import android.util.AtomicFile
 import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
 import java.io.File
 import java.io.FileOutputStream
 import java.io.IOException
-import javax.inject.Inject
 
-@SysUISingleton
-class BubblePersistentRepository @Inject constructor(
-    context: Context
-) {
+class BubblePersistentRepository(context: Context) {
 
     private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir,
             "overflow_bubbles.xml"), "overflow-bubbles")
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
index c6d5732..e0a7c78 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
@@ -19,8 +19,6 @@
 import android.os.UserHandle
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.bubbles.ShortcutKey
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
 
 private const val CAPACITY = 16
 
@@ -28,10 +26,7 @@
  * BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory
  * manipulation.
  */
-@SysUISingleton
-class BubbleVolatileRepository @Inject constructor(
-    private val launcherApps: LauncherApps
-) {
+class BubbleVolatileRepository(private val launcherApps: LauncherApps) {
     /**
      * An ordered set of bubbles based on their natural ordering.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index e303754..cb90b61 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -66,6 +66,7 @@
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -263,6 +264,13 @@
         return ActivityManagerWrapper.getInstance();
     }
 
+    /** */
+    @Provides
+    @SysUISingleton
+    public TaskStackChangeListeners provideTaskStackChangeListeners() {
+        return TaskStackChangeListeners.getInstance();
+    }
+
     /** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 6154a4e..f470a6b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -123,6 +123,14 @@
     }
 
     /**
+     * Appends dozing event to the logs
+     * @param suppressed true if dozing is suppressed
+     */
+    public void traceDozingSuppressed(boolean suppressed) {
+        mLogger.logDozingSuppressed(suppressed);
+    }
+
+    /**
      * Appends fling event to the logs
      */
     public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 46cec95..0c9e143 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -64,6 +64,14 @@
         })
     }
 
+    fun logDozingSuppressed(isDozingSuppressed: Boolean) {
+        buffer.log(TAG, INFO, {
+            bool1 = isDozingSuppressed
+        }, {
+            "DozingSuppressed=$bool1"
+        })
+    }
+
     fun logFling(
         expand: Boolean,
         aboveThreshold: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 1e0460b..befb648 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -413,6 +413,7 @@
         pw.print(" state="); pw.println(mState);
         pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
         pw.print(" wakeLock="); pw.println(mWakeLock);
+        pw.print(" isDozeSuppressed="); pw.println(mDozeHost.isDozeSuppressed());
         pw.println("Parts:");
         for (Part p : mParts) {
             p.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
index 7a8b816..435859a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
@@ -24,7 +24,7 @@
 import javax.inject.Scope;
 
 /**
- * Scope annotation for singleton items within the StatusBarComponent.
+ * Scope annotation for singleton items within the DozeComponent.
  */
 @Documented
 @Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index c281ece..7585110 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -30,8 +30,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 public class WorkLockActivityController {
     private static final String TAG = WorkLockActivityController.class.getSimpleName();
@@ -40,16 +40,16 @@
     private final IActivityTaskManager mIatm;
 
     public WorkLockActivityController(Context context) {
-        this(context, ActivityManagerWrapper.getInstance(), ActivityTaskManager.getService());
+        this(context, TaskStackChangeListeners.getInstance(), ActivityTaskManager.getService());
     }
 
     @VisibleForTesting
     WorkLockActivityController(
-            Context context, ActivityManagerWrapper am, IActivityTaskManager iAtm) {
+            Context context, TaskStackChangeListeners tscl, IActivityTaskManager iAtm) {
         mContext = context;
         mIatm = iAtm;
 
-        am.registerTaskStackListener(mLockListener);
+        tscl.registerTaskStackListener(mLockListener);
     }
 
     private void startWorkChallengeInTask(int taskId, int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f6571ef..5b096ea 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -42,10 +42,10 @@
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.settingslib.Utils;
-import com.android.settingslib.media.MediaOutputSliceConstants;
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.util.animation.TransitionLayout;
@@ -93,7 +93,7 @@
     private int mAlbumArtRadius;
     // This will provide the corners for the album art.
     private final ViewOutlineProvider mViewOutlineProvider;
-
+    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     /**
      * Initialize a new control panel
      * @param context
@@ -104,7 +104,8 @@
     public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
             ActivityStarter activityStarter, MediaViewController mediaViewController,
             SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
-            KeyguardDismissUtil keyguardDismissUtil) {
+            KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
+            mediaOutputDialogFactory) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
@@ -112,6 +113,7 @@
         mMediaViewController = mediaViewController;
         mMediaDataManagerLazy = lazyMediaDataManager;
         mKeyguardDismissUtil = keyguardDismissUtil;
+        mMediaOutputDialogFactory = mediaOutputDialogFactory;
         loadDimens();
 
         mViewOutlineProvider = new ViewOutlineProvider() {
@@ -273,13 +275,7 @@
         setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
         setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
         mViewHolder.getSeamless().setOnClickListener(v -> {
-            final Intent intent = new Intent()
-                    .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
-                    .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
-                            data.getPackageName())
-                    .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
-            mActivityStarter.startActivity(intent, false, true /* dismissShade */,
-                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            mMediaOutputDialogFactory.create(data.getPackageName(), true);
         });
 
         ImageView iconView = mViewHolder.getSeamlessIcon();
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 11551ac..666a603 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -23,7 +23,6 @@
 import android.widget.ImageView
 import android.widget.SeekBar
 import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.R
 import com.android.systemui.util.animation.TransitionLayout
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 3b82999..caef536 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -49,7 +49,7 @@
  * Base dialog for media output UI
  */
 public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
-        MediaOutputController.Callback {
+        MediaOutputController.Callback, Window.Callback {
 
     private static final String TAG = "MediaOutputDialog";
 
@@ -210,4 +210,12 @@
     public void dismissDialog() {
         dismiss();
     }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (!hasFocus && isShowing()) {
+            dismiss();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 6cbf065..df9e7a4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -47,6 +47,7 @@
 import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.RotationLockController;
 
@@ -156,7 +157,7 @@
             throw e.rethrowFromSystemServer();
         }
 
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
     }
 
     void unregisterListeners() {
@@ -171,7 +172,7 @@
             throw e.rethrowFromSystemServer();
         }
 
-        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
     }
 
     void addRotationCallback(Consumer<Integer> watcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index af851a7..4efe4d8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -58,7 +58,6 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -426,12 +425,6 @@
         if (getDisplay() != null) {
             displayId = getDisplay().getDisplayId();
         }
-        // Bubbles will give us a valid display id if it should get the back event
-        Bubbles Bubbles = Dependency.get(Bubbles.class);
-        int bubbleDisplayId = Bubbles.getExpandedDisplayId(mContext);
-        if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
-            displayId = bubbleDisplayId;
-        }
         if (displayId != INVALID_DISPLAY) {
             ev.setDisplayId(displayId);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index fc9c3d9..18cc746 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,8 +15,6 @@
  */
 package com.android.systemui.navigationbar.gestural;
 
-import static android.view.Display.INVALID_DISPLAY;
-
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -57,7 +55,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -71,6 +68,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
@@ -387,7 +385,7 @@
             mGestureNavigationSettingsObserver.unregister();
             mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
             mPluginManager.removePluginListener(this);
-            ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+            TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
             DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
 
             try {
@@ -403,7 +401,7 @@
             updateDisplaySize();
             mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
                     mContext.getMainThreadHandler());
-            ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+            TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
                     runnable -> (mContext.getMainThreadHandler()).post(runnable),
                     mOnPropertiesChangedListener);
@@ -733,13 +731,7 @@
                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                 InputDevice.SOURCE_KEYBOARD);
 
-        // Bubbles will give us a valid display id if it should get the back event
-        final int bubbleDisplayId = Dependency.get(Bubbles.class).getExpandedDisplayId(mContext);
-        if (bubbleDisplayId != INVALID_DISPLAY) {
-            ev.setDisplayId(bubbleDisplayId);
-        } else {
-            ev.setDisplayId(mContext.getDisplay().getDisplayId());
-        }
+        ev.setDisplayId(mContext.getDisplay().getDisplayId());
         InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 284f41a..fbbda5f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -334,6 +336,7 @@
                 .getDimension(R.dimen.navigation_edge_action_drag_threshold);
         setVisibility(GONE);
 
+        boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
                     @Override
@@ -345,8 +348,14 @@
                     public Rect getSampledRegion(View sampledView) {
                         return mSamplingRect;
                     }
+
+                    @Override
+                    public boolean isSamplingEnabled() {
+                        return isPrimaryDisplay;
+                    }
                 });
         mRegionSamplingHelper.setWindowVisible(true);
+        mShowProtection = !isPrimaryDisplay;
     }
 
     @Override
@@ -366,11 +375,6 @@
         updateIsDark(animate);
     }
 
-    private void setShowProtection(boolean showProtection) {
-        mShowProtection = showProtection;
-        invalidate();
-    }
-
     @Override
     public void setIsLeftPanel(boolean isLeftPanel) {
         mIsLeftPanel = isLeftPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 9c5a3de..ccf2598 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -67,6 +67,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.io.PrintWriter;
 import java.util.Collections;
@@ -365,7 +366,7 @@
             mOverviewProxyListenerRegistered = true;
         }
         if (!mTaskListenerRegistered) {
-            ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
+            TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskListener);
             mTaskListenerRegistered = true;
         }
     }
@@ -377,7 +378,7 @@
             mOverviewProxyListenerRegistered = false;
         }
         if (mTaskListenerRegistered) {
-            ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
+            TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskListener);
             mTaskListenerRegistered = false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index fe32227..aaa335c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -321,8 +321,17 @@
             Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
             Consumer<Uri> finisher, Runnable onComplete) {
         // TODO: use task Id, userId, topComponent for smart handler
-
         mOnCompleteRunnable = onComplete;
+
+        if (screenshot == null) {
+            Log.e(TAG, "Got null bitmap from screenshot message");
+            mNotificationsController.notifyScreenshotError(
+                    R.string.screenshot_failed_to_capture_text);
+            finisher.accept(null);
+            mOnCompleteRunnable.run();
+            return;
+        }
+
         if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
             saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
         } else {
@@ -569,7 +578,17 @@
                         .build();
         final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                 SurfaceControl.captureDisplay(captureArgs);
-        final Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+        Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+
+        if (screenshot == null) {
+            Log.e(TAG, "Screenshot bitmap was null");
+            mNotificationsController.notifyScreenshotError(
+                    R.string.screenshot_failed_to_capture_text);
+            finisher.accept(null);
+            mOnCompleteRunnable.run();
+            return;
+        }
+
         saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
     }
 
@@ -593,14 +612,6 @@
 
         mScreenBitmap = screenshot;
 
-        if (mScreenBitmap == null) {
-            mNotificationsController.notifyScreenshotError(
-                    R.string.screenshot_failed_to_capture_text);
-            finisher.accept(null);
-            mOnCompleteRunnable.run();
-            return;
-        }
-
         if (!isUserSetupComplete()) {
             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
             // and sharing shouldn't be exposed to the user.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
index ac3523b..1b1a51b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
@@ -36,10 +35,9 @@
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.MediaOutputSliceConstants;
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -51,7 +49,7 @@
  */
 public class MediaTransferManager {
     private final Context mContext;
-    private final ActivityStarter mActivityStarter;
+    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private MediaDevice mDevice;
     private List<View> mViews = new ArrayList<>();
     private LocalMediaManager mLocalMediaManager;
@@ -74,12 +72,7 @@
             ViewParent parent = view.getParent();
             StatusBarNotification statusBarNotification =
                     getRowForParent(parent).getEntry().getSbn();
-            final Intent intent = new Intent()
-                    .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
-                    .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
-                            statusBarNotification.getPackageName());
-            mActivityStarter.startActivity(intent, false, true /* dismissShade */,
-                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            mMediaOutputDialogFactory.create(statusBarNotification.getPackageName(), true);
             return true;
         }
     };
@@ -107,7 +100,7 @@
 
     public MediaTransferManager(Context context) {
         mContext = context;
-        mActivityStarter = Dependency.get(ActivityStarter.class);
+        mMediaOutputDialogFactory = Dependency.get(MediaOutputDialogFactory.class);
         LocalBluetoothManager lbm = Dependency.get(LocalBluetoothManager.class);
         InfoMediaManager imm = new InfoMediaManager(mContext, null, null, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index f1727ec..094e866 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -124,6 +124,7 @@
     private float mAppearAnimationTranslation;
     private int mNormalColor;
     private boolean mIsBelowSpeedBump;
+    private long mLastActionUpTime;
 
     private float mNormalBackgroundVisibilityAmount;
     private float mDimmedBackgroundFadeInAmount = -1;
@@ -225,6 +226,22 @@
         return super.onInterceptTouchEvent(ev);
     }
 
+    /** Sets the last action up time this view was touched. */
+    void setLastActionUpTime(long eventTime) {
+        mLastActionUpTime = eventTime;
+    }
+
+    /**
+     * Returns the last action up time. The last time will also be cleared because the source of
+     * action is not only from touch event. That prevents the caller from utilizing the time with
+     * unrelated event. The time can be 0 if the event is unavailable.
+     */
+    public long getAndResetLastActionUpTime() {
+        long lastActionUpTime = mLastActionUpTime;
+        mLastActionUpTime = 0;
+        return lastActionUpTime;
+    }
+
     protected boolean disallowSingleClick(MotionEvent ev) {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index dd30c89..41ce51c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import android.os.SystemClock;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
@@ -92,6 +93,9 @@
                 mBlockNextTouch = false;
                 return true;
             }
+            if (ev.getAction() == MotionEvent.ACTION_UP) {
+                mView.setLastActionUpTime(SystemClock.uptimeMillis());
+            }
             if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled()
                     && mView.isInteractive()) {
                 if (mNeedsDimming && !mView.isDimmed()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index efb2469..a5667bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -456,6 +456,7 @@
             return;
         }
         mSuppressed = suppressed;
+        mDozeLog.traceDozingSuppressed(mSuppressed);
         for (Callback callback : mCallbacks) {
             callback.onDozeSuppressedChanged(suppressed);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a3f14ba..1fdf631a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -23,6 +23,7 @@
 import android.util.MathUtils;
 
 import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
@@ -122,6 +123,8 @@
      */
     private int mUnlockedStackScrollerPadding;
 
+    private int mLockScreenMode;
+
     /**
      * Refreshes the dimension values.
      */
@@ -171,6 +174,13 @@
         result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
     }
 
+    /**
+      * Update lock screen mode for testing different layouts
+      */
+    public void onLockScreenModeChanged(int mode) {
+        mLockScreenMode = mode;
+    }
+
     public float getMinStackScrollerPadding() {
         return mBypassEnabled ? mUnlockedStackScrollerPadding
                 : mMinTopMargin + mKeyguardStatusHeight + mClockNotificationsMargin;
@@ -185,6 +195,9 @@
     }
 
     private int getExpandedPreferredClockY() {
+        if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+            return mMinTopMargin;
+        }
         return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
                 : getExpandedClockPosition();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 3f636ff..86d4ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -221,6 +221,11 @@
             new KeyguardUpdateMonitorCallback() {
 
                 @Override
+                public void onLockScreenModeChanged(int mode) {
+                    mClockPositionAlgorithm.onLockScreenModeChanged(mode);
+                }
+
+                @Override
                 public void onBiometricAuthenticated(int userId,
                         BiometricSourceType biometricSourceType,
                         boolean isStrongBiometric) {
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 e7c29b6..873d40f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3539,8 +3539,6 @@
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
             if (mNotificationPanelViewController.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
-            } else if (mBubblesOptional.isPresent()) {
-                mBubblesOptional.get().performBackPressIfNeeded();
             }
             return true;
         }
@@ -4284,6 +4282,19 @@
     }
 
     public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
+        return getDefaultActivityOptions(animationAdapter).toBundle();
+    }
+
+    public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter,
+            boolean isKeyguardShowing, long eventTime) {
+        ActivityOptions options = getDefaultActivityOptions(animationAdapter);
+        options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN
+                : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime);
+        return options.toBundle();
+    }
+
+    public static ActivityOptions getDefaultActivityOptions(
+            @Nullable RemoteAnimationAdapter animationAdapter) {
         ActivityOptions options;
         if (animationAdapter != null) {
             options = ActivityOptions.makeRemoteAnimation(animationAdapter);
@@ -4293,7 +4304,7 @@
         // Anything launched from the notification shade should always go into the secondary
         // split-screen windowing mode.
         options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        return options.toBundle();
+        return options;
     }
 
     void visibilityChanged(boolean visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index aa01642..256ee20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -40,7 +41,6 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.view.RemoteAnimationAdapter;
-import android.view.View;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.NotificationVisibility;
@@ -402,7 +402,7 @@
             PendingIntent intent,
             Intent fillInIntent,
             NotificationEntry entry,
-            View row,
+            ExpandableNotificationRow row,
             boolean wasOccluded,
             boolean isActivityIntent) {
         RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
@@ -414,8 +414,11 @@
                         .registerRemoteAnimationForNextActivityStart(
                                 intent.getCreatorPackage(), adapter);
             }
+            long eventTime = row.getAndResetLastActionUpTime();
+            Bundle options = eventTime > 0 ? getActivityOptions(adapter,
+                    mKeyguardStateController.isShowing(), eventTime) : getActivityOptions(adapter);
             int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
-                    null, null, getActivityOptions(adapter));
+                    null, null, options);
             mMainThreadHandler.post(() -> {
                 mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
             });
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 2081cfe..735338f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -90,6 +90,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -519,6 +520,7 @@
                 Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
                 Intent intent = new Intent(Settings.Panel.ACTION_VOLUME);
                 dismissH(DISMISS_REASON_SETTINGS_CLICKED);
+                Dependency.get(MediaOutputDialogFactory.class).dismiss();
                 Dependency.get(ActivityStarter.class).startActivity(intent,
                         true /* dismissShade */);
             });
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 247baf8..f55445c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -26,6 +26,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipUiEventLogger;
@@ -86,12 +87,19 @@
 
     @SysUISingleton
     @Provides
+    static PipBoundsState providePipBoundsState() {
+        return new PipBoundsState();
+    }
+
+    @SysUISingleton
+    @Provides
     static PipTaskOrganizer providePipTaskOrganizer(Context context,
+            PipBoundsState pipBoundsState,
             PipBoundsHandler pipBoundsHandler,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
-        return new PipTaskOrganizer(context, pipBoundsHandler,
+        return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
                 pipSurfaceTransactionHelper, splitScreenOptional, displayController,
                 pipUiEventLogger, shellTaskOrganizer);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index a948103..9281a09 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -55,9 +55,9 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -104,7 +104,7 @@
     private final DisplayImeController mDisplayImeController;
     private final InputConsumerController mInputConsumerController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final ActivityManagerWrapper mActivityManagerWrapper;
+    private final TaskStackChangeListeners mTaskStackChangeListeners;
     private final NavigationModeController mNavigationModeController;
     private final ScreenLifecycle mScreenLifecycle;
     private final SysUiState mSysUiState;
@@ -125,7 +125,7 @@
             ConfigurationController configurationController,
             InputConsumerController inputConsumerController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            ActivityManagerWrapper activityManagerWrapper,
+            TaskStackChangeListeners taskStackChangeListeners,
             DisplayImeController displayImeController,
             NavigationModeController navigationModeController,
             ScreenLifecycle screenLifecycle,
@@ -140,7 +140,7 @@
         mConfigurationController = configurationController;
         mInputConsumerController = inputConsumerController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mActivityManagerWrapper = activityManagerWrapper;
+        mTaskStackChangeListeners = taskStackChangeListeners;
         mDisplayImeController = displayImeController;
         mNavigationModeController = navigationModeController;
         mScreenLifecycle = screenLifecycle;
@@ -205,7 +205,7 @@
         });
 
         // Handle for system task stack changes.
-        mActivityManagerWrapper.registerTaskStackListener(
+        mTaskStackChangeListeners.registerTaskStackListener(
                 new TaskStackChangeListener() {
                     @Override
                     public void onTaskStackChanged() {
@@ -276,7 +276,7 @@
         };
         mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
 
-        mActivityManagerWrapper.registerTaskStackListener(
+        mTaskStackChangeListeners.registerTaskStackListener(
                 new TaskStackChangeListener() {
                     @Override
                     public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -388,7 +388,7 @@
             }
         });
 
-        mActivityManagerWrapper.registerTaskStackListener(
+        mTaskStackChangeListeners.registerTaskStackListener(
                 new TaskStackChangeListener() {
                     @Override
                     public void onTaskCreated(int taskId, ComponentName componentName) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index ae7b108..975757a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -32,6 +32,7 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipUiEventLogger;
@@ -71,14 +72,16 @@
             DisplayController displayController,
             PipAppOpsListener pipAppOpsListener,
             PipBoundsHandler pipBoundsHandler,
+            PipBoundsState pipBoundsState,
             PipMediaController pipMediaController,
             PipMenuActivityController pipMenuActivityController,
             PipTaskOrganizer pipTaskOrganizer,
             PipTouchHandler pipTouchHandler,
             WindowManagerShellWrapper windowManagerShellWrapper) {
         return new PipController(context, displayController,
-                pipAppOpsListener, pipBoundsHandler, pipMediaController, pipMenuActivityController,
-                pipTaskOrganizer, pipTouchHandler, windowManagerShellWrapper);
+                pipAppOpsListener, pipBoundsHandler, pipBoundsState, pipMediaController,
+                pipMenuActivityController, pipTaskOrganizer, pipTouchHandler,
+                windowManagerShellWrapper);
     }
 
     @SysUISingleton
@@ -94,6 +97,12 @@
 
     @SysUISingleton
     @Provides
+    static PipBoundsState providePipBoundsState() {
+        return new PipBoundsState();
+    }
+
+    @SysUISingleton
+    @Provides
     static PipBoundsHandler providesPipBoundsHandler(Context context) {
         return new PipBoundsHandler(context);
     }
@@ -109,21 +118,23 @@
     @Provides
     static PipTouchHandler providesPipTouchHandler(Context context,
             PipMenuActivityController menuActivityController, PipBoundsHandler pipBoundsHandler,
+            PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger) {
         return new PipTouchHandler(context, menuActivityController, pipBoundsHandler,
-                pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger);
+                pipBoundsState, pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger);
     }
 
     @SysUISingleton
     @Provides
     static PipTaskOrganizer providesPipTaskOrganizer(Context context,
+            PipBoundsState pipBoundsState,
             PipBoundsHandler pipBoundsHandler,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
-        return new PipTaskOrganizer(context, pipBoundsHandler,
+        return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
                 pipSurfaceTransactionHelper, splitScreenOptional, displayController,
                 pipUiEventLogger, shellTaskOrganizer);
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index 353fe62..35fe1ba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -168,7 +168,7 @@
         verify(mMockListener2).onClockChanged(captor2.capture());
         assertThat(captor1.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
         assertThat(captor2.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-        assertThat(captor1.getValue()).isNotSameAs(captor2.getValue());
+        assertThat(captor1.getValue()).isNotSameInstanceAs(captor2.getValue());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
index 1638ea1..71a0434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
@@ -29,8 +29,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SizeCompatModeActivityController.RestartActivityButton;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.Before;
@@ -50,7 +50,7 @@
 
     private SizeCompatModeActivityController mController;
     private TaskStackChangeListener mTaskStackListener;
-    private @Mock ActivityManagerWrapper mMockAm;
+    private @Mock TaskStackChangeListeners mMockTaskListeners;
     private @Mock RestartActivityButton mMockButton;
     private @Mock IBinder mMockActivityToken;
 
@@ -59,7 +59,7 @@
         MockitoAnnotations.initMocks(this);
         doReturn(true).when(mMockButton).show();
 
-        mController = new SizeCompatModeActivityController(mContext, mMockAm,
+        mController = new SizeCompatModeActivityController(mContext, mMockTaskListeners,
                 new CommandQueue(mContext)) {
             @Override
             RestartActivityButton createRestartButton(Context context) {
@@ -69,7 +69,7 @@
 
         ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
                 ArgumentCaptor.forClass(TaskStackChangeListener.class);
-        verify(mMockAm).registerTaskStackListener(listenerCaptor.capture());
+        verify(mMockTaskListeners).registerTaskStackListener(listenerCaptor.capture());
         mTaskStackListener = listenerCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 08ccd854..b082d17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -91,6 +91,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
@@ -194,6 +195,8 @@
 
     @Mock private WindowManagerShellWrapper mWindowManagerShellWrapper;
 
+    @Mock private BubbleLogger mBubbleLogger;
+
     private BubbleData mBubbleData;
 
     private TestableLooper mTestableLooper;
@@ -249,7 +252,7 @@
                         mock(HeadsUpManager.class),
                         mock(Handler.class)
                 );
-        mBubbleData = new BubbleData(mContext);
+        mBubbleData = new BubbleData(mContext, mBubbleLogger);
         when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mBubbleController = new TestableBubbleController(
                 mContext,
@@ -273,7 +276,10 @@
                 mStatusBarService,
                 mWindowManager,
                 mWindowManagerShellWrapper,
-                mLauncherApps);
+                mLauncherApps,
+                mBubbleLogger,
+                mock(Handler.class),
+                mock(ShellTaskOrganizer.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
         // Get a reference to the BubbleController's entry listener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 6e2c7e5..0c872db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.bubbles;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -94,6 +95,8 @@
     private PendingIntent mExpandIntent;
     @Mock
     private PendingIntent mDeleteIntent;
+    @Mock
+    private BubbleLogger mBubbleLogger;
 
     @Captor
     private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
@@ -133,7 +136,7 @@
         mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
         mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
 
-        mBubbleData = new BubbleData(getContext());
+        mBubbleData = new BubbleData(getContext(), mBubbleLogger);
 
         // Used by BubbleData to set lastAccessedTime
         when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
@@ -801,47 +804,48 @@
 
     private void assertBubbleAdded(Bubble expected) {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.addedBubble).named("addedBubble").isEqualTo(expected);
+        assertWithMessage("addedBubble").that(update.addedBubble).isEqualTo(expected);
     }
 
     private void assertBubbleRemoved(Bubble expected, @BubbleController.DismissReason int reason) {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.removedBubbles).named("removedBubbles")
+        assertWithMessage("removedBubbles").that(update.removedBubbles)
                 .isEqualTo(ImmutableList.of(Pair.create(expected, reason)));
     }
 
     private void assertOrderNotChanged() {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.orderChanged).named("orderChanged").isFalse();
+        assertWithMessage("orderChanged").that(update.orderChanged).isFalse();
     }
 
     private void assertOrderChangedTo(Bubble... order) {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.orderChanged).named("orderChanged").isTrue();
-        assertThat(update.bubbles).named("bubble order").isEqualTo(ImmutableList.copyOf(order));
+        assertWithMessage("orderChanged").that(update.orderChanged).isTrue();
+        assertWithMessage("bubble order").that(update.bubbles)
+                .isEqualTo(ImmutableList.copyOf(order));
     }
 
     private void assertSelectionNotChanged() {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.selectionChanged).named("selectionChanged").isFalse();
+        assertWithMessage("selectionChanged").that(update.selectionChanged).isFalse();
     }
 
     private void assertSelectionChangedTo(Bubble bubble) {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.selectionChanged).named("selectionChanged").isTrue();
-        assertThat(update.selectedBubble).named("selectedBubble").isEqualTo(bubble);
+        assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
+        assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
     }
 
     private void assertSelectionCleared() {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.selectionChanged).named("selectionChanged").isTrue();
-        assertThat(update.selectedBubble).named("selectedBubble").isNull();
+        assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
+        assertWithMessage("selectedBubble").that(update.selectedBubble).isNull();
     }
 
     private void assertExpandedChangedTo(boolean expected) {
         BubbleData.Update update = mUpdateCaptor.getValue();
-        assertThat(update.expandedChanged).named("expandedChanged").isTrue();
-        assertThat(update.expanded).named("expanded").isEqualTo(expected);
+        assertWithMessage("expandedChanged").that(update.expandedChanged).isTrue();
+        assertWithMessage("expanded").that(update.expanded).isEqualTo(expected);
     }
 
     private void assertOverflowChangedTo(ImmutableList<Bubble> bubbles) {
@@ -913,4 +917,4 @@
         setCurrentTime(time);
         mBubbleData.setExpanded(shouldBeExpanded);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java
new file mode 100644
index 0000000..7c1b414
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.bubbles;
+
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+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 android.app.ActivityManager;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.ShellTaskOrganizer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class MultiWindowTaskListenerTest extends SysuiTestCase {
+
+    @Mock
+    ShellTaskOrganizer mOrganizer;
+    @Mock
+    MultiWindowTaskListener.Listener mPendingListener;
+    @Mock
+    SurfaceControl mLeash;
+    @Mock
+    ActivityManager.RunningTaskInfo mTaskInfo;
+    @Mock
+    WindowContainerToken mToken;
+
+    Handler mHandler;
+    MultiWindowTaskListener mTaskListener;
+    TestableLooper mTestableLooper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTestableLooper = TestableLooper.get(this);
+        mHandler = new Handler(mTestableLooper.getLooper());
+
+        mTaskInfo = new ActivityManager.RunningTaskInfo();
+        mTaskInfo.token = mToken;
+
+        mTaskListener = new MultiWindowTaskListener(mHandler, mOrganizer);
+    }
+
+    private void addTaskAndVerify() {
+        mTaskListener.setPendingListener(mPendingListener);
+        mTaskListener.onTaskAppeared(mTaskInfo, mLeash);
+        mTestableLooper.processAllMessages();
+        verify(mPendingListener).onTaskAppeared(eq(mTaskInfo), eq(mLeash));
+    }
+
+    @Test
+    public void testListenForMultiWindowMode() {
+        mTaskListener = new MultiWindowTaskListener(mHandler, mOrganizer);
+        verify(mOrganizer).addListener(eq(mTaskListener), eq(TASK_LISTENER_TYPE_MULTI_WINDOW));
+    }
+
+    @Test
+    public void testRemovePendingListener() {
+        addTaskAndVerify();
+        reset(mPendingListener);
+
+        mTaskListener.removeListener(mPendingListener);
+
+        // If it was removed, our pendingListener shouldn't get triggered:
+        mTaskListener.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskListener.onTaskInfoChanged(mTaskInfo);
+        mTaskListener.onBackPressedOnTaskRoot(mTaskInfo);
+        mTaskListener.onTaskVanished(mTaskInfo);
+
+        mTestableLooper.processAllMessages();
+        verify(mPendingListener, never()).onTaskAppeared(any(), any());
+        verify(mPendingListener, never()).onTaskInfoChanged(any());
+        verify(mPendingListener, never()).onBackPressedOnTaskRoot(any());
+        verify(mPendingListener, never()).onTaskVanished(any());
+    }
+
+    @Test
+    public void testOnTaskAppeared() {
+        addTaskAndVerify();
+        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mToken), eq(true));
+    }
+
+    @Test
+    public void testOnTaskAppeared_nullListener() {
+        mTaskListener.onTaskAppeared(mTaskInfo, mLeash);
+        mTestableLooper.processAllMessages();
+
+        verify(mOrganizer, never()).setInterceptBackPressedOnTaskRoot(any(), anyBoolean());
+        verify(mPendingListener, never()).onTaskAppeared(any(), any());
+    }
+
+    @Test
+    public void testOnTaskVanished() {
+        addTaskAndVerify();
+        mTaskListener.onTaskVanished(mTaskInfo);
+        mTestableLooper.processAllMessages();
+
+        verify(mPendingListener).onTaskVanished(eq(mTaskInfo));
+    }
+
+    @Test
+    public void testOnTaskVanished_neverAdded() {
+        mTaskListener.onTaskVanished(mTaskInfo);
+        mTestableLooper.processAllMessages();
+
+        verify(mPendingListener, never()).onTaskVanished(any());
+    }
+
+    @Test
+    public void testOnTaskInfoChanged() {
+        addTaskAndVerify();
+        mTaskListener.onTaskInfoChanged(mTaskInfo);
+        mTestableLooper.processAllMessages();
+
+        verify(mPendingListener).onTaskInfoChanged(eq(mTaskInfo));
+    }
+
+    @Test
+    public void testOnTaskInfoChanged_neverAdded() {
+        mTaskListener.onTaskInfoChanged(mTaskInfo);
+        mTestableLooper.processAllMessages();
+
+        verify(mPendingListener, never()).onTaskInfoChanged(any());
+    }
+
+    @Test
+    public void testOnBackPressedOnTaskRoot() {
+        addTaskAndVerify();
+        mTaskListener.onBackPressedOnTaskRoot(mTaskInfo);
+        mTestableLooper.processAllMessages();
+
+        verify(mPendingListener).onBackPressedOnTaskRoot(eq(mTaskInfo));
+    }
+
+    @Test
+    public void testOnBackPressedOnTaskRoot_neverAdded() {
+        mTaskListener.onBackPressedOnTaskRoot(mTaskInfo);
+        mTestableLooper.processAllMessages();
+
+        verify(mPendingListener, never()).onBackPressedOnTaskRoot(any());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 1eaa6a4..cbacd53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -91,6 +91,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.InjectionInflationController;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
@@ -188,6 +189,8 @@
     private LauncherApps mLauncherApps;
     @Mock
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
+    @Mock
+    private BubbleLogger mBubbleLogger;
 
     private BubbleData mBubbleData;
 
@@ -251,7 +254,7 @@
                         mock(HeadsUpManager.class),
                         mock(Handler.class)
                 );
-        mBubbleData = new BubbleData(mContext);
+        mBubbleData = new BubbleData(mContext, mBubbleLogger);
         when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
         mBubbleController = new TestableBubbleController(
                 mContext,
@@ -275,7 +278,10 @@
                 mStatusBarService,
                 mWindowManager,
                 mWindowManagerShellWrapper,
-                mLauncherApps);
+                mLauncherApps,
+                mBubbleLogger,
+                mock(Handler.class),
+                mock(ShellTaskOrganizer.class));
         mBubbleController.addNotifCallback(mNotifCallback);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java
new file mode 100644
index 0000000..6f3968d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.bubbles;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.ShellTaskOrganizer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class TaskViewTest extends SysuiTestCase {
+
+    @Mock
+    TaskView.Listener mViewListener;
+    @Mock
+    ActivityManager.RunningTaskInfo mTaskInfo;
+    @Mock
+    WindowContainerToken mToken;
+    @Mock
+    ShellTaskOrganizer mOrganizer;
+    @Mock
+    MultiWindowTaskListener mTaskListener;
+
+    SurfaceSession mSession;
+    SurfaceControl mLeash;
+
+    Context mContext;
+    TaskView mTaskView;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLeash = new SurfaceControl.Builder(mSession)
+                .setName("test")
+                .build();
+
+        mContext = getContext();
+
+        when(mTaskListener.getTaskOrganizer()).thenReturn(mOrganizer);
+        mTaskInfo = new ActivityManager.RunningTaskInfo();
+        mTaskInfo.token = mToken;
+        mTaskInfo.taskId = 314;
+        mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
+
+        mTaskView = new TaskView(mContext, mTaskListener);
+        mTaskView.setListener(mViewListener);
+    }
+
+    @After
+    public void tearDown() {
+        if (mTaskView != null) {
+            mTaskView.release();
+        }
+    }
+
+    @Test
+    public void testSetPendingListener_throwsException() {
+        TaskView taskView = new TaskView(mContext, mTaskListener);
+        taskView.setListener(mViewListener);
+        try {
+            taskView.setListener(mViewListener);
+        } catch (IllegalStateException e) {
+            // pass
+            return;
+        }
+        fail("Expected IllegalStateException");
+    }
+
+    @Test
+    public void testStartActivity() {
+        ActivityOptions options = ActivityOptions.makeBasic();
+        mTaskView.startActivity(mock(PendingIntent.class), null, options);
+
+        verify(mTaskListener).setPendingListener(eq(mTaskView));
+        assertThat(options.getLaunchWindowingMode()).isEqualTo(WINDOWING_MODE_MULTI_WINDOW);
+        assertThat(options.getTaskAlwaysOnTop()).isTrue();
+    }
+
+    @Test
+    public void testOnTaskAppeared_noSurface() {
+        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+
+        verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+        verify(mViewListener, never()).onInitialized();
+        // If there's no surface the task should be made invisible
+        verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+    }
+
+    @Test
+    public void testOnTaskAppeared_withSurface() {
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+
+        verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+        verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testSurfaceCreated_noTask() {
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+        verify(mViewListener).onInitialized();
+        // No task, no visibility change
+        verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testSurfaceCreated_withTask() {
+        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+        verify(mViewListener).onInitialized();
+        verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
+    }
+
+    @Test
+    public void testSurfaceDestroyed_noTask() {
+        SurfaceHolder sh = mock(SurfaceHolder.class);
+        mTaskView.surfaceCreated(sh);
+        mTaskView.surfaceDestroyed(sh);
+
+        verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testSurfaceDestroyed_withTask() {
+        SurfaceHolder sh = mock(SurfaceHolder.class);
+        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskView.surfaceCreated(sh);
+        reset(mViewListener);
+        mTaskView.surfaceDestroyed(sh);
+
+        verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+    }
+
+    @Test
+    public void testOnReleased() {
+        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        mTaskView.release();
+
+        verify(mTaskListener).removeListener(eq(mTaskView));
+        verify(mViewListener).onReleased();
+    }
+
+    @Test
+    public void testOnTaskVanished() {
+        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        mTaskView.onTaskVanished(mTaskInfo);
+
+        verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
+    }
+
+    @Test
+    public void testOnBackPressedOnTaskRoot() {
+        mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
+
+        verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index 87ea22a..27c6fc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -19,6 +19,7 @@
 import android.app.INotificationManager;
 import android.content.Context;
 import android.content.pm.LauncherApps;
+import android.os.Handler;
 import android.view.WindowManager;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -35,10 +36,10 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
-
 /**
  * Testable BubbleController subclass that immediately synchronizes surfaces.
  */
@@ -66,14 +67,18 @@
             IStatusBarService statusBarService,
             WindowManager windowManager,
             WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps) {
+            LauncherApps launcherApps,
+            BubbleLogger bubbleLogger,
+            Handler mainHandler,
+            ShellTaskOrganizer shellTaskOrganizer) {
         super(context,
                 notificationShadeWindowController, statusBarStateController, shadeController,
                 data, Runnable::run, configurationController, interruptionStateProvider,
                 zenModeController, lockscreenUserManager, groupManager, entryManager,
                 notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
                 dataRepository, sysUiState, notificationManager, statusBarService,
-                windowManager, windowManagerShellWrapper, launcherApps);
+                windowManager, windowManagerShellWrapper, launcherApps, bubbleLogger,
+                mainHandler, shellTaskOrganizer);
         setInflateSynchronously(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index 3439fe5..cd5740d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -41,8 +41,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,7 +62,7 @@
     private static final int TASK_ID = 444;
 
     private @Mock Context mContext;
-    private @Mock ActivityManagerWrapper mActivityManager;
+    private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
     private @Mock IActivityTaskManager mIActivityTaskManager;
 
     private WorkLockActivityController mController;
@@ -78,10 +78,10 @@
         // Construct controller. Save the TaskStackListener for injecting events.
         final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
                 ArgumentCaptor.forClass(TaskStackChangeListener.class);
-        mController = new WorkLockActivityController(mContext, mActivityManager,
+        mController = new WorkLockActivityController(mContext, mTaskStackChangeListeners,
                 mIActivityTaskManager);
 
-        verify(mActivityManager).registerTaskStackListener(listenerCaptor.capture());
+        verify(mTaskStackChangeListeners).registerTaskStackListener(listenerCaptor.capture());
         mTaskStackListener = listenerCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 81139f19..182a056 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -38,6 +38,7 @@
 import androidx.lifecycle.LiveData
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.dialog.MediaOutputDialogFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.util.animation.TransitionLayout
@@ -92,6 +93,7 @@
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var expandedSet: ConstraintSet
     @Mock private lateinit var collapsedSet: ConstraintSet
+    @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
     private lateinit var appIcon: ImageView
     private lateinit var appName: TextView
     private lateinit var albumView: ImageView
@@ -126,7 +128,8 @@
         whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
 
         player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
-                seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil)
+                seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
+                mediaOutputDialogFactory)
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
index 9b6dd05..3494bd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
@@ -32,15 +32,11 @@
 import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
 import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
 
-import static junit.framework.Assert.assertEquals;
-
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.hardware.input.InputManager;
 import android.testing.AndroidTestingRunner;
@@ -151,19 +147,4 @@
             verify(mUiEventLogger, times(1)).log(expected);
         }
     }
-
-    @Test
-    public void testBubbleEvents_bubbleExpanded() {
-        when(mBubbles.getExpandedDisplayId(mContext)).thenReturn(3);
-
-        int action = KeyEvent.ACTION_DOWN;
-        int flags = 0;
-        int code = KeyEvent.KEYCODE_BACK;
-        mKeyButtonView.setCode(code);
-        mKeyButtonView.sendEvent(action, flags);
-
-        verify(mInputManager, times(1)).injectInputEvent(mInputEventCaptor.capture(),
-                anyInt());
-        assertEquals(3, mInputEventCaptor.getValue().getDisplayId());
-    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
index 72e6df2..724ea02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
@@ -80,7 +80,7 @@
         val background2 = processor.processArtwork(context, artwork)!!
         // THEN the two bitmaps are the same
         // Note: This is currently broken and trying to use caching causes issues
-        assertThat(background1).isNotSameAs(background2)
+        assertThat(background1).isNotSameInstanceAs(background2)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index d0dfb171..524ead2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -34,6 +34,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -64,6 +65,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(MediaOutputDialogFactory.class);
         allowTestableLooperAsMainThread();
         when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
         mDynamicChildBindController =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index 79fa436..5898664 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -118,7 +118,7 @@
         val people = viewModel.people.toList()
         assertThat(people.size).isEqualTo(1)
         assertThat(people[0].name).isEqualTo("name")
-        assertThat(people[0].icon).isSameAs(fakePerson.avatar)
+        assertThat(people[0].icon).isSameInstanceAs(fakePerson.avatar)
 
         people[0].onClick()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index c2091da..d08b2b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -25,8 +25,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.AppOpsManager;
-import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
@@ -37,6 +35,7 @@
 
 import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,6 +50,8 @@
     @Before
     @UiThreadTest
     public void setup() {
+        mDependency.injectMockDependency(MediaOutputDialogFactory.class);
+
         mView = new NotificationContentView(mContext, null);
         ExpandableNotificationRow row = new ExpandableNotificationRow(mContext, null);
         ExpandableNotificationRow mockRow = spy(row);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 1255b6d..8a5afe6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -152,6 +153,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(SmartReplyController.class);
+        mDependency.injectMockDependency(MediaOutputDialogFactory.class);
 
         mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fb37ed5..847e0a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -48,6 +48,7 @@
 import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -115,6 +116,7 @@
         mTestLooper = testLooper;
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
+        dependency.injectMockDependency(MediaOutputDialogFactory.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupMembershipManager = new NotificationGroupManagerLegacy(
                 mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 7ee27c9..3868443 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -350,7 +350,7 @@
         mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
 
         List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
-        assertThat(actionList).containsAllIn(
+        assertThat(actionList).containsAtLeastElementsIn(
                 new AccessibilityNodeInfo.AccessibilityAction[] {
                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD,
                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 76fe3bf..2bc07ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -36,9 +36,9 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tracing.ProtoTracer;
@@ -68,7 +68,7 @@
     @Mock CommandQueue mCommandQueue;
     @Mock ConfigurationController mConfigurationController;
     @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock ActivityManagerWrapper mActivityManagerWrapper;
+    @Mock TaskStackChangeListeners mTaskStackChangeListeners;
     @Mock DisplayImeController mDisplayImeController;
     @Mock InputConsumerController mMockInputConsumerController;
     @Mock NavigationModeController mNavigationModeController;
@@ -88,7 +88,7 @@
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
 
         mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController,
-                mInputConsumerController, mKeyguardUpdateMonitor, mActivityManagerWrapper,
+                mInputConsumerController, mKeyguardUpdateMonitor, mTaskStackChangeListeners,
                 mDisplayImeController, mNavigationModeController, mScreenLifecycle, mSysUiState,
                 Optional.of(mPip), Optional.of(mSplitScreen), Optional.of(mOneHanded),
                 mTaskOrganizer, mProtoTracer);
@@ -116,7 +116,7 @@
         final TestableContext nonPipContext = getNonPipFeatureContext();
         final WMShell nonPipWMShell = new WMShell(nonPipContext, mCommandQueue,
                 mConfigurationController, mMockInputConsumerController, mKeyguardUpdateMonitor,
-                mActivityManagerWrapper, mDisplayImeController, mNavigationModeController,
+                mTaskStackChangeListeners, mDisplayImeController, mNavigationModeController,
                 mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
                 Optional.of(mOneHanded), mTaskOrganizer, mProtoTracer);
         nonPipWMShell.initPip(mPip);
@@ -125,7 +125,7 @@
         verify(mKeyguardUpdateMonitor, never()).registerCallback(any());
         verify(mConfigurationController, never()).addCallback(any());
         verify(mSysUiState, never()).addCallback(any());
-        verify(mActivityManagerWrapper, never()).registerTaskStackListener(any());
+        verify(mTaskStackChangeListeners, never()).registerTaskStackListener(any());
         verify(mMockInputConsumerController, never()).setInputListener(any());
         verify(mMockInputConsumerController, never()).setRegistrationListener(any());
         verify(mPip, never()).registerSessionListenerForCurrentUser();
@@ -136,7 +136,7 @@
         mWMShell.initSplitScreen(mSplitScreen);
 
         verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
-        verify(mActivityManagerWrapper).registerTaskStackListener(
+        verify(mTaskStackChangeListeners).registerTaskStackListener(
                 any(TaskStackChangeListener.class));
     }
 
@@ -149,7 +149,7 @@
         verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
         verify(mNavigationModeController).addListener(
                 any(NavigationModeController.ModeChangedListener.class));
-        verify(mActivityManagerWrapper).registerTaskStackListener(
+        verify(mTaskStackChangeListeners).registerTaskStackListener(
                 any(TaskStackChangeListener.class));
 
         verify(mOneHanded).registerGestureCallback(any(
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 6276c4e..0cf14e3 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -20,6 +20,10 @@
 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
 import static android.net.util.PrefixUtils.asIpPrefix;
 
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
+
 import static java.util.Arrays.asList;
 
 import android.content.Context;
@@ -37,9 +41,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
+import java.net.Inet4Address;
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
@@ -58,10 +63,6 @@
 public class PrivateAddressCoordinator {
     public static final int PREFIX_LENGTH = 24;
 
-    private static final int MAX_UBYTE = 256;
-    private static final int BYTE_MASK = 0xff;
-    private static final byte DEFAULT_ID = (byte) 42;
-
     // Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
     // address may be requested before coordinator get current upstream notification. To ensure
     // coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared
@@ -69,22 +70,22 @@
     // mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprecatedUpstreams().
     private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap;
     private final ArraySet<IpServer> mDownstreams;
-    // IANA has reserved the following three blocks of the IP address space for private intranets:
-    // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
-    // Tethering use 192.168.0.0/16 that has 256 contiguous class C network numbers.
-    private static final String DEFAULT_TETHERING_PREFIX = "192.168.0.0/16";
     private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
     private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
-    private final IpPrefix mTetheringPrefix;
+    private final List<IpPrefix> mTetheringPrefixes;
     private final ConnectivityManager mConnectivityMgr;
     private final TetheringConfiguration mConfig;
     // keyed by downstream type(TetheringManager.TETHERING_*).
     private final SparseArray<LinkAddress> mCachedAddresses;
 
     public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
+        this(context, config, new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"))));
+    }
+
+    public PrivateAddressCoordinator(Context context, TetheringConfiguration config,
+            List<IpPrefix> prefixPools) {
         mDownstreams = new ArraySet<>();
         mUpstreamPrefixMap = new ArrayMap<>();
-        mTetheringPrefix = new IpPrefix(DEFAULT_TETHERING_PREFIX);
         mConnectivityMgr = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         mConfig = config;
@@ -92,6 +93,8 @@
         // Reserved static addresses for bluetooth and wifi p2p.
         mCachedAddresses.put(TETHERING_BLUETOOTH, new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
         mCachedAddresses.put(TETHERING_WIFI_P2P, new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
+
+        mTetheringPrefixes = prefixPools;
     }
 
     /**
@@ -179,52 +182,148 @@
             return cachedAddress;
         }
 
-        // Address would be 192.168.[subAddress]/24.
-        final byte[] bytes = mTetheringPrefix.getRawAddress();
-        final int subAddress = getRandomSubAddr();
-        final int subNet = (subAddress >> 8) & BYTE_MASK;
-        bytes[3] = getSanitizedAddressSuffix(subAddress, (byte) 0, (byte) 1, (byte) 0xff);
-        for (int i = 0; i < MAX_UBYTE; i++) {
-            final int newSubNet = (subNet + i) & BYTE_MASK;
-            bytes[2] = (byte) newSubNet;
-
-            final InetAddress addr;
-            try {
-                addr = InetAddress.getByAddress(bytes);
-            } catch (UnknownHostException e) {
-                throw new IllegalStateException("Invalid address, shouldn't happen.", e);
+        for (IpPrefix prefixRange : mTetheringPrefixes) {
+            final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
+            if (newAddress != null) {
+                mDownstreams.add(ipServer);
+                mCachedAddresses.put(ipServer.interfaceType(), newAddress);
+                return newAddress;
             }
-
-            if (isConflict(new IpPrefix(addr, PREFIX_LENGTH))) continue;
-
-            mDownstreams.add(ipServer);
-            final LinkAddress newAddress = new LinkAddress(addr, PREFIX_LENGTH);
-            mCachedAddresses.put(ipServer.interfaceType(), newAddress);
-            return newAddress;
         }
 
         // No available address.
         return null;
     }
 
-    private boolean isConflict(final IpPrefix prefix) {
-        // Check whether this prefix is in use or conflict with any current upstream network.
-        return isDownstreamPrefixInUse(prefix) || isConflictWithUpstream(prefix);
+    private int getPrefixBaseAddress(final IpPrefix prefix) {
+        return inet4AddressToIntHTH((Inet4Address) prefix.getAddress());
     }
 
-    /** Get random sub address value. Return value is in 0 ~ 0xffff. */
-    @VisibleForTesting
-    public int getRandomSubAddr() {
-        return ((new Random()).nextInt()) & 0xffff; // subNet is in 0 ~ 0xffff.
+    /**
+     * Check whether input prefix conflict with upstream prefixes or in-use downstream prefixes.
+     * If yes, return one of them.
+     */
+    private IpPrefix getConflictPrefix(final IpPrefix prefix) {
+        final IpPrefix upstream = getConflictWithUpstream(prefix);
+        if (upstream != null) return upstream;
+
+        return getInUseDownstreamPrefix(prefix);
     }
 
-    private byte getSanitizedAddressSuffix(final int source, byte... excluded) {
-        final byte subId = (byte) (source & BYTE_MASK);
-        for (byte value : excluded) {
-            if (subId == value) return DEFAULT_ID;
+    // Get the next non-conflict sub prefix. E.g: To get next sub prefix from 10.0.0.0/8, if the
+    // previously selected prefix is 10.20.42.0/24(subPrefix: 0.20.42.0) and the conflicting prefix
+    // is 10.16.0.0/20 (10.16.0.0 ~ 10.16.15.255), then the max address under subPrefix is
+    // 0.16.15.255 and the next subPrefix is 0.16.16.255/24 (0.16.15.255 + 0.0.1.0).
+    // Note: the sub address 0.0.0.255 here is fine to be any value that it will be replaced as
+    // selected random sub address later.
+    private int getNextSubPrefix(final IpPrefix conflictPrefix, final int prefixRangeMask) {
+        final int suffixMask = ~prefixLengthToV4NetmaskIntHTH(conflictPrefix.getPrefixLength());
+        // The largest offset within the prefix assignment block that still conflicts with
+        // conflictPrefix.
+        final int maxConflict =
+                (getPrefixBaseAddress(conflictPrefix) | suffixMask) & ~prefixRangeMask;
+
+        final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);
+        // Pick a sub prefix a full prefix (1 << (32 - PREFIX_LENGTH) addresses) greater than
+        // maxConflict. This ensures that the selected prefix never overlaps with conflictPrefix.
+        // There is no need to mask the result with PREFIX_LENGTH bits because this is done by
+        // findAvailablePrefixFromRange when it constructs the prefix.
+        return maxConflict + (1 << (32 - PREFIX_LENGTH));
+    }
+
+    private LinkAddress chooseDownstreamAddress(final IpPrefix prefixRange) {
+        // The netmask of the prefix assignment block (e.g., 0xfff00000 for 172.16.0.0/12).
+        final int prefixRangeMask = prefixLengthToV4NetmaskIntHTH(prefixRange.getPrefixLength());
+
+        // The zero address in the block (e.g., 0xac100000 for 172.16.0.0/12).
+        final int baseAddress = getPrefixBaseAddress(prefixRange);
+
+        // The subnet mask corresponding to PREFIX_LENGTH.
+        final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);
+
+        // The offset within prefixRange of a randomly-selected prefix of length PREFIX_LENGTH.
+        // This may not be the prefix of the address returned by this method:
+        // - If it is already in use, the method will return an address in another prefix.
+        // - If all prefixes within prefixRange are in use, the method will return null. For
+        // example, for a /24 prefix within 172.26.0.0/12, this will be a multiple of 256 in
+        // [0, 1048576). In other words, a random 32-bit number with mask 0x000fff00.
+        //
+        // prefixRangeMask is required to ensure no wrapping. For example, consider:
+        // - prefixRange 127.0.0.0/8
+        // - randomPrefixStart 127.255.255.0
+        // - A conflicting prefix of 127.255.254.0/23
+        // In this case without prefixRangeMask, getNextSubPrefix would return 128.0.0.0, which
+        // means the "start < end" check in findAvailablePrefixFromRange would not reject the prefix
+        // because Java doesn't have unsigned integers, so 128.0.0.0 = 0x80000000 = -2147483648
+        // is less than 127.0.0.0 = 0x7f000000 = 2130706432.
+        //
+        // Additionally, it makes debug output easier to read by making the numbers smaller.
+        final int randomPrefixStart = getRandomInt() & ~prefixRangeMask & prefixMask;
+
+        // A random offset within the prefix. Used to determine the local address once the prefix
+        // is selected. It does not result in an IPv4 address ending in .0, .1, or .255
+        // For a PREFIX_LENGTH of 255, this is a number between 2 and 254.
+        final int subAddress = getSanitizedSubAddr(~prefixMask);
+
+        // Find a prefix length PREFIX_LENGTH between randomPrefixStart and the end of the block,
+        // such that the prefix does not conflict with any upstream.
+        IpPrefix downstreamPrefix = findAvailablePrefixFromRange(
+                 randomPrefixStart, (~prefixRangeMask) + 1, baseAddress, prefixRangeMask);
+        if (downstreamPrefix != null) return getLinkAddress(downstreamPrefix, subAddress);
+
+        // If that failed, do the same, but between 0 and randomPrefixStart.
+        downstreamPrefix = findAvailablePrefixFromRange(
+                0, randomPrefixStart, baseAddress, prefixRangeMask);
+
+        return getLinkAddress(downstreamPrefix, subAddress);
+    }
+
+    private LinkAddress getLinkAddress(final IpPrefix prefix, final int subAddress) {
+        if (prefix == null) return null;
+
+        final InetAddress address = intToInet4AddressHTH(getPrefixBaseAddress(prefix) | subAddress);
+        return new LinkAddress(address, PREFIX_LENGTH);
+    }
+
+    private IpPrefix findAvailablePrefixFromRange(final int start, final int end,
+            final int baseAddress, final int prefixRangeMask) {
+        int newSubPrefix = start;
+        while (newSubPrefix < end) {
+            final InetAddress address = intToInet4AddressHTH(baseAddress | newSubPrefix);
+            final IpPrefix prefix = new IpPrefix(address, PREFIX_LENGTH);
+
+            final IpPrefix conflictPrefix = getConflictPrefix(prefix);
+
+            if (conflictPrefix == null) return prefix;
+
+            newSubPrefix = getNextSubPrefix(conflictPrefix, prefixRangeMask);
         }
 
-        return subId;
+        return null;
+    }
+
+    /** Get random int which could be used to generate random address. */
+    @VisibleForTesting
+    public int getRandomInt() {
+        return (new Random()).nextInt();
+    }
+
+    /** Get random subAddress and avoid selecting x.x.x.0, x.x.x.1 and x.x.x.255 address. */
+    private int getSanitizedSubAddr(final int subAddrMask) {
+        final int randomSubAddr = getRandomInt() & subAddrMask;
+        // If prefix length > 30, the selecting speace would be less than 4 which may be hard to
+        // avoid 3 consecutive address.
+        if (PREFIX_LENGTH > 30) return randomSubAddr;
+
+        // TODO: maybe it is not necessary to avoid .0, .1 and .255 address because tethering
+        // address would not be conflicted. This code only works because PREFIX_LENGTH is not longer
+        // than 24
+        final int candidate = randomSubAddr & 0xff;
+        if (candidate == 0 || candidate == 1 || candidate == 255) {
+            return (randomSubAddr & 0xfffffffc) + 2;
+        }
+
+        return randomSubAddr;
     }
 
     /** Release downstream record for IpServer. */
@@ -237,14 +336,18 @@
         mUpstreamPrefixMap.clear();
     }
 
-    private boolean isConflictWithUpstream(final IpPrefix source) {
+    private IpPrefix getConflictWithUpstream(final IpPrefix prefix) {
         for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
             final List<IpPrefix> list = mUpstreamPrefixMap.valueAt(i);
-            for (IpPrefix target : list) {
-                if (isConflictPrefix(source, target)) return true;
+            for (IpPrefix upstream : list) {
+                if (isConflictPrefix(prefix, upstream)) return upstream;
             }
         }
-        return false;
+        return null;
+    }
+
+    private boolean isConflictWithUpstream(final IpPrefix prefix) {
+        return getConflictWithUpstream(prefix) != null;
     }
 
     private boolean isConflictPrefix(final IpPrefix prefix1, final IpPrefix prefix2) {
@@ -257,11 +360,10 @@
 
     // InUse Prefixes are prefixes of mCachedAddresses which are active downstream addresses, last
     // downstream addresses(reserved for next time) and static addresses(e.g. bluetooth, wifi p2p).
-    private boolean isDownstreamPrefixInUse(final IpPrefix prefix) {
-        // This class always generates downstream prefixes with the same prefix length, so
-        // prefixes cannot be contained in each other. They can only be equal to each other.
+    private IpPrefix getInUseDownstreamPrefix(final IpPrefix prefix) {
         for (int i = 0; i < mCachedAddresses.size(); i++) {
-            if (prefix.equals(asIpPrefix(mCachedAddresses.valueAt(i)))) return true;
+            final IpPrefix downstream = asIpPrefix(mCachedAddresses.valueAt(i));
+            if (isConflictPrefix(prefix, downstream)) return downstream;
         }
 
         // IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include
@@ -270,10 +372,10 @@
             final IpPrefix target = getDownstreamPrefix(downstream);
             if (target == null) continue;
 
-            if (isConflictPrefix(prefix, target)) return true;
+            if (isConflictPrefix(prefix, target)) return target;
         }
 
-        return false;
+        return null;
     }
 
     private IpPrefix getDownstreamPrefix(final IpServer downstream) {
@@ -284,6 +386,13 @@
     }
 
     void dump(final IndentingPrintWriter pw) {
+        pw.println("mTetheringPrefixes:");
+        pw.increaseIndent();
+        for (IpPrefix prefix : mTetheringPrefixes) {
+            pw.println(prefix);
+        }
+        pw.decreaseIndent();
+
         pw.println("mUpstreamPrefixMap:");
         pw.increaseIndent();
         for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 474f4e8..5a0c5b0 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -326,7 +326,7 @@
         // It is OK for the configuration to be passed to the PrivateAddressCoordinator at
         // construction time because the only part of the configuration it uses is
         // shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that.
-        mPrivateAddressCoordinator = new PrivateAddressCoordinator(mContext, mConfig);
+        mPrivateAddressCoordinator = mDeps.getPrivateAddressCoordinator(mContext, mConfig);
 
         // Must be initialized after tethering configuration is loaded because BpfCoordinator
         // constructor needs to use the configuration.
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 131a5fb..45b9141 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -156,4 +156,12 @@
     public boolean isTetheringDenied() {
         return TextUtils.equals(SystemProperties.get("ro.tether.denied"), "true");
     }
+
+    /**
+     * Get a reference to PrivateAddressCoordinator to be used by Tethering.
+     */
+    public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
+            TetheringConfiguration cfg) {
+        return new PrivateAddressCoordinator(ctx, cfg);
+    }
 }
diff --git a/packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java b/packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
index 95e36fa..42a91aa 100644
--- a/packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
+++ b/packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
@@ -17,9 +17,9 @@
 package android.net.ip;
 
 import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
 
-import static com.android.internal.util.BitUtils.uint16;
+import static com.android.net.module.util.IpUtils.icmpv6Checksum;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -30,34 +30,29 @@
 import android.net.INetd;
 import android.net.InetAddresses;
 import android.net.MacAddress;
-import android.net.TestNetworkInterface;
-import android.net.TestNetworkManager;
 import android.net.util.InterfaceParams;
 import android.net.util.TetheringUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
-import android.system.ErrnoException;
-import android.system.Os;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.net.module.util.IpUtils;
 import com.android.testutils.TapPacketReader;
+import com.android.testutils.TapPacketReaderRule;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.MockitoAnnotations;
 
-import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicReference;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -65,16 +60,18 @@
     private static final int DATA_BUFFER_LEN = 4096;
     private static final int PACKET_TIMEOUT_MS = 5_000;
 
-    // TODO: make NetworkStackConstants accessible to this test and use the constant from there.
-    private static final int ETHER_SRC_ADDR_OFFSET = 6;
+    // Start the readers manually on a common handler shared with DadProxy, for simplicity
+    @Rule
+    public final TapPacketReaderRule mUpstreamReader = new TapPacketReaderRule(
+            DATA_BUFFER_LEN, false /* autoStart */);
+    @Rule
+    public final TapPacketReaderRule mTetheredReader = new TapPacketReaderRule(
+            DATA_BUFFER_LEN, false /* autoStart */);
 
-    private DadProxy mProxy;
-    TestNetworkInterface mUpstreamTestIface, mTetheredTestIface;
     private InterfaceParams mUpstreamParams, mTetheredParams;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
     private TapPacketReader mUpstreamPacketReader, mTetheredPacketReader;
-    private FileDescriptor mUpstreamTapFd, mTetheredTapFd;
 
     private static INetd sNetd;
 
@@ -106,12 +103,12 @@
 
     @After
     public void tearDown() throws Exception {
+        mUpstreamReader.stop();
+        mTetheredReader.stop();
+
         if (mHandlerThread != null) {
-            mHandler.post(mUpstreamPacketReader::stop); // Also closes the socket
-            mHandler.post(mTetheredPacketReader::stop); // Also closes the socket
-            mUpstreamTapFd = null;
-            mTetheredTapFd = null;
             mHandlerThread.quitSafely();
+            mHandlerThread.join(PACKET_TIMEOUT_MS);
         }
 
         if (mTetheredParams != null) {
@@ -120,54 +117,20 @@
         if (mUpstreamParams != null) {
             sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name);
         }
-
-        if (mUpstreamTestIface != null) {
-            try {
-                Os.close(mUpstreamTestIface.getFileDescriptor().getFileDescriptor());
-            } catch (ErrnoException e) { }
-        }
-
-        if (mTetheredTestIface != null) {
-            try {
-                Os.close(mTetheredTestIface.getFileDescriptor().getFileDescriptor());
-            } catch (ErrnoException e) { }
-        }
-    }
-
-    private TestNetworkInterface setupTapInterface() {
-        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
-        AtomicReference<TestNetworkInterface> iface = new AtomicReference<>();
-
-        inst.getUiAutomation().adoptShellPermissionIdentity();
-        try {
-            final TestNetworkManager tnm = (TestNetworkManager) inst.getContext().getSystemService(
-                    Context.TEST_NETWORK_SERVICE);
-            iface.set(tnm.createTapInterface());
-        } finally {
-            inst.getUiAutomation().dropShellPermissionIdentity();
-        }
-
-        return iface.get();
     }
 
     private void setupTapInterfaces() {
         // Create upstream test iface.
-        mUpstreamTestIface = setupTapInterface();
-        mUpstreamParams = InterfaceParams.getByName(mUpstreamTestIface.getInterfaceName());
+        mUpstreamReader.start(mHandler);
+        mUpstreamParams = InterfaceParams.getByName(mUpstreamReader.iface.getInterfaceName());
         assertNotNull(mUpstreamParams);
-        mUpstreamTapFd = mUpstreamTestIface.getFileDescriptor().getFileDescriptor();
-        mUpstreamPacketReader = new TapPacketReader(mHandler, mUpstreamTapFd,
-                                                    DATA_BUFFER_LEN);
-        mHandler.post(mUpstreamPacketReader::start);
+        mUpstreamPacketReader = mUpstreamReader.getReader();
 
         // Create tethered test iface.
-        mTetheredTestIface = setupTapInterface();
-        mTetheredParams = InterfaceParams.getByName(mTetheredTestIface.getInterfaceName());
+        mTetheredReader.start(mHandler);
+        mTetheredParams = InterfaceParams.getByName(mTetheredReader.getIface().getInterfaceName());
         assertNotNull(mTetheredParams);
-        mTetheredTapFd = mTetheredTestIface.getFileDescriptor().getFileDescriptor();
-        mTetheredPacketReader = new TapPacketReader(mHandler, mTetheredTapFd,
-                                                    DATA_BUFFER_LEN);
-        mHandler.post(mTetheredPacketReader::start);
+        mTetheredPacketReader = mTetheredReader.getReader();
     }
 
     private static final int IPV6_HEADER_LEN = 40;
@@ -177,31 +140,6 @@
     private static final int ICMPV6_CHECKSUM_OFFSET = 2;
     private static final int ETHER_TYPE_IPV6 = 0x86dd;
 
-    // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
-    private static int checksumFold(int sum) {
-        while (sum > 0xffff) {
-            sum = (sum >> 16) + (sum & 0xffff);
-        }
-        return sum;
-    }
-
-    // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
-    private static short checksumAdjust(short checksum, short oldWord, short newWord) {
-        checksum = (short) ~checksum;
-        int tempSum = checksumFold(uint16(checksum) + uint16(newWord) + 0xffff - uint16(oldWord));
-        return (short) ~tempSum;
-    }
-
-    // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
-    private static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset,
-            int transportLen) {
-        // The ICMPv6 checksum is the same as the TCP checksum, except the pseudo-header uses
-        // 58 (ICMPv6) instead of 6 (TCP). Calculate the TCP checksum, and then do an incremental
-        // checksum adjustment  for the change in the next header byte.
-        short checksum = IpUtils.tcpChecksum(buf, ipOffset, transportOffset, transportLen);
-        return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6);
-    }
-
     private static ByteBuffer createDadPacket(int type) {
         // Refer to buildArpPacket()
         int icmpLen = ICMPV6_NA_NS_LEN
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 191eb6e..da13e34 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -51,6 +51,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public final class PrivateAddressCoordinatorTest {
@@ -70,7 +73,17 @@
     private final Network mWifiNetwork = new Network(1);
     private final Network mMobileNetwork = new Network(2);
     private final Network mVpnNetwork = new Network(3);
-    private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork, mVpnNetwork};
+    private final Network mMobileNetwork2 = new Network(4);
+    private final Network mMobileNetwork3 = new Network(5);
+    private final Network mMobileNetwork4 = new Network(6);
+    private final Network mMobileNetwork5 = new Network(7);
+    private final Network mMobileNetwork6 = new Network(8);
+    private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork, mVpnNetwork,
+            mMobileNetwork2, mMobileNetwork3, mMobileNetwork4, mMobileNetwork5, mMobileNetwork6};
+    private final ArrayList<IpPrefix> mTetheringPrefixes = new ArrayList<>(Arrays.asList(
+            new IpPrefix("192.168.0.0/16"),
+            new IpPrefix("172.16.0.0/12"),
+            new IpPrefix("10.0.0.0/8")));
 
     private void setUpIpServers() throws Exception {
         when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB);
@@ -87,7 +100,8 @@
         when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks);
         when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(false);
         setUpIpServers();
-        mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
+        mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig,
+                mTetheringPrefixes));
     }
 
     @Test
@@ -117,28 +131,28 @@
     @Test
     public void testSanitizedAddress() throws Exception {
         int fakeSubAddr = 0x2b00; // 43.0.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
         LinkAddress actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, false /* useLastAddress */);
-        assertEquals(new LinkAddress("192.168.43.42/24"), actualAddress);
+        assertEquals(new LinkAddress("192.168.43.2/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2d01; // 45.1.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
         actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, false /* useLastAddress */);
-        assertEquals(new LinkAddress("192.168.45.42/24"), actualAddress);
+        assertEquals(new LinkAddress("192.168.45.2/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2eff; // 46.255.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
         actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, false /* useLastAddress */);
-        assertEquals(new LinkAddress("192.168.46.42/24"), actualAddress);
+        assertEquals(new LinkAddress("192.168.46.254/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2f05; // 47.5.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
         actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
@@ -148,7 +162,7 @@
     @Test
     public void testReservedPrefix() throws Exception {
         // - Test bluetooth prefix is reserved.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(mBluetoothAddress.getAddress().getAddress()));
         final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, false /* useLastAddress */);
@@ -157,7 +171,7 @@
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         // - Test previous enabled hotspot prefix(cached prefix) is reserved.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(hotspotAddress.getAddress().getAddress()));
         final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mUsbIpServer, false /* useLastAddress */);
@@ -167,7 +181,7 @@
         mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
 
         // - Test wifi p2p prefix is reserved.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
         final LinkAddress etherAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mEthernetIpServer, false /* useLastAddress */);
@@ -180,23 +194,22 @@
 
     @Test
     public void testRequestLastDownstreamAddress() throws Exception {
-        final int fakeHotspotSubAddr = 0x2b05;
-        final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
+        final int fakeHotspotSubAddr = 0x2b05; // 43.5
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
         final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, true /* useLastAddress */);
-        assertEquals("Wrong wifi prefix: ", predefinedPrefix, asIpPrefix(hotspotAddress));
+        assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
         when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddress);
 
         final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mUsbIpServer, true /* useLastAddress */);
-        assertNotEquals(predefinedPrefix, asIpPrefix(usbAddress));
+        assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.45.5/24"), usbAddress);
 
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
         mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
 
         final int newFakeSubAddr = 0x3c05;
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
 
         final LinkAddress newHotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, true /* useLastAddress */);
@@ -204,6 +217,18 @@
         final LinkAddress newUsbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mUsbIpServer, true /* useLastAddress */);
         assertEquals(usbAddress, newUsbAddress);
+
+        // BUG: the code should detect a conflict, but it doesn't.
+        // Regression introduced in r.android.com/168169687.
+        // Ensure conflict notification works when using cached address.
+        when(mHotspotIpServer.getAddress()).thenReturn(newHotspotAddress);
+        when(mUsbIpServer.getAddress()).thenReturn(usbAddress);
+        final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
+                new LinkAddress("192.168.88.23/16"), null,
+                makeNetworkCapabilities(TRANSPORT_WIFI));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+        verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
     }
 
     private UpstreamNetworkState buildUpstreamNetworkState(final Network network,
@@ -229,11 +254,10 @@
 
     @Test
     public void testNoConflictUpstreamPrefix() throws Exception {
-        final int fakeHotspotSubId = 43;
-        final int fakeHotspotSubAddr = 0x2b05;
+        final int fakeHotspotSubAddr = 0x2b05; // 43.5
         final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
         // Force always get subAddress "43.5" for conflict testing.
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
         // - Enable hotspot with prefix 192.168.43.0/24
         final LinkAddress hotspotAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
                 mHotspotIpServer, true /* useLastAddress */);
@@ -312,6 +336,209 @@
         assertEquals(predefinedPrefix, ethPrefix);
     }
 
+    @Test
+    public void testChooseAvailablePrefix() throws Exception {
+        final int randomAddress = 0x8605; // 134.5
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
+        final LinkAddress addr0 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.134.5.
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.134.5/24"), addr0);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr0);
+        final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
+                new LinkAddress("192.168.134.13/26"), null,
+                makeNetworkCapabilities(TRANSPORT_WIFI));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+
+        // Check whether return address is next prefix of 192.168.134.0/24.
+        final LinkAddress addr1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.135.5/24"), addr1);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr1);
+        final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork,
+                new LinkAddress("192.168.149.16/19"), null,
+                makeNetworkCapabilities(TRANSPORT_WIFI));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream2);
+
+
+        // The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
+        final LinkAddress addr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.160.5/24"), addr2);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr2);
+        final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
+                new LinkAddress("192.168.129.53/18"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        // Update another conflict upstream which is covered by the previous one (but not the first
+        // one) and verify whether this would affect the result.
+        final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
+                new LinkAddress("192.168.170.7/19"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
+
+        // The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
+        final LinkAddress addr3 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.192.5/24"), addr3);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr3);
+        final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
+                new LinkAddress("192.168.188.133/17"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
+
+        // Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because
+        // 192.168.134/24 ~ 192.168.255.255/24 is not available.
+        final LinkAddress addr4 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.0.5/24"), addr4);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr4);
+        final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
+                new LinkAddress("192.168.3.59/21"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
+
+        // Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
+        final LinkAddress addr5 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr5);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr5);
+        final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
+                new LinkAddress("192.168.68.43/21"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
+
+        // Update an upstream that does *not* conflict, check whether return the same address
+        // 192.168.5/24.
+        final LinkAddress addr6 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr6);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr6);
+        final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6,
+                new LinkAddress("192.168.10.97/21"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream6);
+
+        // Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
+        final LinkAddress addr7 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.16.5/24"), addr7);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr7);
+        final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6,
+                new LinkAddress("192.168.0.0/17"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream7);
+
+        // Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16.
+        final LinkAddress addr8 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8);
+        when(mHotspotIpServer.getAddress()).thenReturn(addr6);
+    }
+
+    @Test
+    public void testChoosePrefixFromDifferentRanges() throws Exception {
+        final int randomAddress = 0x1f2b2a; // 31.43.42
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
+        final LinkAddress classC1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.43.42.
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.43.42/24"), classC1);
+        when(mHotspotIpServer.getAddress()).thenReturn(classC1);
+        final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
+                new LinkAddress("192.168.88.23/17"), null,
+                makeNetworkCapabilities(TRANSPORT_WIFI));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+        verifyNotifyConflictAndRelease(mHotspotIpServer);
+
+        // Check whether return address is next address of prefix 192.168.128.0/17.
+        final LinkAddress classC2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("192.168.128.42/24"), classC2);
+        when(mHotspotIpServer.getAddress()).thenReturn(classC2);
+        final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
+                new LinkAddress("192.1.2.3/8"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
+        verifyNotifyConflictAndRelease(mHotspotIpServer);
+
+        // Check whether return address is under prefix 172.16.0.0/12.
+        final LinkAddress classB1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("172.31.43.42/24"), classB1);
+        when(mHotspotIpServer.getAddress()).thenReturn(classB1);
+        final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
+                new LinkAddress("172.28.123.100/14"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
+        verifyNotifyConflictAndRelease(mHotspotIpServer);
+
+        // 172.28.0.0 ~ 172.31.255.255 is not available.
+        // Check whether return address is next address of prefix 172.16.0.0/14.
+        final LinkAddress classB2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("172.16.0.42/24"), classB2);
+        when(mHotspotIpServer.getAddress()).thenReturn(classB2);
+
+        // Check whether new downstream is next address of address 172.16.0.42/24.
+        final LinkAddress classB3 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("172.16.1.42/24"), classB3);
+        when(mUsbIpServer.getAddress()).thenReturn(classB3);
+        final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
+                new LinkAddress("172.16.0.1/24"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
+        verifyNotifyConflictAndRelease(mHotspotIpServer);
+        verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+
+        // Check whether return address is next address of prefix 172.16.1.42/24.
+        final LinkAddress classB4 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("172.16.2.42/24"), classB4);
+        when(mHotspotIpServer.getAddress()).thenReturn(classB4);
+        final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
+                new LinkAddress("172.16.0.1/13"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
+        verifyNotifyConflictAndRelease(mHotspotIpServer);
+        verifyNotifyConflictAndRelease(mUsbIpServer);
+
+        // Check whether return address is next address of prefix 172.16.0.1/13.
+        final LinkAddress classB5 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("172.24.0.42/24"), classB5);
+        when(mHotspotIpServer.getAddress()).thenReturn(classB5);
+        // Check whether return address is next address of prefix 172.24.0.42/24.
+        final LinkAddress classB6 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("172.24.1.42/24"), classB6);
+        when(mUsbIpServer.getAddress()).thenReturn(classB6);
+        final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
+                new LinkAddress("172.24.0.1/12"), null,
+                makeNetworkCapabilities(TRANSPORT_CELLULAR));
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
+        verifyNotifyConflictAndRelease(mHotspotIpServer);
+        verifyNotifyConflictAndRelease(mUsbIpServer);
+
+        // Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42.
+        final LinkAddress classA1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("10.31.43.42/24"), classA1);
+        when(mHotspotIpServer.getAddress()).thenReturn(classA1);
+        // Check whether new downstream is next address of address 10.31.43.42/24.
+        final LinkAddress classA2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer, true/* useLastAddress */);
+        assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2);
+    }
+
+    private void verifyNotifyConflictAndRelease(final IpServer ipServer) throws Exception {
+        verify(ipServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        mPrivateAddressCoordinator.releaseDownstream(ipServer);
+        reset(ipServer);
+        setUpIpServers();
+    }
+
     private int getSubAddress(final byte... ipv4Address) {
         assertEquals(4, ipv4Address.length);
 
@@ -330,7 +557,7 @@
 
     @Test
     public void testEnableLegacyWifiP2PAddress() throws Exception {
-        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
         // No matter #shouldEnableWifiP2pDedicatedIp() is enabled or not, legacy wifi p2p prefix
         // is resevered.
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index df57020..20e94b2 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -24,6 +24,9 @@
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
@@ -179,6 +182,7 @@
     private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
     private static final String TEST_NCM_IFNAME = "test_ncm0";
     private static final String TEST_ETH_IFNAME = "test_eth0";
+    private static final String TEST_BT_IFNAME = "test_pan0";
     private static final String TETHERING_NAME = "Tethering";
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
@@ -230,6 +234,7 @@
     private TetheringConfiguration mConfig;
     private EntitlementManager mEntitleMgr;
     private OffloadController mOffloadCtrl;
+    private PrivateAddressCoordinator mPrivateAddressCoordinator;
 
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
@@ -446,6 +451,18 @@
         public boolean isTetheringDenied() {
             return false;
         }
+
+
+        @Override
+        public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
+                TetheringConfiguration cfg) {
+            final ArrayList<IpPrefix> prefixPool = new ArrayList<>(Arrays.asList(
+                    new IpPrefix("192.168.0.0/16"),
+                    new IpPrefix("172.16.0.0/12"),
+                    new IpPrefix("10.0.0.0/8")));
+            mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(ctx, cfg, prefixPool));
+            return mPrivateAddressCoordinator;
+        }
     }
 
     private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -1875,27 +1892,36 @@
         sendConfigurationChanged();
     }
 
-    private static UpstreamNetworkState buildV4WifiUpstreamState(final String ipv4Address,
-            final int prefixLength, final Network network) {
+    private static UpstreamNetworkState buildV4UpstreamState(final LinkAddress address,
+            final Network network, final String iface, final int transportType) {
         final LinkProperties prop = new LinkProperties();
-        prop.setInterfaceName(TEST_WIFI_IFNAME);
+        prop.setInterfaceName(iface);
 
-        prop.addLinkAddress(
-                new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address),
-                        prefixLength));
+        prop.addLinkAddress(address);
 
         final NetworkCapabilities capabilities = new NetworkCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+                .addTransportType(transportType);
         return new UpstreamNetworkState(prop, capabilities, network);
     }
 
+    private void updateV4Upstream(final LinkAddress ipv4Address, final Network network,
+            final String iface, final int transportType) {
+        final UpstreamNetworkState upstream = buildV4UpstreamState(ipv4Address, network, iface,
+                transportType);
+        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
+                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
+                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
+                0,
+                upstream);
+        mLooper.dispatchAll();
+    }
+
     @Test
     public void testHandleIpConflict() throws Exception {
         final Network wifiNetwork = new Network(200);
         final Network[] allNetworks = { wifiNetwork };
         when(mCm.getAllNetworks()).thenReturn(allNetworks);
-        UpstreamNetworkState upstreamNetwork = null;
-        runUsbTethering(upstreamNetwork);
+        runUsbTethering(null);
         final ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
                 ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
         verify(mNetd).interfaceSetCfg(ifaceConfigCaptor.capture());
@@ -1903,13 +1929,10 @@
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
                 any(), any());
         reset(mNetd, mUsbManager);
-        upstreamNetwork = buildV4WifiUpstreamState(ipv4Address, 30, wifiNetwork);
-        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
-                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
-                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
-                0,
-                upstreamNetwork);
-        mLooper.dispatchAll();
+
+        // Cause a prefix conflict by assigning a /30 out of the downstream's /24 to the upstream.
+        updateV4Upstream(new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address), 30),
+                wifiNetwork, TEST_WIFI_IFNAME, TRANSPORT_WIFI);
         // verify turn off usb tethering
         verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
         mTethering.interfaceRemoved(TEST_USB_IFNAME);
@@ -1921,9 +1944,10 @@
     @Test
     public void testNoAddressAvailable() throws Exception {
         final Network wifiNetwork = new Network(200);
-        final Network[] allNetworks = { wifiNetwork };
+        final Network btNetwork = new Network(201);
+        final Network mobileNetwork = new Network(202);
+        final Network[] allNetworks = { wifiNetwork, btNetwork, mobileNetwork };
         when(mCm.getAllNetworks()).thenReturn(allNetworks);
-        final String upstreamAddress = "192.168.0.100";
         runUsbTethering(null);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
                 any(), any());
@@ -1940,13 +1964,13 @@
         mLooper.dispatchAll();
         reset(mUsbManager, mEm);
 
-        final UpstreamNetworkState upstreamNetwork = buildV4WifiUpstreamState(
-                upstreamAddress, 16, wifiNetwork);
-        mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
-                Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
-                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
-                0,
-                upstreamNetwork);
+        updateV4Upstream(new LinkAddress("192.168.0.100/16"), wifiNetwork, TEST_WIFI_IFNAME,
+                TRANSPORT_WIFI);
+        updateV4Upstream(new LinkAddress("172.16.0.0/12"), btNetwork, TEST_BT_IFNAME,
+                TRANSPORT_BLUETOOTH);
+        updateV4Upstream(new LinkAddress("10.0.0.0/8"), mobileNetwork, TEST_MOBILE_IFNAME,
+                TRANSPORT_CELLULAR);
+
         mLooper.dispatchAll();
         // verify turn off usb tethering
         verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9ddf7a4..d71b919 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -941,10 +941,19 @@
             if (resolvedUserId != mCurrentUserId) {
                 return null;
             }
-            if (mA11yWindowManager.findA11yWindowInfoByIdLocked(windowId) == null) {
+            final AccessibilityWindowInfo accessibilityWindowInfo = mA11yWindowManager
+                    .findA11yWindowInfoByIdLocked(windowId);
+            if (accessibilityWindowInfo == null) {
                 return null;
             }
-            return mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+            // We use AccessibilityWindowInfo#getId instead of windowId. When the windowId comes
+            // from an embedded hierarchy, the system can't find correct window token because
+            // embedded hierarchy doesn't have windowInfo. Calling
+            // AccessibilityWindowManager#findA11yWindowInfoByIdLocked can look for its parent's
+            // windowInfo, so it is safer to use AccessibilityWindowInfo#getId
+            // to get window token to find real window.
+            return mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(userId,
+                    accessibilityWindowInfo.getId());
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index a860db3..14af8c6 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -19,6 +19,7 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
@@ -27,11 +28,13 @@
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
@@ -140,6 +143,9 @@
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this));
         mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTapAndHold(
@@ -153,9 +159,17 @@
                 new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTapAndHold(
+                        mContext, 3, 1, GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
                         mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, this));
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTapAndHold(
+                        mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD, this));
+        mMultiFingerGestures.add(
+                new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
         // Four-finger taps.
         mMultiFingerGestures.add(
                 new MultiFingerMultiTap(mContext, 4, 1, GESTURE_4_FINGER_SINGLE_TAP, this));
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
index e15c495..46b4628 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
@@ -162,6 +162,7 @@
         // Accept down only before target number of fingers are down
         // or the finger count is not more than target.
         if ((currentFingerCount > mTargetFingerCount) || mIsTargetFingerCountReached) {
+            mIsTargetFingerCountReached = false;
             cancelGesture(event, rawEvent, policyFlags);
             return;
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 6157004..032820d 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -391,7 +391,10 @@
 
             checkArgument(getCallingUserId() == userId,
                     "Must be called by either same user or system");
-            mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
+            int callingUid = Binder.getCallingUid();
+            if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) {
+                throw new SecurityException(pkg + " doesn't belong to uid " + callingUid);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b59f7645..bb9f6d2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -140,6 +140,7 @@
 import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
+import android.os.BasicShellCommandHandler;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -156,11 +157,8 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -7658,14 +7656,14 @@
     }
 
     @Override
-    public void onShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
-            FileDescriptor err, @NonNull String[] args, ShellCallback callback,
-            @NonNull ResultReceiver resultReceiver) {
-        (new ShellCmd()).exec(this, in, out, err, args, callback, resultReceiver);
+    public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+            @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+            @NonNull String[] args) {
+        return new ShellCmd().exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
+                err.getFileDescriptor(), args);
     }
 
-    private class ShellCmd extends ShellCommand {
-
+    private class ShellCmd extends BasicShellCommandHandler {
         @Override
         public int onCommand(String cmd) {
             if (cmd == null) {
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 0c34744..afddd65 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -33,7 +33,6 @@
 import android.hardware.input.InputManager;
 import android.hardware.vibrator.IVibrator;
 import android.hardware.vibrator.V1_0.EffectStrength;
-import android.icu.text.DateFormat;
 import android.media.AudioManager;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -80,6 +79,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -90,6 +90,8 @@
 public class VibratorService extends IVibratorService.Stub
         implements InputManager.InputDeviceListener {
     private static final String TAG = "VibratorService";
+    private static final SimpleDateFormat DEBUG_DATE_FORMAT =
+            new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
     private static final boolean DEBUG = false;
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
 
@@ -126,7 +128,7 @@
     private final LinkedList<VibrationInfo> mPreviousRingVibrations;
     private final LinkedList<VibrationInfo> mPreviousNotificationVibrations;
     private final LinkedList<VibrationInfo> mPreviousAlarmVibrations;
-    private final LinkedList<ExternalVibration> mPreviousExternalVibrations;
+    private final LinkedList<VibrationInfo> mPreviousExternalVibrations;
     private final LinkedList<VibrationInfo> mPreviousVibrations;
     private final int mPreviousVibrationsLimit;
     private final boolean mAllowPriorityVibrationsInLowPowerMode;
@@ -162,7 +164,7 @@
     @GuardedBy("mLock")
     private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
-    private ExternalVibration mCurrentExternalVibration;
+    private ExternalVibrationHolder mCurrentExternalVibration;
     private boolean mVibratorUnderExternalControl;
     private boolean mLowPowerMode;
     @GuardedBy("mLock")
@@ -231,19 +233,12 @@
         void onComplete(long vibrationId);
     }
 
-    /**
-     * Holder for a vibration to be played. This class can be shared with native methods for
-     * hardware callback support.
-     */
+    /** Holder for a {@link VibrationEffect}. */
     private final class Vibration implements IBinder.DeathRecipient {
 
         public final IBinder token;
         // Start time in CLOCK_BOOTTIME base.
         public final long startTime;
-        // Start time in unix epoch time. Only to be used for debugging purposes and to correlate
-        // with other system events, any duration calculations should be done use startTime so as
-        // not to be affected by discontinuities created by RTC adjustments.
-        public final long startTimeDebug;
         public final VibrationAttributes attrs;
         public final long id;
         public final int uid;
@@ -255,18 +250,28 @@
         // The original effect that was requested. Typically these two things differ because
         // the effect was scaled based on the users vibration intensity settings.
         public VibrationEffect originalEffect;
+        // The scale applied to the original effect.
+        public float scale;
+
+        // Start/end times in unix epoch time. Only to be used for debugging purposes and to
+        // correlate with other system events, any duration calculations should be done use
+        // startTime so as not to be affected by discontinuities created by RTC adjustments.
+        private final long mStartTimeDebug;
+        private long mEndTimeDebug;
+        private VibrationInfo.Status mStatus;
 
         private Vibration(IBinder token, VibrationEffect effect,
                 VibrationAttributes attrs, int uid, String opPkg, String reason) {
             this.token = token;
-            this.id = mNextVibrationId.getAndIncrement();
             this.effect = effect;
+            this.id = mNextVibrationId.getAndIncrement();
             this.startTime = SystemClock.elapsedRealtime();
-            this.startTimeDebug = System.currentTimeMillis();
             this.attrs = attrs;
             this.uid = uid;
             this.opPkg = opPkg;
             this.reason = reason;
+            mStartTimeDebug = System.currentTimeMillis();
+            mStatus = VibrationInfo.Status.RUNNING;
         }
 
         @Override
@@ -276,11 +281,24 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Vibration finished because binder died, cleaning up");
                     }
-                    doCancelVibrateLocked();
+                    doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
                 }
             }
         }
 
+        public void end(VibrationInfo.Status status) {
+            if (hasEnded()) {
+                // Vibration already ended, keep first ending status set and ignore this one.
+                return;
+            }
+            mStatus = status;
+            mEndTimeDebug = System.currentTimeMillis();
+        }
+
+        public boolean hasEnded() {
+            return mStatus != VibrationInfo.Status.RUNNING;
+        }
+
         public boolean hasTimeoutLongerThan(long millis) {
             final long duration = effect.getDuration();
             return duration >= 0 && duration > millis;
@@ -308,40 +326,109 @@
 
         public VibrationInfo toInfo() {
             return new VibrationInfo(
-                    startTimeDebug, effect, originalEffect, attrs, uid, opPkg, reason);
+                    mStartTimeDebug, mEndTimeDebug, effect, originalEffect, scale, attrs,
+                    uid, opPkg, reason, mStatus);
         }
     }
 
-    private static class VibrationInfo {
+    /** Holder for a {@link ExternalVibration}. */
+    private final class ExternalVibrationHolder {
+
+        public final ExternalVibration externalVibration;
+        public int scale;
+
         private final long mStartTimeDebug;
+        private long mEndTimeDebug;
+        private VibrationInfo.Status mStatus;
+
+        private ExternalVibrationHolder(ExternalVibration externalVibration) {
+            this.externalVibration = externalVibration;
+            this.scale = SCALE_NONE;
+            mStartTimeDebug = System.currentTimeMillis();
+            mStatus = VibrationInfo.Status.RUNNING;
+        }
+
+        public void end(VibrationInfo.Status status) {
+            if (mStatus != VibrationInfo.Status.RUNNING) {
+                // Vibration already ended, keep first ending status set and ignore this one.
+                return;
+            }
+            mStatus = status;
+            mEndTimeDebug = System.currentTimeMillis();
+        }
+
+        public VibrationInfo toInfo() {
+            return new VibrationInfo(
+                    mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
+                    scale, externalVibration.getVibrationAttributes(),
+                    externalVibration.getUid(), externalVibration.getPackage(),
+                    /* reason= */ null, mStatus);
+        }
+    }
+
+    /** Debug information about vibrations. */
+    private static class VibrationInfo {
+
+        public enum Status {
+            RUNNING,
+            FINISHED,
+            CANCELLED,
+            ERROR_APP_OPS,
+            IGNORED,
+            IGNORED_APP_OPS,
+            IGNORED_BACKGROUND,
+            IGNORED_RINGTONE,
+            IGNORED_UNKNOWN_VIBRATION,
+            IGNORED_UNSUPPORTED,
+            IGNORED_FOR_ALARM,
+            IGNORED_FOR_EXTERNAL,
+            IGNORED_FOR_ONGOING,
+            IGNORED_FOR_POWER,
+            IGNORED_FOR_SETTINGS,
+        }
+
+        private final long mStartTimeDebug;
+        private final long mEndTimeDebug;
         private final VibrationEffect mEffect;
         private final VibrationEffect mOriginalEffect;
+        private final float mScale;
         private final VibrationAttributes mAttrs;
         private final int mUid;
         private final String mOpPkg;
         private final String mReason;
+        private final VibrationInfo.Status mStatus;
 
-        VibrationInfo(long startTimeDebug, VibrationEffect effect,
-                VibrationEffect originalEffect, VibrationAttributes attrs, int uid,
-                String opPkg, String reason) {
+        VibrationInfo(long startTimeDebug, long endTimeDebug, VibrationEffect effect,
+                VibrationEffect originalEffect, float scale, VibrationAttributes attrs,
+                int uid, String opPkg, String reason, VibrationInfo.Status status) {
             mStartTimeDebug = startTimeDebug;
+            mEndTimeDebug = endTimeDebug;
             mEffect = effect;
             mOriginalEffect = originalEffect;
+            mScale = scale;
             mAttrs = attrs;
             mUid = uid;
             mOpPkg = opPkg;
             mReason = reason;
+            mStatus = status;
         }
 
         @Override
         public String toString() {
             return new StringBuilder()
                     .append("startTime: ")
-                    .append(DateFormat.getDateTimeInstance().format(new Date(mStartTimeDebug)))
+                    .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug)))
+                    .append(", endTime: ")
+                    .append(mEndTimeDebug == 0 ? null
+                            : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug)))
+                    .append(", status: ")
+                    .append(mStatus.name().toLowerCase())
                     .append(", effect: ")
                     .append(mEffect)
                     .append(", originalEffect: ")
                     .append(mOriginalEffect)
+                    .append(", scale: ")
+                    .append(String.format("%.2f", mScale))
                     .append(", attrs: ")
                     .append(mAttrs)
                     .append(", uid: ")
@@ -354,11 +441,80 @@
         }
 
         void dumpProto(ProtoOutputStream proto, long fieldId) {
-            synchronized (this) {
-                final long token = proto.start(fieldId);
-                proto.write(VibrationProto.START_TIME, mStartTimeDebug);
-                proto.end(token);
+            final long token = proto.start(fieldId);
+            proto.write(VibrationProto.START_TIME, mStartTimeDebug);
+            proto.write(VibrationProto.END_TIME, mEndTimeDebug);
+            proto.write(VibrationProto.STATUS, mStatus.ordinal());
+
+            final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
+            proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage());
+            proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage());
+            proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags());
+            proto.end(attrsToken);
+
+            if (mEffect != null) {
+                dumpEffect(proto, VibrationProto.EFFECT, mEffect);
             }
+            if (mOriginalEffect != null) {
+                dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect);
+            }
+
+            proto.end(token);
+        }
+
+        private void dumpEffect(ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
+            final long token = proto.start(fieldId);
+            if (effect instanceof VibrationEffect.OneShot) {
+                dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
+            } else if (effect instanceof VibrationEffect.Waveform) {
+                dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect);
+            } else if (effect instanceof VibrationEffect.Prebaked) {
+                dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect);
+            } else if (effect instanceof VibrationEffect.Composed) {
+                dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect);
+            }
+            proto.end(token);
+        }
+
+        private void dumpEffect(ProtoOutputStream proto, long fieldId,
+                VibrationEffect.OneShot effect) {
+            final long token = proto.start(fieldId);
+            proto.write(OneShotProto.DURATION, (int) effect.getDuration());
+            proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude());
+            proto.end(token);
+        }
+
+        private void dumpEffect(ProtoOutputStream proto, long fieldId,
+                VibrationEffect.Waveform effect) {
+            final long token = proto.start(fieldId);
+            for (long timing : effect.getTimings()) {
+                proto.write(WaveformProto.TIMINGS, (int) timing);
+            }
+            for (int amplitude : effect.getAmplitudes()) {
+                proto.write(WaveformProto.AMPLITUDES, amplitude);
+            }
+            proto.write(WaveformProto.REPEAT, effect.getRepeatIndex() >= 0);
+            proto.end(token);
+        }
+
+        private void dumpEffect(ProtoOutputStream proto, long fieldId,
+                VibrationEffect.Prebaked effect) {
+            final long token = proto.start(fieldId);
+            proto.write(PrebakedProto.EFFECT_ID, effect.getId());
+            proto.write(PrebakedProto.EFFECT_STRENGTH, effect.getEffectStrength());
+            proto.write(PrebakedProto.FALLBACK, effect.shouldFallback());
+            proto.end(token);
+        }
+
+        private void dumpEffect(ProtoOutputStream proto, long fieldId,
+                VibrationEffect.Composed effect) {
+            final long token = proto.start(fieldId);
+            for (PrimitiveEffect primitive : effect.getPrimitiveEffects()) {
+                proto.write(ComposedProto.EFFECT_IDS, primitive.id);
+                proto.write(ComposedProto.EFFECT_SCALES, primitive.scale);
+                proto.write(ComposedProto.DELAYS, primitive.delay);
+            }
+            proto.end(token);
         }
     }
 
@@ -549,7 +705,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration finished by callback, cleaning up");
                 }
-                doCancelVibrateLocked();
+                doCancelVibrateLocked(VibrationInfo.Status.FINISHED);
             }
         }
     }
@@ -792,6 +948,7 @@
             }
 
             attrs = fixupVibrationAttributes(attrs);
+            Vibration vib = new Vibration(token, effect, attrs, uid, opPkg, reason);
 
             // If our current vibration is longer than the new vibration and is the same amplitude,
             // then just let the current one finish.
@@ -808,6 +965,7 @@
                             Slog.d(TAG,
                                     "Ignoring incoming vibration in favor of current vibration");
                         }
+                        endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_ONGOING);
                         return;
                     }
                 }
@@ -819,6 +977,7 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
                     }
+                    endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_EXTERNAL);
                     return;
                 }
 
@@ -832,24 +991,29 @@
                     if (DEBUG) {
                         Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
                     }
+                    endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_ALARM);
                     return;
                 }
 
-                Vibration vib = new Vibration(token, effect, attrs, uid, opPkg, reason);
                 if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
                         > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
                         && !vib.isNotification() && !vib.isRingtone() && !vib.isAlarm()) {
                     Slog.e(TAG, "Ignoring incoming vibration as process with"
                             + " uid= " + uid + " is background,"
                             + " attrs= " + vib.attrs);
+                    endVibrationLocked(vib, VibrationInfo.Status.IGNORED_BACKGROUND);
                     return;
                 }
                 linkVibration(vib);
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    doCancelVibrateLocked();
+                    doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
                     startVibrationLocked(vib);
-                    addToPreviousVibrationsLocked(vib);
+
+                    if (!vib.hasEnded() && mCurrentVibration.id != vib.id) {
+                        // Vibration was unexpectedly ignored: add to list for debugging
+                        endVibrationLocked(vib, VibrationInfo.Status.IGNORED);
+                    }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -868,7 +1032,7 @@
         return effect.getDuration() == Long.MAX_VALUE;
     }
 
-    private void addToPreviousVibrationsLocked(Vibration vib) {
+    private void endVibrationLocked(Vibration vib, VibrationInfo.Status status) {
         final LinkedList<VibrationInfo> previousVibrations;
         if (vib.isRingtone()) {
             previousVibrations = mPreviousRingVibrations;
@@ -883,9 +1047,18 @@
         if (previousVibrations.size() > mPreviousVibrationsLimit) {
             previousVibrations.removeFirst();
         }
+        vib.end(status);
         previousVibrations.addLast(vib.toInfo());
     }
 
+    private void endVibrationLocked(ExternalVibrationHolder vib, VibrationInfo.Status status) {
+        if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
+            mPreviousExternalVibrations.removeFirst();
+        }
+        vib.end(status);
+        mPreviousExternalVibrations.addLast(vib.toInfo());
+    }
+
     @Override // Binder call
     public void cancelVibrate(IBinder token) {
         mContext.enforceCallingOrSelfPermission(
@@ -899,7 +1072,7 @@
                 }
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    doCancelVibrateLocked();
+                    doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -908,7 +1081,7 @@
     }
 
     @GuardedBy("mLock")
-    private void doCancelVibrateLocked() {
+    private void doCancelVibrateLocked(VibrationInfo.Status status) {
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
         try {
@@ -917,12 +1090,13 @@
                 mThread = null;
             }
             if (mCurrentExternalVibration != null) {
-                mCurrentExternalVibration.mute();
+                endVibrationLocked(mCurrentExternalVibration, status);
+                mCurrentExternalVibration.externalVibration.mute();
                 mCurrentExternalVibration = null;
                 setVibratorUnderExternalControl(false);
             }
             doVibratorOff();
-            reportFinishVibrationLocked();
+            reportFinishVibrationLocked(status);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -936,7 +1110,7 @@
         synchronized (mLock) {
             // Make sure the vibration is really done. This also reports that the vibration is
             // finished.
-            doCancelVibrateLocked();
+            doCancelVibrateLocked(VibrationInfo.Status.FINISHED);
         }
     }
 
@@ -944,7 +1118,7 @@
     private void startVibrationLocked(final Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            final int intensity = getCurrentIntensityLocked(vib);
+            final int intensity = getCurrentIntensityLocked(vib.attrs.getUsage());
             if (!shouldVibrate(vib, intensity)) {
                 return;
             }
@@ -959,6 +1133,7 @@
     private void startVibrationInnerLocked(Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
         try {
+            // Set current vibration before starting it, so callback will work.
             mCurrentVibration = vib;
             if (vib.effect instanceof VibrationEffect.OneShot) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -971,18 +1146,21 @@
             } else if (vib.effect instanceof VibrationEffect.Prebaked) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 doVibratorPrebakedEffectLocked(vib);
-            } else if (vib.effect instanceof  VibrationEffect.Composed) {
+            } else if (vib.effect instanceof VibrationEffect.Composed) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 doVibratorComposedEffectLocked(vib);
             } else {
                 Slog.e(TAG, "Unknown vibration type, ignoring");
+                endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNKNOWN_VIBRATION);
+                // The set current vibration is not actually playing, so drop it.
+                mCurrentVibration = null;
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
-    private boolean isAllowedToVibrateLocked(Vibration vib) {
+    private boolean shouldVibrateForPowerModeLocked(Vibration vib) {
         if (!mLowPowerMode) {
             return true;
         }
@@ -993,14 +1171,28 @@
                 || usage == VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
     }
 
-    private int getCurrentIntensityLocked(Vibration vib) {
-        if (vib.isRingtone()) {
+    private int getCurrentIntensityLocked(int usageHint) {
+        if (isRingtone(usageHint)) {
             return mRingIntensity;
-        } else if (vib.isNotification()) {
+        } else if (isNotification(usageHint)) {
             return mNotificationIntensity;
-        } else if (vib.isHapticFeedback()) {
+        } else if (isHapticFeedback(usageHint)) {
             return mHapticFeedbackIntensity;
-        } else if (vib.isAlarm()) {
+        } else if (isAlarm(usageHint)) {
+            return Vibrator.VIBRATION_INTENSITY_HIGH;
+        } else {
+            return Vibrator.VIBRATION_INTENSITY_MEDIUM;
+        }
+    }
+
+    private int getDefaultIntensity(int usageHint) {
+        if (isRingtone(usageHint)) {
+            return mVibrator.getDefaultRingVibrationIntensity();
+        } else if (isNotification(usageHint)) {
+            return mVibrator.getDefaultNotificationVibrationIntensity();
+        } else if (isHapticFeedback(usageHint)) {
+            return mVibrator.getDefaultHapticFeedbackIntensity();
+        } else if (isAlarm(usageHint)) {
             return Vibrator.VIBRATION_INTENSITY_HIGH;
         } else {
             return Vibrator.VIBRATION_INTENSITY_MEDIUM;
@@ -1019,21 +1211,7 @@
             return;
         }
 
-        final int defaultIntensity;
-        if (vib.isRingtone()) {
-            defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
-        } else if (vib.isNotification()) {
-            defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
-        } else if (vib.isHapticFeedback()) {
-            defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
-        } else if (vib.isAlarm()) {
-            defaultIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
-        } else {
-            // If we don't know what kind of vibration we're playing then just skip scaling for
-            // now.
-            return;
-        }
-
+        final int defaultIntensity = getDefaultIntensity(vib.attrs.getUsage());
         final ScaleLevel scale = mScaleLevels.get(intensity - defaultIntensity);
         if (scale == null) {
             // We should have scaling levels for all cases, so not being able to scale because of a
@@ -1045,6 +1223,7 @@
 
         vib.originalEffect = vib.effect;
         vib.effect = vib.effect.resolve(mDefaultVibrationAmplitude).scale(scale.factor);
+        vib.scale = scale.factor;
     }
 
     private boolean shouldVibrateForRingtone() {
@@ -1084,11 +1263,13 @@
     }
 
     private boolean shouldVibrate(Vibration vib, int intensity) {
-        if (!isAllowedToVibrateLocked(vib)) {
+        if (!shouldVibrateForPowerModeLocked(vib)) {
+            endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_POWER);
             return false;
         }
 
         if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_SETTINGS);
             return false;
         }
 
@@ -1096,6 +1277,7 @@
             if (DEBUG) {
                 Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
             }
+            endVibrationLocked(vib, VibrationInfo.Status.IGNORED_RINGTONE);
             return false;
         }
 
@@ -1105,6 +1287,9 @@
                 // We might be getting calls from within system_server, so we don't actually
                 // want to throw a SecurityException here.
                 Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
+                endVibrationLocked(vib, VibrationInfo.Status.ERROR_APP_OPS);
+            } else {
+                endVibrationLocked(vib, VibrationInfo.Status.IGNORED_APP_OPS);
             }
             return false;
         }
@@ -1113,10 +1298,11 @@
     }
 
     @GuardedBy("mLock")
-    private void reportFinishVibrationLocked() {
+    private void reportFinishVibrationLocked(VibrationInfo.Status status) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         try {
             if (mCurrentVibration != null) {
+                endVibrationLocked(mCurrentVibration, status);
                 mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
                         mCurrentVibration.opPkg);
                 unlinkVibration(mCurrentVibration);
@@ -1153,7 +1339,7 @@
 
             if (devicesUpdated || lowPowerModeUpdated) {
                 // If the state changes out from under us then just reset.
-                doCancelVibrateLocked();
+                doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
             }
 
             updateAlwaysOnLocked();
@@ -1224,7 +1410,7 @@
     }
 
     private void updateAlwaysOnLocked(int id, Vibration vib) {
-        final int intensity = getCurrentIntensityLocked(vib);
+        final int intensity = getCurrentIntensityLocked(vib.attrs.getUsage());
         if (!shouldVibrate(vib, intensity)) {
             mNativeWrapper.vibratorAlwaysOnDisable(id);
         } else {
@@ -1345,6 +1531,10 @@
                     return;
                 }
             }
+            endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
+            // The set current vibration is not actually playing, so drop it.
+            mCurrentVibration = null;
+
             if (!prebaked.shouldFallback()) {
                 return;
             }
@@ -1355,11 +1545,12 @@
             }
             Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
                     vib.opPkg, vib.reason + " (fallback)");
-            final int intensity = getCurrentIntensityLocked(fallbackVib);
+            // Set current vibration before starting it, so callback will work.
+            mCurrentVibration = fallbackVib;
+            final int intensity = getCurrentIntensityLocked(fallbackVib.attrs.getUsage());
             linkVibration(fallbackVib);
             applyVibrationIntensityScalingLocked(fallbackVib, intensity);
             startVibrationInnerLocked(fallbackVib);
-            return;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -1376,11 +1567,10 @@
                 usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
             }
             // Input devices don't support composed effect, so skip trying it with them.
-            if (usingInputDeviceVibrators) {
-                return;
-            }
-
-            if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+            if (usingInputDeviceVibrators || !hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+                endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
+                // The set current vibration is not actually playing, so drop it.
+                mCurrentVibration = null;
                 return;
             }
 
@@ -1489,15 +1679,25 @@
             } else {
                 pw.println("null");
             }
-            pw.print("  mCurrentExternalVibration=" + mCurrentExternalVibration);
+            pw.print("  mCurrentExternalVibration=");
+            if (mCurrentExternalVibration != null) {
+                pw.println(mCurrentExternalVibration.toInfo().toString());
+            } else {
+                pw.println("null");
+            }
             pw.println("  mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
             pw.println("  mIsVibrating=" + mIsVibrating);
-            pw.println("  mVibratorStateListeners Count=" +
-                            mVibratorStateListeners.getRegisteredCallbackCount());
+            pw.println("  mVibratorStateListeners Count="
+                    + mVibratorStateListeners.getRegisteredCallbackCount());
             pw.println("  mLowPowerMode=" + mLowPowerMode);
             pw.println("  mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
+            pw.println("  mHapticFeedbackDefaultIntensity="
+                    + mVibrator.getDefaultHapticFeedbackIntensity());
             pw.println("  mNotificationIntensity=" + mNotificationIntensity);
+            pw.println("  mNotificationDefaultIntensity="
+                    + mVibrator.getDefaultNotificationVibrationIntensity());
             pw.println("  mRingIntensity=" + mRingIntensity);
+            pw.println("  mRingDefaultIntensity=" + mVibrator.getDefaultRingVibrationIntensity());
             pw.println("  mSupportedEffects=" + mSupportedEffects);
             pw.println("  mSupportedPrimitives=" + mSupportedPrimitives);
             pw.println();
@@ -1523,8 +1723,8 @@
             }
 
             pw.println("  Previous external vibrations:");
-            for (ExternalVibration vib : mPreviousExternalVibrations) {
-                pw.println("    " + vib);
+            for (VibrationInfo info : mPreviousExternalVibrations) {
+                pw.println("    " + info);
             }
         }
     }
@@ -1535,36 +1735,45 @@
         synchronized (mLock) {
             if (mCurrentVibration != null) {
                 mCurrentVibration.toInfo().dumpProto(proto,
-                    VibratorServiceDumpProto.CURRENT_VIBRATION);
+                        VibratorServiceDumpProto.CURRENT_VIBRATION);
+            }
+            if (mCurrentExternalVibration != null) {
+                mCurrentExternalVibration.toInfo().dumpProto(proto,
+                        VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
             }
             proto.write(VibratorServiceDumpProto.IS_VIBRATING, mIsVibrating);
             proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
-                mVibratorUnderExternalControl);
+                    mVibratorUnderExternalControl);
             proto.write(VibratorServiceDumpProto.LOW_POWER_MODE, mLowPowerMode);
             proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
-                mHapticFeedbackIntensity);
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
-                mNotificationIntensity);
+                    mHapticFeedbackIntensity);
+            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultHapticFeedbackIntensity());
+            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY, mNotificationIntensity);
+            proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultNotificationVibrationIntensity());
             proto.write(VibratorServiceDumpProto.RING_INTENSITY, mRingIntensity);
+            proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultRingVibrationIntensity());
 
             for (VibrationInfo info : mPreviousRingVibrations) {
-                info.dumpProto(proto,
-                    VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
+                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
             }
 
             for (VibrationInfo info : mPreviousNotificationVibrations) {
-                info.dumpProto(proto,
-                    VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
+                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
             }
 
             for (VibrationInfo info : mPreviousAlarmVibrations) {
-                info.dumpProto(proto,
-                    VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
+                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
             }
 
             for (VibrationInfo info : mPreviousVibrations) {
-                info.dumpProto(proto,
-                    VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+            }
+
+            for (VibrationInfo info : mPreviousExternalVibrations) {
+                info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
             }
         }
         proto.flush();
@@ -1579,8 +1788,8 @@
 
         VibrateWaveformThread(Vibration vib) {
             mWaveform = (VibrationEffect.Waveform) vib.effect;
-            mVibration = new Vibration(vib.token, /* effect= */ null, vib.attrs, vib.uid, vib.opPkg,
-                    vib.reason);
+            mVibration = new Vibration(vib.token, /* effect= */ null, vib.attrs, vib.uid,
+                    vib.opPkg, vib.reason);
             mTmpWorkSource.set(vib.uid);
             mWakeLock.setWorkSource(mTmpWorkSource);
         }
@@ -1655,8 +1864,8 @@
                                     // appropriate intervals.
                                     onDuration = getTotalOnDuration(timings, amplitudes, index - 1,
                                             repeat);
-                                    mVibration.effect =
-                                            VibrationEffect.createOneShot(onDuration, amplitude);
+                                    mVibration.effect = VibrationEffect.createOneShot(
+                                            onDuration, amplitude);
                                     doVibratorOn(mVibration);
                                 } else {
                                     doVibratorSetAmplitude(amplitude);
@@ -1827,7 +2036,7 @@
                     if (mCurrentVibration != null
                             && !(mCurrentVibration.isHapticFeedback()
                                 && mCurrentVibration.isFromSystem())) {
-                        doCancelVibrateLocked();
+                        doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
                     }
                 }
             }
@@ -1882,63 +2091,54 @@
 
             int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
             if (mode != AppOpsManager.MODE_ALLOWED) {
+                ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+                vibHolder.scale = SCALE_MUTE;
                 if (mode == AppOpsManager.MODE_ERRORED) {
                     Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+                    endVibrationLocked(vibHolder, VibrationInfo.Status.ERROR_APP_OPS);
+                } else {
+                    endVibrationLocked(vibHolder, VibrationInfo.Status.IGNORED_APP_OPS);
                 }
                 return SCALE_MUTE;
             }
 
             final int scaleLevel;
             synchronized (mLock) {
-                if (!vib.equals(mCurrentExternalVibration)) {
-                    if (mCurrentExternalVibration == null) {
-                        // If we're not under external control right now, then cancel any normal
-                        // vibration that may be playing and ready the vibrator for external
-                        // control.
-                        doCancelVibrateLocked();
-                        setVibratorUnderExternalControl(true);
-                    }
-                    // At this point we either have an externally controlled vibration playing, or
-                    // no vibration playing. Since the interface defines that only one externally
-                    // controlled vibration can play at a time, by returning something other than
-                    // SCALE_MUTE from this function we can be assured that if we are currently
-                    // playing vibration, it will be muted in favor of the new vibration.
-                    //
-                    // Note that this doesn't support multiple concurrent external controls, as we
-                    // would need to mute the old one still if it came from a different controller.
-                    mCurrentExternalVibration = vib;
-                    mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
-                    mCurrentExternalVibration.linkToDeath(mCurrentExternalDeathRecipient);
-                    if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
-                        mPreviousExternalVibrations.removeFirst();
-                    }
-                    mPreviousExternalVibrations.addLast(vib);
-                    if (DEBUG) {
-                        Slog.e(TAG, "Playing external vibration: " + vib);
-                    }
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
+                    // We are already playing this external vibration, so we can return the same
+                    // scale calculated in the previous call to this method.
+                    return mCurrentExternalVibration.scale;
                 }
-                final int usage = vib.getVibrationAttributes().getUsage();
-                final int defaultIntensity;
-                final int currentIntensity;
-                if (isRingtone(usage)) {
-                    defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
-                    currentIntensity = mRingIntensity;
-                } else if (isNotification(usage)) {
-                    defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
-                    currentIntensity = mNotificationIntensity;
-                } else if (isHapticFeedback(usage)) {
-                    defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
-                    currentIntensity = mHapticFeedbackIntensity;
-                } else if (isAlarm(usage)) {
-                    defaultIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
-                    currentIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
+                if (mCurrentExternalVibration == null) {
+                    // If we're not under external control right now, then cancel any normal
+                    // vibration that may be playing and ready the vibrator for external control.
+                    doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
+                    setVibratorUnderExternalControl(true);
                 } else {
-                    defaultIntensity = 0;
-                    currentIntensity = 0;
+                    endVibrationLocked(mCurrentExternalVibration, VibrationInfo.Status.CANCELLED);
                 }
+                // At this point we either have an externally controlled vibration playing, or
+                // no vibration playing. Since the interface defines that only one externally
+                // controlled vibration can play at a time, by returning something other than
+                // SCALE_MUTE from this function we can be assured that if we are currently
+                // playing vibration, it will be muted in favor of the new vibration.
+                //
+                // Note that this doesn't support multiple concurrent external controls, as we
+                // would need to mute the old one still if it came from a different controller.
+                mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+                mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
+                vib.linkToDeath(mCurrentExternalDeathRecipient);
+                if (DEBUG) {
+                    Slog.e(TAG, "Playing external vibration: " + vib);
+                }
+                int usage = vib.getVibrationAttributes().getUsage();
+                int defaultIntensity = getDefaultIntensity(usage);
+                int currentIntensity = getCurrentIntensityLocked(usage);
                 scaleLevel = currentIntensity - defaultIntensity;
             }
             if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
+                mCurrentExternalVibration.scale = scaleLevel;
                 return scaleLevel;
             } else {
                 // Presumably we want to play this but something about our scaling has gone
@@ -1952,22 +2152,42 @@
         @Override
         public void onExternalVibrationStop(ExternalVibration vib) {
             synchronized (mLock) {
-                if (vib.equals(mCurrentExternalVibration)) {
-                    mCurrentExternalVibration.unlinkToDeath(mCurrentExternalDeathRecipient);
-                    mCurrentExternalDeathRecipient = null;
-                    mCurrentExternalVibration = null;
-                    setVibratorUnderExternalControl(false);
+                if (mCurrentExternalVibration != null
+                        && mCurrentExternalVibration.externalVibration.equals(vib)) {
                     if (DEBUG) {
                         Slog.e(TAG, "Stopping external vibration" + vib);
                     }
+                    doCancelExternalVibrateLocked(VibrationInfo.Status.FINISHED);
                 }
             }
         }
 
+        private void doCancelExternalVibrateLocked(VibrationInfo.Status status) {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked");
+            try {
+                if (mCurrentExternalVibration == null) {
+                    return;
+                }
+                endVibrationLocked(mCurrentExternalVibration, status);
+                mCurrentExternalVibration.externalVibration.unlinkToDeath(
+                        mCurrentExternalDeathRecipient);
+                mCurrentExternalDeathRecipient = null;
+                mCurrentExternalVibration = null;
+                setVibratorUnderExternalControl(false);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
+        }
+
         private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
             public void binderDied() {
                 synchronized (mLock) {
-                    onExternalVibrationStop(mCurrentExternalVibration);
+                    if (mCurrentExternalVibration != null) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "External vibration finished because binder died");
+                        }
+                        doCancelExternalVibrateLocked(VibrationInfo.Status.CANCELLED);
+                    }
                 }
             }
         }
@@ -2229,5 +2449,4 @@
             }
         }
     }
-
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b2e021f..0f095ab 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -558,11 +558,18 @@
                 }
                 if (r.mAllowStartForeground == FGS_FEATURE_DENIED
                         && mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
-                    Slog.w(TAG, "startForegroundService() not allowed due to "
-                                    + "mAllowStartForeground false: service "
-                                    + r.shortInstanceName);
-                    showFgsBgRestrictedNotificationLocked(r);
-                    forcedStandby = true;
+                    if (mAm.mConstants.mFlagFgsStartTempAllowListEnabled
+                            && mAm.isOnDeviceIdleWhitelistLocked(r.appInfo.uid, false)) {
+                        // uid is on DeviceIdleController's allowlist.
+                        Slog.d(TAG, "startForegroundService() mAllowStartForeground false "
+                                + "but allowlist true: service " + r.shortInstanceName);
+                    } else {
+                        Slog.w(TAG, "startForegroundService() not allowed due to "
+                                + "mAllowStartForeground false: service "
+                                + r.shortInstanceName);
+                        showFgsBgRestrictedNotificationLocked(r);
+                        return null;
+                    }
                 }
             }
         }
@@ -1462,13 +1469,20 @@
                         }
                         if (r.mAllowStartForeground == FGS_FEATURE_DENIED
                                 && mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
-                            Slog.w(TAG,
-                                    "Service.startForeground() not allowed due to "
-                                            + "mAllowStartForeground false: service "
-                                            + r.shortInstanceName);
-                            showFgsBgRestrictedNotificationLocked(r);
-                            updateServiceForegroundLocked(r.app, true);
-                            ignoreForeground = true;
+                            if (mAm.mConstants.mFlagFgsStartTempAllowListEnabled
+                                    && mAm.isOnDeviceIdleWhitelistLocked(r.appInfo.uid, false)) {
+                                // uid is on DeviceIdleController's allowlist.
+                                Slog.d(TAG, "Service.startForeground() "
+                                        + "mAllowStartForeground false but allowlist true: service "
+                                        + r.shortInstanceName);
+                            } else {
+                                Slog.w(TAG, "Service.startForeground() not allowed due to "
+                                                + "mAllowStartForeground false: service "
+                                                + r.shortInstanceName);
+                                showFgsBgRestrictedNotificationLocked(r);
+                                updateServiceForegroundLocked(r.app, true);
+                                ignoreForeground = true;
+                            }
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 48055b5..b54a917e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -161,6 +161,13 @@
     private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
             "default_fgs_starts_restriction_enabled";
 
+    /**
+     * Default value for mFlagFgsStartTempAllowListEnabled if not explicitly set in
+     * Settings.Global.
+     */
+    private static final String KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED =
+            "default_fgs_starts_temp_allowlist_enabled";
+
     // Maximum number of cached processes we will allow.
     public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
 
@@ -321,6 +328,10 @@
     // at all.
     volatile boolean mFlagFgsStartRestrictionEnabled = false;
 
+    // When the foreground service background start restriction is enabled, if the app in
+    // DeviceIdleController's Temp AllowList is allowed to bypass the restriction.
+    volatile boolean mFlagFgsStartTempAllowListEnabled = false;
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -472,6 +483,9 @@
                             case KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED:
                                 updateFgsStartsRestriction();
                                 break;
+                            case KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED:
+                                updateFgsStartsTempAllowList();
+                                break;
                             case KEY_OOMADJ_UPDATE_POLICY:
                                 updateOomAdjUpdatePolicy();
                                 break;
@@ -724,6 +738,13 @@
                 /*defaultValue*/ false);
     }
 
+    private void updateFgsStartsTempAllowList() {
+        mFlagFgsStartTempAllowListEnabled = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED,
+                /*defaultValue*/ false);
+    }
+
     private void updateOomAdjUpdatePolicy() {
         OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 0d077c6..68cfc23 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -26,6 +26,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.attention.AttentionManagerInternal;
 import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
 import android.content.BroadcastReceiver;
@@ -68,6 +69,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * An attention service implementation that runs in System Server process.
@@ -81,21 +83,15 @@
     /** Service will unbind if connection is not used for that amount of time. */
     private static final long CONNECTION_TTL_MILLIS = 60_000;
 
-    /**
-     * We cache the DeviceConfig values to avoid frequent ashmem-related checks; if the cached
-     * values are stale for more than this duration we will update the cache.
-     */
-    @VisibleForTesting static final long DEVICE_CONFIG_MAX_STALENESS_MILLIS = 4 * 60 * 60 * 1000L;
-
-    @VisibleForTesting long mLastReadDeviceConfigMillis = Long.MIN_VALUE;
-
     /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
-    private static final String KEY_SERVICE_ENABLED = "service_enabled";
+    @VisibleForTesting
+    static final String KEY_SERVICE_ENABLED = "service_enabled";
 
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
 
-    private boolean mIsServiceEnabledCached;
+    @VisibleForTesting
+    boolean mIsServiceEnabled;
 
     /**
      * DeviceConfig flag name, describes how much time we consider a result fresh; if the check
@@ -108,7 +104,8 @@
     @VisibleForTesting
     static final long DEFAULT_STALE_AFTER_MILLIS = 1_000;
 
-    private long mStaleAfterMillisCached;
+    @VisibleForTesting
+    long mStaleAfterMillis;
 
     /** The size of the buffer that stores recent attention check results. */
     @VisibleForTesting
@@ -156,6 +153,11 @@
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             mContext.registerReceiver(new ScreenStateReceiver(),
                     new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
+            readValuesFromDeviceConfig();
+            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                    ActivityThread.currentApplication().getMainExecutor(),
+                    (properties) -> onDeviceConfigChange(properties.getKeyset()));
         }
     }
 
@@ -179,17 +181,9 @@
         return mComponentName != null;
     }
 
-    /**
-     * Returns {@code true} if attention service is supported on this device.
-     */
-    @VisibleForTesting
-    protected boolean isAttentionServiceSupported() {
-        return isServiceEnabled();
-    }
-
-    private boolean isServiceEnabled() {
-        ensureDeviceConfigCachedValuesFreshness();
-        return mIsServiceEnabledCached;
+    private boolean getIsServiceEnabled() {
+        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_SERVICE_ENABLED,
+                DEFAULT_SERVICE_ENABLED);
     }
 
     /**
@@ -198,39 +192,38 @@
      */
     @VisibleForTesting
     protected long getStaleAfterMillis() {
-        ensureDeviceConfigCachedValuesFreshness();
-        return mStaleAfterMillisCached;
-    }
-
-    @VisibleForTesting
-    protected void ensureDeviceConfigCachedValuesFreshness() {
-        final long now = SystemClock.elapsedRealtime();
-        final long whenBecomesStale =
-                mLastReadDeviceConfigMillis + DEVICE_CONFIG_MAX_STALENESS_MILLIS;
-        if (now < whenBecomesStale) {
-            if (DEBUG) {
-                Slog.d(LOG_TAG,
-                        "Cached values are still fresh. Refreshed at=" + mLastReadDeviceConfigMillis
-                                + ", now=" + now);
-            }
-            return;
-        }
-
-        mIsServiceEnabledCached = DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
-                KEY_SERVICE_ENABLED,
-                DEFAULT_SERVICE_ENABLED);
-
         final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
                 KEY_STALE_AFTER_MILLIS,
                 DEFAULT_STALE_AFTER_MILLIS);
+
         if (millis < 0 || millis > 10_000) {
             Slog.w(LOG_TAG, "Bad flag value supplied for: " + KEY_STALE_AFTER_MILLIS);
-            mStaleAfterMillisCached = DEFAULT_STALE_AFTER_MILLIS;
-        } else {
-            mStaleAfterMillisCached = millis;
+            return DEFAULT_STALE_AFTER_MILLIS;
         }
 
-        mLastReadDeviceConfigMillis = now;
+        return millis;
+    }
+
+    private void onDeviceConfigChange(@NonNull Set<String> keys) {
+        for (String key : keys) {
+            switch (key) {
+                case KEY_SERVICE_ENABLED:
+                case KEY_STALE_AFTER_MILLIS:
+                    readValuesFromDeviceConfig();
+                    return;
+                default:
+                    Slog.i(LOG_TAG, "Ignoring change on " + key);
+            }
+        }
+    }
+
+    private void readValuesFromDeviceConfig() {
+        mIsServiceEnabled = getIsServiceEnabled();
+        mStaleAfterMillis = getStaleAfterMillis();
+
+        Slog.i(LOG_TAG, "readValuesFromDeviceConfig():"
+                + "\nmIsServiceEnabled=" + mIsServiceEnabled
+                + "\nmStaleAfterMillis=" + mStaleAfterMillis);
     }
 
     /**
@@ -246,7 +239,7 @@
     boolean checkAttention(long timeout, AttentionCallbackInternal callbackInternal) {
         Objects.requireNonNull(callbackInternal);
 
-        if (!isAttentionServiceSupported()) {
+        if (!mIsServiceEnabled) {
             Slog.w(LOG_TAG, "Trying to call checkAttention() on an unsupported device.");
             return false;
         }
@@ -272,7 +265,7 @@
             // throttle frequent requests
             final AttentionCheckCache cache = mAttentionCheckCacheBuffer == null ? null
                     : mAttentionCheckCacheBuffer.getLast();
-            if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
+            if (cache != null && now < cache.mLastComputed + mStaleAfterMillis) {
                 callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
                 return true;
             }
@@ -379,7 +372,8 @@
 
     private void dumpInternal(IndentingPrintWriter ipw) {
         ipw.println("Attention Manager Service (dumpsys attention) state:\n");
-        ipw.println("isServiceEnabled=" + isServiceEnabled());
+        ipw.println("isServiceEnabled=" + mIsServiceEnabled);
+        ipw.println("mStaleAfterMillis=" + mStaleAfterMillis);
         ipw.println("AttentionServicePackageName=" + getServiceConfigPackage(mContext));
         ipw.println("Resolved component:");
         if (mComponentName != null) {
@@ -403,7 +397,7 @@
     private final class LocalService extends AttentionManagerInternal {
         @Override
         public boolean isAttentionServiceSupported() {
-            return AttentionManagerService.this.isAttentionServiceSupported();
+            return AttentionManagerService.this.mIsServiceEnabled;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index cb7db92..c87f62f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -89,7 +89,8 @@
         }
     }
 
-    void onError(int sensorId, int cookie, int error, int vendorCode) throws RemoteException {
+    public void onError(int sensorId, int cookie, int error, int vendorCode)
+            throws RemoteException {
         if (mSensorReceiver != null) {
             mSensorReceiver.onError(sensorId, cookie, error, vendorCode);
         } else if (mFaceServiceReceiver != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutConsumer.java b/services/core/java/com/android/server/biometrics/sensors/LockoutConsumer.java
new file mode 100644
index 0000000..153bd46
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutConsumer.java
@@ -0,0 +1,25 @@
+/*
+ * 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.server.biometrics.sensors;
+
+/**
+ * Interface that clients interested/eligible for lockout events should implement.
+ */
+public interface LockoutConsumer {
+    void onLockoutTimed(long durationMillis);
+    void onLockoutPermanent();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 105fcec..dc22970 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -33,10 +33,9 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
-import android.hardware.biometrics.ITestService;
-import android.hardware.biometrics.SensorPropertiesInternal;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
@@ -53,6 +52,8 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Pair;
 import android.util.Slog;
@@ -91,54 +92,52 @@
     private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     private final LockPatternUtils mLockPatternUtils;
     @NonNull private List<ServiceProvider> mServiceProviders;
-    @Nullable private TestService mTestService;
+    @NonNull private final ArrayMap<Integer, TestSession> mTestSessions;
 
-    private final class TestService extends ITestService.Stub {
+    private final class TestSession extends ITestSession.Stub {
+        private final int mSensorId;
 
-        @Override
-        public List<SensorPropertiesInternal> getSensorPropertiesInternal(
-                String opPackageName) {
-            Utils.checkPermission(getContext(), TEST_BIOMETRIC);
-            return null;
+        TestSession(int sensorId) {
+            mSensorId = sensorId;
         }
 
         @Override
-        public void enableTestHal(int sensorId, boolean enableTestHal) {
+        public void enableTestHal(boolean enableTestHal) {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
 
         @Override
-        public void enrollStart(int sensorId, int userId) {
+        public void startEnroll(int userId) {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
 
         @Override
-        public void enrollFinish(int sensorId, int userId) {
+        public void finishEnroll(int userId) {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
 
         @Override
-        public void authenticateSuccess(int sensorId, int userId)  {
+        public void acceptAuthentication(int userId)  {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
 
         @Override
-        public void authenticateReject(int sensorId, int userId)  {
+        public void rejectAuthentication(int userId)  {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
 
         @Override
-        public void notifyAcquired(int sensorId, int userId)  {
+        public void notifyAcquired(int userId)  {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
 
         @Override
-        public void notifyError(int sensorId, int userId)  {
+        public void notifyError(int userId)  {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
 
         @Override
-        public void internalCleanup(int sensorId, int userId)  {
+        public void cleanupInternalState(int userId)  {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
         }
     }
@@ -148,15 +147,17 @@
      */
     private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
         @Override
-        public ITestService getTestService(String opPackageName) {
+        public ITestSession createTestSession(int sensorId, String opPackageName) {
             Utils.checkPermission(getContext(), TEST_BIOMETRIC);
 
-            synchronized (this) {
-                if (mTestService == null) {
-                    mTestService = new TestService();
+            final TestSession session;
+            synchronized (mTestSessions) {
+                if (!mTestSessions.containsKey(sensorId)) {
+                    mTestSessions.put(sensorId, new TestSession(sensorId));
                 }
+                session = mTestSessions.get(sensorId);
             }
-            return mTestService;
+            return session;
         }
 
         @Override // Binder call
@@ -187,7 +188,7 @@
         }
 
         @Override // Binder call
-        public void revokeChallenge(IBinder token, String opPackageName) {
+        public void revokeChallenge(IBinder token, String opPackageName, long challenge) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -196,7 +197,8 @@
                 return;
             }
 
-            provider.second.scheduleRevokeChallenge(provider.first, token, opPackageName);
+            provider.second.scheduleRevokeChallenge(provider.first, token, opPackageName,
+                    challenge);
         }
 
         @Override // Binder call
@@ -619,6 +621,7 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mServiceProviders = new ArrayList<>();
+        mTestSessions = new ArrayMap<>();
 
         initializeAidlHals();
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 1162c9c..35d0188 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -68,7 +68,7 @@
             @NonNull IFingerprintServiceReceiver receiver, String opPackageName);
 
     void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token,
-            @NonNull String opPackageName);
+            @NonNull String opPackageName, long challenge);
 
     void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
new file mode 100644
index 0000000..e923943
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -0,0 +1,152 @@
+/*
+ * 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.server.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskStackListener;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+
+import java.util.ArrayList;
+
+/**
+ * Fingerprint-specific authentication client supporting the
+ * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
+ */
+public class FingerprintAuthenticationClient extends AuthenticationClient<ISession> implements
+        Udfps, LockoutConsumer {
+    private static final String TAG = "FingerprintAuthenticationClient";
+
+    @NonNull private final LockoutCache mLockoutCache;
+    @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+    @Nullable private ICancellationSignal mCancellationSignal;
+
+    public FingerprintAuthenticationClient(@NonNull Context context,
+            @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+            @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
+            boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
+            int sensorId, boolean isStrongBiometric, int statsClient,
+            @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
+            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+        super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
+                cookie, requireConfirmation, sensorId, isStrongBiometric,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
+                lockoutCache);
+        mLockoutCache = lockoutCache;
+        mUdfpsOverlayController = udfpsOverlayController;
+    }
+
+    @Override
+    public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
+            boolean authenticated, ArrayList<Byte> token) {
+        super.onAuthenticated(identifier, authenticated, token);
+
+        if (authenticated) {
+            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mCallback.onClientFinished(this, true /* success */);
+        }
+    }
+
+    @Override
+    protected void startHalOperation() {
+        UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        try {
+            mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, mOperationId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                    0 /* vendorCode */);
+            UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    protected void stopHalOperation() {
+        UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+        try {
+            mCancellationSignal.cancel();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+            onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                    0 /* vendorCode */);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+
+    @Override
+    public void onPointerDown(int x, int y, float minor, float major) {
+        try {
+            getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
+    @Override
+    public void onPointerUp() {
+        try {
+            getFreshDaemon().onPointerUp(0 /* pointerId */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
+    @Override
+    public void onLockoutTimed(long durationMillis) {
+        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
+        // Lockout metrics are logged as an error code.
+        final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
+        logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId());
+
+        try {
+            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
+    @Override
+    public void onLockoutPermanent() {
+        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
+        // Lockout metrics are logged as an error code.
+        final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+        logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId());
+
+        try {
+            getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
index 3a6b113..7db01ee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -34,7 +34,7 @@
     private static final String TAG = "FingerprintGenerateChallengeClient";
     private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
 
-    private IGenerateChallengeCallback mGenerateChallengeCallback =
+    private final IGenerateChallengeCallback mGenerateChallengeCallback =
             new IGenerateChallengeCallback.Stub() {
         @Override
         public void onChallengeGenerated(int sensorId, int userId, long challenge) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 44a23cf..5e6c30e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -19,6 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
 import android.content.Context;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -36,12 +39,15 @@
 import android.util.SparseArray;
 import android.view.Surface;
 
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.ClientMonitor;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -51,6 +57,7 @@
 /**
  * Provider for a single instance of the {@link IFingerprint} HAL.
  */
+@SuppressWarnings("deprecation")
 public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider {
 
     @NonNull private final Context mContext;
@@ -58,9 +65,49 @@
     @NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
     @NonNull private final ClientMonitor.LazyDaemon<IFingerprint> mLazyDaemon;
     @NonNull private final Handler mHandler;
+    @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+    @NonNull private final IActivityTaskManager mActivityTaskManager;
+    @NonNull private final BiometricTaskStackListener mTaskStackListener;
 
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
 
+    private final class BiometricTaskStackListener extends TaskStackListener {
+        @Override
+        public void onTaskStackChanged() {
+            mHandler.post(() -> {
+                for (int i = 0; i < mSensors.size(); i++) {
+                    final ClientMonitor<?> client = mSensors.get(i).getScheduler()
+                            .getCurrentClient();
+                    if (!(client instanceof AuthenticationClient)) {
+                        Slog.e(getTag(), "Task stack changed for client: " + client);
+                        continue;
+                    }
+                    if (Utils.isKeyguard(mContext, client.getOwnerString())) {
+                        continue; // Keyguard is always allowed
+                    }
+
+                    try {
+                        final List<ActivityManager.RunningTaskInfo> runningTasks =
+                                mActivityTaskManager.getTasks(1);
+                        if (!runningTasks.isEmpty()) {
+                            final String topPackage =
+                                    runningTasks.get(0).topActivity.getPackageName();
+                            if (!topPackage.contentEquals(client.getOwnerString())
+                                    && !client.isAlreadyDone()) {
+                                Slog.e(getTag(), "Stopping background authentication, top: "
+                                        + topPackage + " currentClient: " + client);
+                                mSensors.get(i).getScheduler()
+                                        .cancelAuthentication(client.getToken());
+                            }
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(getTag(), "Unable to get running tasks", e);
+                    }
+                }
+            });
+        }
+    }
+
     public FingerprintProvider(@NonNull Context context, @NonNull SensorProps[] props,
             @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -69,6 +116,9 @@
         mSensors = new SparseArray<>();
         mLazyDaemon = this::getHalInstance;
         mHandler = new Handler(Looper.getMainLooper());
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+        mActivityTaskManager = ActivityTaskManager.getService();
+        mTaskStackListener = new BiometricTaskStackListener();
 
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
@@ -132,7 +182,7 @@
         mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
     }
 
-    private void scheduleCreateSessionWithoutHandler(@NonNull IFingerprint daemon, int sensorId,
+    private void createNewSessionWithoutHandler(@NonNull IFingerprint daemon, int sensorId,
             int userId) throws RemoteException {
         // Note that per IFingerprint createSession contract, this method will block until all
         // existing operations are canceled/finished. However, also note that this is fine, since
@@ -157,7 +207,7 @@
     @NonNull
     @Override
     public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
-        List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+        final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
         for (int i = 0; i < mSensors.size(); i++) {
             props.add(mSensors.valueAt(i).getSensorProperties());
         }
@@ -166,7 +216,27 @@
 
     @Override
     public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
+        mHandler.post(() -> {
+            final IFingerprint daemon = getHalInstance();
+            if (daemon == null) {
+                Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
+                return;
+            }
 
+            try {
+                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
+                    createNewSessionWithoutHandler(daemon, sensorId, userId);
+                }
+
+                final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
+                        mContext, mSensors.get(sensorId).getLazySession(), userId,
+                        mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+                        mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
+                scheduleForSensor(sensorId, client);
+            } catch (RemoteException e) {
+                Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
+            }
+        });
     }
 
     @Override
@@ -176,14 +246,19 @@
             final FingerprintGenerateChallengeClient client =
                     new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
                             new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
-            mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+            scheduleForSensor(sensorId, client);
         });
     }
 
     @Override
     public void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token,
-            @NonNull String opPackageName) {
-
+            @NonNull String opPackageName, long challenge) {
+        mHandler.post(() -> {
+            final FingerprintRevokeChallengeClient client =
+                    new FingerprintRevokeChallengeClient(mContext, mLazyDaemon, token,
+                            opPackageName, sensorId, challenge);
+            scheduleForSensor(sensorId, client);
+        });
     }
 
     @Override
@@ -194,12 +269,15 @@
             final IFingerprint daemon = getHalInstance();
             if (daemon == null) {
                 Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
+                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
+                // this operation. We should not send the callback yet, since the scheduler may
+                // be processing something else.
                 return;
             }
 
             try {
                 if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
-                    scheduleCreateSessionWithoutHandler(daemon, sensorId, userId);
+                    createNewSessionWithoutHandler(daemon, sensorId, userId);
                 }
 
                 final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
@@ -227,7 +305,7 @@
 
     @Override
     public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
-
+        mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
     }
 
     @Override
@@ -242,17 +320,43 @@
             int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull String opPackageName, boolean restricted, int statsClient,
             boolean isKeyguard) {
+        mHandler.post(() -> {
+            final IFingerprint daemon = getHalInstance();
+            if (daemon == null) {
+                Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
+                // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
+                // this operation. We should not send the callback yet, since the scheduler may
+                // be processing something else.
+                return;
+            }
 
+            try {
+                if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
+                    createNewSessionWithoutHandler(daemon, sensorId, userId);
+                }
+
+                final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+                final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+                        mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+                        operationId, restricted, opPackageName, cookie,
+                        false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+                        mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
+                        mUdfpsOverlayController);
+                mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+            } catch (RemoteException e) {
+                Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
+            }
+        });
     }
 
     @Override
     public void startPreparedClient(int sensorId, int cookie) {
-
+        mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
     }
 
     @Override
     public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
-
+        mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelAuthentication(token));
     }
 
     @Override
@@ -285,7 +389,7 @@
 
     @Override
     public int getLockoutModeForUser(int sensorId, int userId) {
-        return 0;
+        return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId);
     }
 
     @Override
@@ -295,12 +399,24 @@
 
     @Override
     public void onPointerDown(int sensorId, int x, int y, float minor, float major) {
-
+        final ClientMonitor<?> client = mSensors.get(sensorId).getScheduler().getCurrentClient();
+        if (!(client instanceof Udfps)) {
+            Slog.e(getTag(), "onPointerDown received during client: " + client);
+            return;
+        }
+        final Udfps udfps = (Udfps) client;
+        udfps.onPointerDown(x, y, minor, major);
     }
 
     @Override
     public void onPointerUp(int sensorId) {
-
+        final ClientMonitor<?> client = mSensors.get(sensorId).getScheduler().getCurrentClient();
+        if (!(client instanceof Udfps)) {
+            Slog.e(getTag(), "onPointerUp received during client: " + client);
+            return;
+        }
+        final Udfps udfps = (Udfps) client;
+        udfps.onPointerUp();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
new file mode 100644
index 0000000..bc35ad4
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -0,0 +1,78 @@
+/*
+ * 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.server.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+/**
+ * Fingerprint-specific resetLockout client for the {@link IFingerprint} AIDL HAL interface.
+ * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
+ * cleared.
+ */
+public class FingerprintResetLockoutClient extends ClientMonitor<ISession> {
+
+    private static final String TAG = "FingerprintResetLockoutClient";
+
+    private final HardwareAuthToken mHardwareAuthToken;
+    private final LockoutCache mLockoutCache;
+    private final LockoutResetDispatcher mLockoutResetDispatcher;
+
+    public FingerprintResetLockoutClient(@NonNull Context context,
+            @NonNull LazyDaemon<ISession> lazyDaemon, int userId, String owner, int sensorId,
+            @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+        super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+                0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+        mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+        mLockoutCache = lockoutTracker;
+        mLockoutResetDispatcher = lockoutResetDispatcher;
+    }
+
+    @Override
+    public void unableToStart() {
+        // Nothing to do here
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to reset lockout", e);
+            mCallback.onClientFinished(this, false /* success */);
+        }
+    }
+
+    void onLockoutCleared() {
+        mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_NONE);
+        mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
+        mCallback.onClientFinished(this, true /* success */);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
new file mode 100644
index 0000000..e97dbe7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -0,0 +1,65 @@
+/*
+ * 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.server.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.IRevokeChallengeCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.RevokeChallengeClient;
+
+/**
+ * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
+ */
+public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<IFingerprint> {
+
+    private static final String TAG = "FingerpirntRevokeChallengeClient";
+
+    private final long mChallenge;
+
+    private final IRevokeChallengeCallback mRevokeChallengeCallback =
+            new IRevokeChallengeCallback.Stub() {
+        @Override
+        public void onChallengeRevoked(int sensorId, int userId, long challenge) {
+            final boolean success = challenge == mChallenge;
+            mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success);
+        }
+    };
+
+    public FingerprintRevokeChallengeClient(
+            @NonNull Context context,
+            @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+            @NonNull IBinder token,
+            @NonNull String owner, int sensorId, long challenge) {
+        super(context, lazyDaemon, token, owner, sensorId);
+        mChallenge = challenge;
+    }
+
+    @Override
+    protected void startHalOperation() {
+        try {
+            getFreshDaemon().revokeChallenge(getSensorId(), getTargetUserId(), mChallenge,
+                    mRevokeChallengeCallback);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to revokeChallenge", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/LockoutCache.java
new file mode 100644
index 0000000..2abbcb0
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/LockoutCache.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.biometrics.sensors.fingerprint.aidl;
+
+import android.util.SparseIntArray;
+
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+/**
+ * For a single sensor, caches lockout states for all users.
+ */
+public class LockoutCache implements LockoutTracker {
+
+    // Map of userId to LockoutMode
+    private final SparseIntArray mUserLockoutStates;
+
+    LockoutCache() {
+        mUserLockoutStates = new SparseIntArray();
+    }
+
+    public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
+        synchronized (this) {
+            mUserLockoutStates.put(userId, mode);
+        }
+    }
+
+    @Override
+    public int getLockoutModeForUser(int userId) {
+        synchronized (this) {
+            return mUserLockoutStates.get(userId, LOCKOUT_NONE);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 3c27f9c..dc90be8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -37,6 +37,7 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.ClientMonitor;
 import com.android.server.biometrics.sensors.Interruptable;
+import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
 
@@ -54,6 +55,7 @@
     @NonNull private final Handler mHandler;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
     @NonNull private final BiometricScheduler mScheduler;
+    @NonNull private final LockoutCache mLockoutCache;
 
     @Nullable private Session mCurrentSession; // TODO: Death recipient
     @NonNull private final ClientMonitor.LazyDaemon<ISession> mLazySession;
@@ -82,6 +84,7 @@
         mHandler = handler;
         mSensorProperties = sensorProperties;
         mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
+        mLockoutCache = new LockoutCache();
         mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
     }
 
@@ -207,17 +210,48 @@
 
             @Override
             public void onLockoutTimed(long durationMillis) {
+                mHandler.post(() -> {
+                    final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                    if (!(client instanceof LockoutConsumer)) {
+                        Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
+                                + Utils.getClientName(client));
+                        return;
+                    }
 
+                    final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+                    lockoutConsumer.onLockoutTimed(durationMillis);
+                });
             }
 
             @Override
             public void onLockoutPermanent() {
+                mHandler.post(() -> {
+                    final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                    if (!(client instanceof LockoutConsumer)) {
+                        Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
+                                + Utils.getClientName(client));
+                        return;
+                    }
 
+                    final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+                    lockoutConsumer.onLockoutPermanent();
+                });
             }
 
             @Override
             public void onLockoutCleared() {
+                mHandler.post(() -> {
+                    final ClientMonitor<?> client = mScheduler.getCurrentClient();
+                    if (!(client instanceof FingerprintResetLockoutClient)) {
+                        Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
+                                + Utils.getClientName(client));
+                        return;
+                    }
 
+                    final FingerprintResetLockoutClient resetLockoutClient =
+                            (FingerprintResetLockoutClient) client;
+                    resetLockoutClient.onLockoutCleared();
+                });
             }
 
             @Override
@@ -253,4 +287,8 @@
     @NonNull BiometricScheduler getScheduler() {
         return mScheduler;
     }
+
+    @NonNull LockoutCache getLockoutCache() {
+        return mLockoutCache;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 8ce99f4..da68bc8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -518,7 +518,7 @@
 
     @Override
     public void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token,
-            @NonNull String opPackageName) {
+            @NonNull String opPackageName, long challenge) {
         mHandler.post(() -> {
             final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
                     mContext, mLazyDaemon, token, opPackageName, mSensorProperties.sensorId);
@@ -594,16 +594,12 @@
 
     @Override
     public void startPreparedClient(int sensorId, int cookie) {
-        mHandler.post(() -> {
-            mScheduler.startPreparedClient(cookie);
-        });
+        mHandler.post(() -> mScheduler.startPreparedClient(cookie));
     }
 
     @Override
     public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
-        mHandler.post(() -> {
-            mScheduler.cancelAuthentication(token);
-        });
+        mHandler.post(() -> mScheduler.cancelAuthentication(token));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 09c01d7..1ed6b35 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2152,7 +2152,11 @@
                 break;
         }
 
-        // Prepare arguments for mtpd.
+        // Prepare arguments for mtpd. MTU/MRU calculated conservatively. Only IPv4 supported
+        // because LegacyVpn.
+        // 1500 - 60 (Carrier-internal IPv6 + UDP + GTP) - 10 (PPP) - 16 (L2TP) - 8 (UDP)
+        //   - 77 (IPsec w/ SHA-2 512, 256b trunc-len, AES-CBC) - 8 (UDP encap) - 20 (IPv4)
+        //   - 28 (464xlat)
         String[] mtpd = null;
         switch (profile.type) {
             case VpnProfile.TYPE_PPTP:
@@ -2160,7 +2164,7 @@
                     iface, "pptp", profile.server, "1723",
                     "name", profile.username, "password", profile.password,
                     "linkname", "vpn", "refuse-eap", "nodefaultroute",
-                    "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+                    "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
                     (profile.mppe ? "+mppe" : "nomppe"),
                 };
                 break;
@@ -2170,7 +2174,7 @@
                     iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
                     "name", profile.username, "password", profile.password,
                     "linkname", "vpn", "refuse-eap", "nodefaultroute",
-                    "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+                    "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
                 };
                 break;
         }
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 4cff5c0..7e3c1ab 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -16,8 +16,11 @@
 
 package com.android.server.devicestate;
 
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
 import android.annotation.NonNull;
 import android.content.Context;
+import android.hardware.devicestate.IDeviceStateManager;
 import android.util.IntArray;
 import android.util.Slog;
 
@@ -49,9 +52,6 @@
  * @see DeviceStatePolicy
  */
 public final class DeviceStateManagerService extends SystemService {
-    /** Invalid device state. */
-    public static final int INVALID_DEVICE_STATE = -1;
-
     private static final String TAG = "DeviceStateManagerService";
     private static final boolean DEBUG = false;
 
@@ -88,6 +88,7 @@
     @Override
     public void onStart() {
         mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
+        publishBinderService(Context.DEVICE_STATE_SERVICE, new BinderService());
     }
 
     /**
@@ -267,4 +268,9 @@
             requestState(state);
         }
     }
+
+    /** Implementation of {@link IDeviceStateManager} published as a binder service. */
+    private final class BinderService extends IDeviceStateManager.Stub {
+
+    }
 }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index eb2c7e6..93cada7 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -208,7 +208,6 @@
     private PackageManager mPackageManager;
     private Context mContext;
 
-    private DisplayDeviceConfig mDisplayDeviceConfig;
     private final Injector mInjector;
 
     AutomaticBrightnessController(Callbacks callbacks, Looper looper,
@@ -217,14 +216,13 @@
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
-            HysteresisLevels screenBrightnessThresholds, Context context, DisplayDeviceConfig
-            displayDeviceConfig) {
+            HysteresisLevels screenBrightnessThresholds, Context context) {
         this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
                 lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
                 lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
                 darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
-                ambientBrightnessThresholds, screenBrightnessThresholds, context,
-                displayDeviceConfig);
+                ambientBrightnessThresholds, screenBrightnessThresholds, context
+        );
     }
 
     @VisibleForTesting
@@ -234,8 +232,7 @@
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
-            HysteresisLevels screenBrightnessThresholds, Context context, DisplayDeviceConfig
-            displayDeviceConfig) {
+            HysteresisLevels screenBrightnessThresholds, Context context) {
         mInjector = injector;
         mContext = context;
         mCallbacks = callbacks;
@@ -257,7 +254,6 @@
         mScreenBrightnessThresholds = screenBrightnessThresholds;
         mShortTermModelValid = true;
         mShortTermModelAnchor = -1;
-        mDisplayDeviceConfig = displayDeviceConfig;
         mHandler = new AutomaticBrightnessHandler(looper);
         mAmbientLightRingBuffer =
             new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
diff --git a/services/core/java/com/android/server/display/DisplayBlanker.java b/services/core/java/com/android/server/display/DisplayBlanker.java
index d294898..e2129ba 100644
--- a/services/core/java/com/android/server/display/DisplayBlanker.java
+++ b/services/core/java/com/android/server/display/DisplayBlanker.java
@@ -20,5 +20,5 @@
  * Interface used to update the actual display state.
  */
 public interface DisplayBlanker {
-    void requestDisplayState(int state, float brightness);
+    void requestDisplayState(int displayId, int state, float brightness);
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index af62aeb..ffce3be 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1768,7 +1768,9 @@
             final int callingUid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
-                return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid);
+                synchronized (mSyncRoot) {
+                    return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid);
+                }
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2380,7 +2382,7 @@
             synchronized (mSyncRoot) {
                 DisplayBlanker blanker = new DisplayBlanker() {
                     @Override
-                    public void requestDisplayState(int state, float brightness) {
+                    public void requestDisplayState(int displayId, int state, float brightness) {
                         // The order of operations is important for legacy reasons.
                         if (state == Display.STATE_OFF) {
                             requestGlobalDisplayStateInternal(state, brightness);
@@ -2393,11 +2395,9 @@
                         }
                     }
                 };
-                LogicalDisplay defaultDisplay =
-                        mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY);
-                DisplayDevice defaultDevice = defaultDisplay.getPrimaryDisplayDeviceLocked();
                 mDisplayPowerController = new DisplayPowerController(
-                        mContext, callbacks, handler, sensorManager, blanker, defaultDevice);
+                        mContext, callbacks, handler, sensorManager, blanker,
+                        Display.DEFAULT_DISPLAY);
                 mSensorManager = sensorManager;
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58ef9d1..0211876 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -163,8 +163,8 @@
     // The display blanker.
     private final DisplayBlanker mBlanker;
 
-    // The display device.
-    private final DisplayDevice mDisplayDevice;
+    // The ID of the LogicalDisplay tied to this DisplayPowerController.
+    private final int mDisplayId;
 
     // Tracker for brightness changes.
     private final BrightnessTracker mBrightnessTracker;
@@ -406,7 +406,7 @@
      */
     public DisplayPowerController(Context context,
             DisplayPowerCallbacks callbacks, Handler handler,
-            SensorManager sensorManager, DisplayBlanker blanker, DisplayDevice displayDevice) {
+            SensorManager sensorManager, DisplayBlanker blanker, int displayId) {
         mHandler = new DisplayControllerHandler(handler.getLooper());
         mBrightnessTracker = new BrightnessTracker(context, null);
         mSettingsObserver = new SettingsObserver(mHandler);
@@ -417,10 +417,9 @@
         mBlanker = blanker;
         mContext = context;
         mBrightnessSynchronizer = new BrightnessSynchronizer(context);
-        mDisplayDevice = displayDevice;
+        mDisplayId = displayId;
 
         PowerManager pm =  context.getSystemService(PowerManager.class);
-        DisplayDeviceConfig displayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
 
         final Resources resources = context.getResources();
 
@@ -515,7 +514,7 @@
                         mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
                         initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
                         autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
-                        screenBrightnessThresholds, context, displayDeviceConfig);
+                        screenBrightnessThresholds, context);
             } else {
                 mUseSoftwareAutoBrightnessConfig = false;
             }
@@ -684,7 +683,7 @@
         // Initialize the power state object for the default display.
         // In the future, we might manage multiple displays independently.
         mPowerState = new DisplayPowerState(mBlanker,
-                mColorFadeEnabled ? new ColorFade(Display.DEFAULT_DISPLAY) : null);
+                mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId);
 
         if (mColorFadeEnabled) {
             mColorFadeOnAnimator = ObjectAnimator.ofFloat(
@@ -1153,7 +1152,7 @@
         if (ready && state != Display.STATE_OFF
                 && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) {
             setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON);
-            mWindowManagerPolicy.screenTurnedOn();
+            mWindowManagerPolicy.screenTurnedOn(mDisplayId);
         }
 
         // Grab a wake lock if we have unfinished business.
@@ -1277,7 +1276,7 @@
                 if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON) {
                     setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
                     blockScreenOff();
-                    mWindowManagerPolicy.screenTurningOff(mPendingScreenOffUnblocker);
+                    mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
                     unblockScreenOff();
                 } else if (mPendingScreenOffUnblocker != null) {
                     // Abort doing the state change until screen off is unblocked.
@@ -1309,14 +1308,14 @@
                 && !mScreenOffBecauseOfProximity) {
             setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
             unblockScreenOn();
-            mWindowManagerPolicy.screenTurnedOff();
+            mWindowManagerPolicy.screenTurnedOff(mDisplayId);
         } else if (!isOff
                 && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) {
 
             // We told policy already that screen was turning off, but now we changed our minds.
             // Complete the full state transition on -> turningOff -> off.
             unblockScreenOff();
-            mWindowManagerPolicy.screenTurnedOff();
+            mWindowManagerPolicy.screenTurnedOff(mDisplayId);
             setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
         }
         if (!isOff && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF) {
@@ -1326,7 +1325,7 @@
             } else {
                 unblockScreenOn();
             }
-            mWindowManagerPolicy.screenTurningOn(mPendingScreenOnUnblocker);
+            mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
         }
 
         // Return true if the screen isn't blocked.
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 4b6430d..54f30a9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -58,6 +58,7 @@
     private final DisplayBlanker mBlanker;
     private final ColorFade mColorFade;
     private final PhotonicModulator mPhotonicModulator;
+    private final int mDisplayId;
 
     private int mScreenState;
     private float mScreenBrightness;
@@ -71,13 +72,14 @@
 
     private Runnable mCleanListener;
 
-    public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade) {
+    public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, int displayId) {
         mHandler = new Handler(true /*async*/);
         mChoreographer = Choreographer.getInstance();
         mBlanker = blanker;
         mColorFade = colorFade;
         mPhotonicModulator = new PhotonicModulator();
         mPhotonicModulator.start();
+        mDisplayId = displayId;
 
         // At boot time, we know that the screen is on and the electron beam
         // animation is not playing.  We don't know the screen's brightness though,
@@ -434,10 +436,10 @@
 
                 // Apply pending change.
                 if (DEBUG) {
-                    Slog.d(TAG, "Updating screen state: state="
+                    Slog.d(TAG, "Updating screen state: id=" + mDisplayId +  ", state="
                             + Display.stateToString(state) + ", backlight=" + brightnessState);
                 }
-                mBlanker.requestDisplayState(state, brightnessState);
+                mBlanker.requestDisplayState(mDisplayId, state, brightnessState);
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 507a265..858cce4 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -969,6 +969,10 @@
         }
 
         private int findMatchingModeIdLocked(int configId) {
+            if (configId < 0 || configId >= mDisplayConfigs.length) {
+                Slog.e(TAG, "Invalid display config index " + configId);
+                return NO_DISPLAY_MODE_ID;
+            }
             SurfaceControl.DisplayConfig config = mDisplayConfigs[configId];
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 DisplayModeRecord record = mSupportedModes.valueAt(i);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index dcfd3f2..35d3621 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -3900,7 +3900,7 @@
                         // quick check: if this uid doesn't have INTERNET permission, it
                         // doesn't have network access anyway, so it is a waste to mess
                         // with it here.
-                        if (hasInternetPermissionUL(uid)) {
+                        if (hasInternetPermissionUL(uid) && !isUidForegroundOnRestrictPowerUL(uid)) {
                             uidRules.put(uid, FIREWALL_RULE_DENY);
                         }
                     }
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index d202a2a..5646c75 100644
--- a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -30,6 +30,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.CollectionUtils;
@@ -94,39 +95,41 @@
         // also needed to track CBRS.
         final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager);
 
-        for (final int subId : newSubs) {
-            final RatTypeListener match = CollectionUtils.find(mRatListeners,
-                    it -> it.mSubId == subId);
-            if (match != null) continue;
+        // IMSI is needed for every newly added sub. Listener stores subscriberId into it to
+        // prevent binder call to telephony when querying RAT. Keep listener registration with empty
+        // IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported
+        // with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration.
+        final List<Pair<Integer, String>> filteredNewSubs =
+                CollectionUtils.mapNotNull(newSubs, subId -> {
+                    final String subscriberId = mTeleManager.getSubscriberId(subId);
+                    return TextUtils.isEmpty(subscriberId) ? null : new Pair(subId, subscriberId);
+                });
 
-            // Create listener for every newly added sub. Also store subscriberId into it to
-            // prevent binder call to telephony when querying RAT. If the subscriberId is empty
-            // for any reason, such as SIM PIN locked, skip registration.
-            // SubscriberId will be unavailable again if 1. modem crashed 2. reboot
-            // 3. re-insert SIM. If that happens, the listeners will be eventually synchronized
-            // with active sub list once all subscriberIds are ready.
-            final String subscriberId = mTeleManager.getSubscriberId(subId);
-            if (TextUtils.isEmpty(subscriberId)) {
-                Log.d(NetworkStatsService.TAG, "Empty subscriberId for newly added sub "
-                        + subId + ", skip listener registration");
+        for (final Pair<Integer, String> sub : filteredNewSubs) {
+            // Fully match listener with subId and IMSI, since in some rare cases, IMSI might be
+            // suddenly change regardless of subId, such as switch IMSI feature in modem side.
+            // If that happens, register new listener with new IMSI and remove old one later.
+            if (CollectionUtils.find(mRatListeners,
+                    it -> it.equalsKey(sub.first, sub.second)) != null) {
                 continue;
             }
+
             final RatTypeListener listener =
-                    new RatTypeListener(mExecutor, this, subId, subscriberId);
+                    new RatTypeListener(mExecutor, this, sub.first, sub.second);
             mRatListeners.add(listener);
 
             // Register listener to the telephony manager that associated with specific sub.
-            mTeleManager.createForSubscriptionId(subId)
+            mTeleManager.createForSubscriptionId(sub.first)
                     .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
-            Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + subId);
+            Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + sub.first);
         }
 
         for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
-            // If the new list contains the subId of the listener, keeps it.
-            final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId);
-            if (match != null) continue;
-
-            handleRemoveRatTypeListener(listener);
+            // If there is no subId and IMSI matched the listener, removes it.
+            if (CollectionUtils.find(filteredNewSubs,
+                    it -> listener.equalsKey(it.first, it.second)) == null) {
+                handleRemoveRatTypeListener(listener);
+            }
         }
     }
 
@@ -232,5 +235,9 @@
         public int getSubId() {
             return mSubId;
         }
+
+        boolean equalsKey(int subId, @NonNull String subscriberId) {
+            return mSubId == subId && TextUtils.equals(mSubscriberId, subscriberId);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 74b7bd7..4040f41 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -833,6 +833,7 @@
 
     public void onUserSwitched(int user) {
         if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
+        unbindOtherUserServices(user);
         rebindServices(true, user);
     }
 
@@ -1219,6 +1220,27 @@
         bindToServices(componentsToBind);
     }
 
+    /**
+     * Called when user switched to unbind all services from other users.
+     */
+    @VisibleForTesting
+    void unbindOtherUserServices(int currentUser) {
+        final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
+
+        synchronized (mMutex) {
+            final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
+            for (ManagedServiceInfo info : removableBoundServices) {
+                if (info.userid != currentUser) {
+                    Set<ComponentName> toUnbind =
+                            componentsToUnbind.get(info.userid, new ArraySet<>());
+                    toUnbind.add(info.component);
+                    componentsToUnbind.put(info.userid, toUnbind);
+                }
+            }
+        }
+        unbindFromServices(componentsToUnbind);
+    }
+
     protected void unbindFromServices(SparseArray<Set<ComponentName>> componentsToUnbind) {
         for (int i = 0; i < componentsToUnbind.size(); i++) {
             final int userId = componentsToUnbind.keyAt(i);
@@ -1264,7 +1286,8 @@
     /**
      * Version of registerService that takes the name of a service component to bind to.
      */
-    private void registerService(final ComponentName name, final int userid) {
+    @VisibleForTesting
+    void registerService(final ComponentName name, final int userid) {
         synchronized (mMutex) {
             registerServiceLocked(name, userid);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fa9cc04..03bf74f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -421,6 +421,7 @@
     private IActivityManager mAm;
     private ActivityTaskManagerInternal mAtm;
     private ActivityManager mActivityManager;
+    private ActivityManagerInternal mAmi;
     private IPackageManager mPackageManager;
     private PackageManager mPackageManagerClient;
     AudioManager mAudioManager;
@@ -1897,7 +1898,7 @@
             DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
             UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
             NotificationHistoryManager historyManager, StatsManager statsManager,
-            TelephonyManager telephonyManager) {
+            TelephonyManager telephonyManager, ActivityManagerInternal ami) {
         mHandler = handler;
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -1919,6 +1920,7 @@
         mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
         mCompanionManager = companionManager;
         mActivityManager = activityManager;
+        mAmi = ami;
         mDeviceIdleManager = getContext().getSystemService(DeviceIdleManager.class);
         mDpm = dpm;
         mUm = userManager;
@@ -2197,7 +2199,8 @@
                 new NotificationHistoryManager(getContext(), handler),
                 mStatsManager = (StatsManager) getContext().getSystemService(
                         Context.STATS_MANAGER),
-                getContext().getSystemService(TelephonyManager.class));
+                getContext().getSystemService(TelephonyManager.class),
+                LocalServices.getService(ActivityManagerInternal.class));
 
         publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
                 DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -5247,8 +5250,8 @@
             if (!summaries.containsKey(pkg)) {
                 // Add summary
                 final ApplicationInfo appInfo =
-                       adjustedSbn.getNotification().extras.getParcelable(
-                               Notification.EXTRA_BUILDER_APPLICATION_INFO);
+                        adjustedSbn.getNotification().extras.getParcelable(
+                                Notification.EXTRA_BUILDER_APPLICATION_INFO);
                 final Bundle extras = new Bundle();
                 extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
                 final String channelId = notificationRecord.getChannel().getId();
@@ -5284,11 +5287,11 @@
                         notificationRecord.getIsAppImportanceLocked());
                 summaries.put(pkg, summarySbn.getKey());
             }
-        }
-        if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
-                summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
-                true)) {
-            mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground));
+            if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
+                    summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
+                    true)) {
+                mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground));
+            }
         }
     }
 
@@ -6027,13 +6030,17 @@
                 + " cannot post for pkg " + targetPkg + " in user " + userId);
     }
 
+    public boolean hasFlag(final int flags, final int flag) {
+        return (flags & flag) != 0;
+    }
     /**
      * Checks if a notification can be posted. checks rate limiter, snooze helper, and blocking.
      *
      * Has side effects.
      */
-    private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
+    boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
             NotificationRecord r, boolean isAutogroup) {
+        Notification n = r.getNotification();
         final String pkg = r.getSbn().getPackageName();
         final boolean isSystemNotification =
                 isUidSystemOrPhone(uid) || ("android".equals(pkg));
@@ -6042,71 +6049,101 @@
         // Limit the number of notifications that any given package except the android
         // package or a registered listener can enqueue.  Prevents DOS attacks and deals with leaks.
         if (!isSystemNotification && !isNotificationFromListener) {
-            synchronized (mNotificationLock) {
-                final int callingUid = Binder.getCallingUid();
-                if (mNotificationsByKey.get(r.getSbn().getKey()) == null
-                        && isCallerInstantApp(callingUid, userId)) {
-                    // Ephemeral apps have some special constraints for notifications.
-                    // They are not allowed to create new notifications however they are allowed to
-                    // update notifications created by the system (e.g. a foreground service
-                    // notification).
-                    throw new SecurityException("Instant app " + pkg
-                            + " cannot create notifications");
-                }
+            final int callingUid = Binder.getCallingUid();
+            if (mNotificationsByKey.get(r.getSbn().getKey()) == null
+                    && isCallerInstantApp(callingUid, userId)) {
+                // Ephemeral apps have some special constraints for notifications.
+                // They are not allowed to create new notifications however they are allowed to
+                // update notifications created by the system (e.g. a foreground service
+                // notification).
+                throw new SecurityException("Instant app " + pkg
+                        + " cannot create notifications");
+            }
 
-                // rate limit updates that aren't completed progress notifications
-                if (mNotificationsByKey.get(r.getSbn().getKey()) != null
-                        && !r.getNotification().hasCompletedProgress()
-                        && !isAutogroup) {
+            // rate limit updates that aren't completed progress notifications
+            if (mNotificationsByKey.get(r.getSbn().getKey()) != null
+                    && !r.getNotification().hasCompletedProgress()
+                    && !isAutogroup) {
 
-                    final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
-                    if (appEnqueueRate > mMaxPackageEnqueueRate) {
-                        mUsageStats.registerOverRateQuota(pkg);
-                        final long now = SystemClock.elapsedRealtime();
-                        if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
-                            Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
-                                    + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
-                            mLastOverRateLogTime = now;
-                        }
-                        return false;
+                final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
+                if (appEnqueueRate > mMaxPackageEnqueueRate) {
+                    mUsageStats.registerOverRateQuota(pkg);
+                    final long now = SystemClock.elapsedRealtime();
+                    if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
+                        Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
+                                + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
+                        mLastOverRateLogTime = now;
                     }
+                    return false;
                 }
+            }
 
-                // limit the number of non-fgs outstanding notificationrecords an app can have
-                if (!r.getNotification().isForegroundService()) {
-                    int count = getNotificationCountLocked(pkg, userId, id, tag);
-                    if (count >= MAX_PACKAGE_NOTIFICATIONS) {
-                        mUsageStats.registerOverCountQuota(pkg);
-                        Slog.e(TAG, "Package has already posted or enqueued " + count
-                                + " notifications.  Not showing more.  package=" + pkg);
-                        return false;
-                    }
+            // limit the number of non-fgs outstanding notificationrecords an app can have
+            if (!n.isForegroundService()) {
+                int count = getNotificationCountLocked(pkg, userId, id, tag);
+                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
+                    mUsageStats.registerOverCountQuota(pkg);
+                    Slog.e(TAG, "Package has already posted or enqueued " + count
+                            + " notifications.  Not showing more.  package=" + pkg);
+                    return false;
                 }
             }
         }
 
-        synchronized (mNotificationLock) {
-            // snoozed apps
-            if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
-                MetricsLogger.action(r.getLogMaker()
-                        .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
-                        .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
-                mNotificationRecordLogger.log(
-                        NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
-                        r);
-                if (DBG) {
-                    Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
+        // bubble or inline reply that's immutable?
+        if (n.getBubbleMetadata() != null
+                && n.getBubbleMetadata().getIntent() != null
+                && hasFlag(mAmi.getPendingIntentFlags(
+                        n.getBubbleMetadata().getIntent().getTarget()),
+                        PendingIntent.FLAG_IMMUTABLE)) {
+            throw new IllegalArgumentException(r.getKey() + " Not posted."
+                    + " PendingIntents attached to bubbles must be mutable");
+        }
+
+        if (n.actions != null) {
+            for (Notification.Action action : n.actions) {
+                if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
+                        && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
+                        PendingIntent.FLAG_IMMUTABLE)) {
+                    throw new IllegalArgumentException(r.getKey() + " Not posted."
+                            + " PendingIntents attached to actions with remote"
+                            + " inputs must be mutable");
                 }
-                mSnoozeHelper.update(userId, r);
-                handleSavePolicyFile();
-                return false;
             }
+        }
+
+        if (r.getSystemGeneratedSmartActions() != null) {
+            for (Notification.Action action : r.getSystemGeneratedSmartActions()) {
+                if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
+                        && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
+                        PendingIntent.FLAG_IMMUTABLE)) {
+                    throw new IllegalArgumentException(r.getKey() + " Not posted."
+                            + " PendingIntents attached to contextual actions with remote inputs"
+                            + " must be mutable");
+                }
+            }
+        }
+
+        // snoozed apps
+        if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
+            MetricsLogger.action(r.getLogMaker()
+                    .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
+                    .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
+            mNotificationRecordLogger.log(
+                    NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
+                    r);
+            if (DBG) {
+                Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
+            }
+            mSnoozeHelper.update(userId, r);
+            handleSavePolicyFile();
+            return false;
+        }
 
 
-            // blocked apps
-            if (isBlocked(r, mUsageStats)) {
-                return false;
-            }
+        // blocked apps
+        if (isBlocked(r, mUsageStats)) {
+            return false;
         }
 
         return true;
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index ad022c7..7992fea 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -777,12 +777,15 @@
         void registerApkInApex(AndroidPackage pkg) {
             synchronized (mLock) {
                 for (ActiveApexInfo aai : mActiveApexInfosCache) {
-                    if (pkg.getBaseApkPath().startsWith(aai.apexDirectory.getAbsolutePath())) {
+                    if (pkg.getBaseApkPath().startsWith(
+                            aai.apexDirectory.getAbsolutePath() + File.separator)) {
                         List<String> apks = mApksInApex.get(aai.apexModuleName);
                         if (apks == null) {
                             apks = Lists.newArrayList();
                             mApksInApex.put(aai.apexModuleName, apks);
                         }
+                        Slog.i(TAG, "Registering " + pkg.getPackageName() + " as apk-in-apex of "
+                                + aai.apexModuleName);
                         apks.add(pkg.getPackageName());
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
index dda5faf..ababb83 100644
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -376,10 +376,10 @@
                 case IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR:
                     // fall through
                 case IDataLoaderStatusListener.STREAM_SOURCE_ERROR: {
-                    return PackageManager.UNSTARTABLE_REASON_DATALOADER_TRANSPORT;
+                    return PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR;
                 }
                 case IDataLoaderStatusListener.STREAM_STORAGE_ERROR: {
-                    return PackageManager.UNSTARTABLE_REASON_DATALOADER_STORAGE;
+                    return PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE;
                 }
                 default:
                     return PackageManager.UNSTARTABLE_REASON_UNKNOWN;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9caa91b..bf6a136 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17269,13 +17269,13 @@
         private final PackageSetting mPackageSetting;
         private final String mPackageName;
         private final String mPathString;
-        private final int mUserId;
+        private final int mUid;
         private final int[] mInstalledUserIds;
 
         IncrementalStatesCallback(PackageSetting packageSetting, int userId) {
             mPackageSetting = packageSetting;
             mPackageName = packageSetting.name;
-            mUserId = userId;
+            mUid = UserHandle.getUid(userId, packageSetting.appId);
             mPathString = packageSetting.getPathString();
             final int[] allUserIds = resolveUserIds(userId);
             final ArrayList<Integer> installedUserIds = new ArrayList<>();
@@ -17311,7 +17311,7 @@
                         ps, mInstalledUserIds, mSettings.mPackages);
             }
             Bundle extras = new Bundle();
-            extras.putInt(Intent.EXTRA_UID, mUserId);
+            extras.putInt(Intent.EXTRA_UID, mUid);
             extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
             sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_LOADED, mPackageName,
                     extras, 0 /*flags*/,
@@ -17331,9 +17331,9 @@
                         ps, mInstalledUserIds, mSettings.mPackages);
             }
             Bundle extras = new Bundle();
-            extras.putInt(Intent.EXTRA_UID, mUserId);
+            extras.putInt(Intent.EXTRA_UID, mUid);
             extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
-            extras.putInt(Intent.EXTRA_REASON, reason);
+            extras.putInt(Intent.EXTRA_UNSTARTABLE_REASON, reason);
             // send broadcast to users with this app installed
             sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTARTABLE, mPackageName,
                     extras, 0 /*flags*/,
@@ -17353,7 +17353,7 @@
                         ps, mInstalledUserIds, mSettings.mPackages);
             }
             Bundle extras = new Bundle();
-            extras.putInt(Intent.EXTRA_UID, mUserId);
+            extras.putInt(Intent.EXTRA_UID, mUid);
             extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
             // send broadcast to users with this app installed
             sendPackageBroadcast(Intent.ACTION_PACKAGE_STARTABLE, mPackageName,
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index c086017..fedbed2 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
 import android.content.pm.parsing.component.ParsedPermission;
+import android.os.Build;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -445,36 +446,38 @@
         return null;
     }
 
-    public @Nullable PermissionInfo generatePermissionInfo(@NonNull String groupName, int flags) {
-        if (groupName == null) {
-            if (perm == null || perm.getGroup() == null) {
-                return generatePermissionInfo(protectionLevel, flags);
-            }
-        } else {
-            if (perm != null && groupName.equals(perm.getGroup())) {
-                return PackageInfoUtils.generatePermissionInfo(perm, flags);
-            }
-        }
-        return null;
+    @Nullable
+    public String getGroup() {
+        return perm != null ? perm.getGroup() : null;
     }
 
-    public @NonNull PermissionInfo generatePermissionInfo(int adjustedProtectionLevel, int flags) {
+    @NonNull
+    public PermissionInfo generatePermissionInfo(int flags) {
+        return generatePermissionInfo(flags, Build.VERSION_CODES.CUR_DEVELOPMENT);
+    }
+
+    @NonNull
+    public PermissionInfo generatePermissionInfo(int flags, int targetSdkVersion) {
         PermissionInfo permissionInfo;
         if (perm != null) {
-            final boolean protectionLevelChanged = protectionLevel != adjustedProtectionLevel;
             permissionInfo = PackageInfoUtils.generatePermissionInfo(perm, flags);
-            if (protectionLevelChanged) {
-                // if we return different protection level, don't use the cached info
-                permissionInfo = new PermissionInfo(permissionInfo);
-                permissionInfo.protectionLevel = adjustedProtectionLevel;
-            }
-            return permissionInfo;
+        } else {
+            permissionInfo = new PermissionInfo();
+            permissionInfo.name = name;
+            permissionInfo.packageName = sourcePackageName;
+            permissionInfo.nonLocalizedLabel = name;
         }
-        permissionInfo = new PermissionInfo();
-        permissionInfo.name = name;
-        permissionInfo.packageName = sourcePackageName;
-        permissionInfo.nonLocalizedLabel = name;
-        permissionInfo.protectionLevel = protectionLevel;
+        if (targetSdkVersion >= Build.VERSION_CODES.O) {
+            permissionInfo.protectionLevel = protectionLevel;
+        } else {
+            final int protection = protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+            if (protection == PermissionInfo.PROTECTION_SIGNATURE) {
+                // Signature permission's protection flags are always reported.
+                permissionInfo.protectionLevel = protectionLevel;
+            } else {
+                permissionInfo.protectionLevel = protection;
+            }
+        }
         return permissionInfo;
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 25e1848..45872a8 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -544,24 +544,37 @@
 
     @Override
     @Nullable
-    public PermissionInfo getPermissionInfo(String permName, String packageName,
+    public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
             @PermissionInfoFlags int flags) {
         final int callingUid = getCallingUid();
         if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
             return null;
         }
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        final AndroidPackage opPackage = mPackageManagerInt.getPackage(opPackageName);
+        final int targetSdkVersion = getPermissionInfoCallingTargetSdkVersion(opPackage,
+                callingUid);
         synchronized (mLock) {
             final BasePermission bp = mSettings.getPermissionLocked(permName);
             if (bp == null) {
                 return null;
             }
-            final int adjustedProtectionLevel = adjustPermissionProtectionFlagsLocked(
-                    bp.getProtectionLevel(), pkg, callingUid);
-            return bp.generatePermissionInfo(adjustedProtectionLevel, flags);
+            return bp.generatePermissionInfo(flags, targetSdkVersion);
         }
     }
 
+    private int getPermissionInfoCallingTargetSdkVersion(@Nullable AndroidPackage pkg, int uid) {
+        final int appId = UserHandle.getAppId(uid);
+        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID
+                || appId == Process.SHELL_UID) {
+            // System sees all flags.
+            return Build.VERSION_CODES.CUR_DEVELOPMENT;
+        }
+        if (pkg == null) {
+            return Build.VERSION_CODES.CUR_DEVELOPMENT;
+        }
+        return pkg.getTargetSdkVersion();
+    }
+
     @Override
     @Nullable
     public ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
@@ -576,9 +589,8 @@
             }
             final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
             for (BasePermission bp : mSettings.mPermissions.values()) {
-                final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
-                if (pi != null) {
-                    out.add(pi);
+                if (Objects.equals(bp.getGroup(), groupName)) {
+                    out.add(bp.generatePermissionInfo(flags));
                 }
             }
             return new ParceledListSlice<>(out);
@@ -2235,32 +2247,6 @@
         }
     }
 
-    private int adjustPermissionProtectionFlagsLocked(int protectionLevel,
-            @Nullable AndroidPackage pkg, int uid) {
-        // Signature permission flags area always reported
-        final int protectionLevelMasked = protectionLevel
-                & (PermissionInfo.PROTECTION_NORMAL
-                | PermissionInfo.PROTECTION_DANGEROUS
-                | PermissionInfo.PROTECTION_SIGNATURE);
-        if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
-            return protectionLevel;
-        }
-        // System sees all flags.
-        final int appId = UserHandle.getAppId(uid);
-        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
-                || appId == Process.SHELL_UID) {
-            return protectionLevel;
-        }
-        if (pkg == null) {
-            return protectionLevel;
-        }
-        if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
-            return protectionLevelMasked;
-        }
-        // Apps that target O see flags for all protection levels.
-        return protectionLevel;
-    }
-
     /**
      * We might auto-grant permissions if any permission of the group is already granted. Hence if
      * the group of a granted permission changes we need to revoke it to avoid having permissions of
@@ -4903,8 +4889,7 @@
                     BasePermission bp = mSettings.mPermissions.valueAt(i);
 
                     if (bp.perm != null && bp.perm.getProtection() == protection) {
-                        matchingPermissions.add(
-                                PackageInfoUtils.generatePermissionInfo(bp.perm, 0));
+                        matchingPermissions.add(bp.generatePermissionInfo(0));
                     }
                 }
             }
@@ -4925,8 +4910,7 @@
 
                     if (bp.perm != null && (bp.perm.getProtectionFlags() & protectionFlags)
                             == protectionFlags) {
-                        matchingPermissions.add(
-                                PackageInfoUtils.generatePermissionInfo(bp.perm, 0));
+                        matchingPermissions.add(bp.generatePermissionInfo(0));
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index df283e2..0313aae 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4506,7 +4506,11 @@
 
     // Called on the DisplayManager's DisplayPowerController thread.
     @Override
-    public void screenTurnedOff() {
+    public void screenTurnedOff(int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+
         if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turned off...");
 
         updateScreenOffSleepToken(true);
@@ -4529,7 +4533,11 @@
 
     // Called on the DisplayManager's DisplayPowerController thread.
     @Override
-    public void screenTurningOn(final ScreenOnListener screenOnListener) {
+    public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+
         if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on...");
 
         Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */);
@@ -4552,7 +4560,11 @@
 
     // Called on the DisplayManager's DisplayPowerController thread.
     @Override
-    public void screenTurnedOn() {
+    public void screenTurnedOn(int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+
         synchronized (mLock) {
             if (mKeyguardDelegate != null) {
                 mKeyguardDelegate.onScreenTurnedOn();
@@ -4562,7 +4574,11 @@
     }
 
     @Override
-    public void screenTurningOff(ScreenOffListener screenOffListener) {
+    public void screenTurningOff(int displayId, ScreenOffListener screenOffListener) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+
         mWindowManagerFuncs.screenTurningOff(screenOffListener);
         synchronized (mLock) {
             if (mKeyguardDelegate != null) {
@@ -4824,8 +4840,8 @@
         }
         startedWakingUp(ON_BECAUSE_OF_UNKNOWN);
         finishedWakingUp(ON_BECAUSE_OF_UNKNOWN);
-        screenTurningOn(null);
-        screenTurnedOn();
+        screenTurningOn(DEFAULT_DISPLAY, null);
+        screenTurnedOn(DEFAULT_DISPLAY);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index b96d65c..0d8d347 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -831,7 +831,7 @@
     public void finishedGoingToSleep(int why);
 
     /**
-     * Called when the device is about to turn on the screen to show content.
+     * Called when the display is about to turn on to show content.
      * When waking up, this method will be called once after the call to wakingUp().
      * When dozing, the method will be called sometime after the call to goingToSleep() and
      * may be called repeatedly in the case where the screen is pulsing on and off.
@@ -839,13 +839,13 @@
      * Must call back on the listener to tell it when the higher-level system
      * is ready for the screen to go on (i.e. the lock screen is shown).
      */
-    public void screenTurningOn(ScreenOnListener screenOnListener);
+    public void screenTurningOn(int displayId, ScreenOnListener screenOnListener);
 
     /**
-     * Called when the device has actually turned on the screen, i.e. the display power state has
-     * been set to ON and the screen is unblocked.
+     * Called when the display has actually turned on, i.e. the display power state has been set to
+     * ON and the screen is unblocked.
      */
-    public void screenTurnedOn();
+    public void screenTurnedOn(int displayId);
 
     /**
      * Called when the display would like to be turned off. This gives policy a chance to do some
@@ -854,12 +854,12 @@
      * @param screenOffListener Must be called to tell that the display power state can actually be
      *                          changed now after policy has done its work.
      */
-    public void screenTurningOff(ScreenOffListener screenOffListener);
+    public void screenTurningOff(int displayId, ScreenOffListener screenOffListener);
 
     /**
-     * Called when the device has turned the screen off.
+     * Called when the display has turned off.
      */
-    public void screenTurnedOff();
+    public void screenTurnedOff(int displayId);
 
     public interface ScreenOnListener {
         void onScreenOn();
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index a4fa745..44a6336 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -25,6 +25,7 @@
 import android.content.rollback.PackageRollbackInfo.RestoreInfo;
 import android.content.rollback.RollbackInfo;
 import android.os.UserHandle;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseIntArray;
 
@@ -37,6 +38,7 @@
 import org.json.JSONObject;
 
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.file.Files;
@@ -66,8 +68,6 @@
     //
     // * XXX, YYY are the rollbackIds for the corresponding rollbacks.
     // * rollback.json contains all relevant metadata for the rollback.
-    //
-    // TODO: Use AtomicFile for all the .json files?
     private final File mRollbackDataDir;
 
     RollbackStore(File rollbackDataDir) {
@@ -259,6 +259,8 @@
      * Saves the given rollback to persistent storage.
      */
     static void saveRollback(Rollback rollback) {
+        FileOutputStream fos = null;
+        AtomicFile file = new AtomicFile(new File(rollback.getBackupDir(), "rollback.json"));
         try {
             JSONObject dataJson = new JSONObject();
             dataJson.put("info", rollbackInfoToJson(rollback.info));
@@ -272,11 +274,16 @@
             dataJson.putOpt(
                     "extensionVersions", extensionVersionsToJson(rollback.getExtensionVersions()));
 
-            PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json"));
+            fos = file.startWrite();
+            PrintWriter pw = new PrintWriter(fos);
             pw.println(dataJson.toString());
             pw.close();
+            file.finishWrite(fos);
         } catch (JSONException | IOException e) {
             Slog.e(TAG, "Unable to save rollback for: " + rollback.info.getRollbackId(), e);
+            if (fos != null) {
+                file.failWrite(fos);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e0b671f..f43a4ce 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1630,13 +1630,14 @@
             if (modemInfo == null) {
                 return StatsManager.PULL_SKIP;
             }
-            pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, modemInfo.getTimestamp(),
+            pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+                    modemInfo.getTimestampMillis(),
                     modemInfo.getSleepTimeMillis(), modemInfo.getIdleTimeMillis(),
-                    modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis(),
-                    modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis(),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(0),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(1),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(2),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(3),
+                    modemInfo.getTransmitDurationMillisAtPowerLevel(4),
                     modemInfo.getReceiveTimeMillis(),
                     -1 /*`energy_used` field name deprecated, use -1 to indicate as unused.*/));
         } finally {
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 08eaa29..1f73977 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -28,6 +28,8 @@
 import android.app.time.TimeZoneConfiguration;
 import android.os.UserHandle;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Objects;
 
 /**
@@ -40,6 +42,7 @@
     private final @UserIdInt int mUserId;
     private final boolean mUserConfigAllowed;
     private final boolean mAutoDetectionSupported;
+    private final boolean mGeoDetectionSupported;
     private final boolean mAutoDetectionEnabled;
     private final boolean mLocationEnabled;
     private final boolean mGeoDetectionEnabled;
@@ -48,9 +51,13 @@
         mUserId = builder.mUserId;
         mUserConfigAllowed = builder.mUserConfigAllowed;
         mAutoDetectionSupported = builder.mAutoDetectionSupported;
+        mGeoDetectionSupported = builder.mGeoDetectionSupported;
         mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
         mLocationEnabled = builder.mLocationEnabled;
         mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
+        // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported
+        // cannot be true if mAutoDetectionSupported == false
+        Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported);
     }
 
     /** Returns the ID of the user this configuration is associated with. */
@@ -69,11 +76,16 @@
         return mUserConfigAllowed;
     }
 
-    /** Returns true if the device supports some form of auto time zone detection. */
+    /** Returns true if the device supports any form of auto time zone detection. */
     public boolean isAutoDetectionSupported() {
         return mAutoDetectionSupported;
     }
 
+    /** Returns true if the device supports geolocation time zone detection. */
+    public boolean isGeoDetectionSupported() {
+        return mGeoDetectionSupported;
+    }
+
     /** Returns the value of the auto time zone detection enabled setting. */
     public boolean getAutoDetectionEnabledSetting() {
         return mAutoDetectionEnabled;
@@ -101,10 +113,10 @@
      * distinct from the raw setting value.
      */
     public boolean getGeoDetectionEnabledBehavior() {
-        if (getAutoDetectionEnabledBehavior()) {
-            return mLocationEnabled && mGeoDetectionEnabled;
-        }
-        return false;
+        return getAutoDetectionEnabledBehavior()
+                && isGeoDetectionSupported()
+                && isLocationEnabled()
+                && getGeoDetectionEnabledSetting();
     }
 
     /** Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values. */
@@ -121,10 +133,10 @@
 
         // Automatic time zone detection is only supported on devices if there is a telephony
         // network available or geolocation time zone detection is possible.
-        boolean deviceHasTimeZoneDetection = isAutoDetectionSupported();
+        boolean deviceHasAutoTimeZoneDetection = isAutoDetectionSupported();
 
         final int configureAutoDetectionEnabledCapability;
-        if (!deviceHasTimeZoneDetection) {
+        if (!deviceHasAutoTimeZoneDetection) {
             configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
         } else if (!allowConfigDateTime) {
             configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
@@ -133,8 +145,9 @@
         }
         builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
 
+        boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported();
         final int configureGeolocationDetectionEnabledCapability;
-        if (!deviceHasTimeZoneDetection) {
+        if (!deviceHasLocationTimeZoneDetection) {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
         } else if (!allowConfigDateTime) {
             configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
@@ -199,6 +212,7 @@
         return mUserId == that.mUserId
                 && mUserConfigAllowed == that.mUserConfigAllowed
                 && mAutoDetectionSupported == that.mAutoDetectionSupported
+                && mGeoDetectionSupported == that.mGeoDetectionSupported
                 && mAutoDetectionEnabled == that.mAutoDetectionEnabled
                 && mLocationEnabled == that.mLocationEnabled
                 && mGeoDetectionEnabled == that.mGeoDetectionEnabled;
@@ -207,7 +221,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionSupported,
-                mAutoDetectionEnabled, mLocationEnabled, mGeoDetectionEnabled);
+                mGeoDetectionSupported, mAutoDetectionEnabled, mLocationEnabled,
+                mGeoDetectionEnabled);
     }
 
     @Override
@@ -216,6 +231,7 @@
                 + "mUserId=" + mUserId
                 + ", mUserConfigAllowed=" + mUserConfigAllowed
                 + ", mAutoDetectionSupported=" + mAutoDetectionSupported
+                + ", mGeoDetectionSupported=" + mGeoDetectionSupported
                 + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled
                 + ", mLocationEnabled=" + mLocationEnabled
                 + ", mGeoDetectionEnabled=" + mGeoDetectionEnabled
@@ -228,8 +244,10 @@
     public static class Builder {
 
         private final @UserIdInt int mUserId;
+
         private boolean mUserConfigAllowed;
         private boolean mAutoDetectionSupported;
+        private boolean mGeoDetectionSupported;
         private boolean mAutoDetectionEnabled;
         private boolean mLocationEnabled;
         private boolean mGeoDetectionEnabled;
@@ -248,6 +266,7 @@
             this.mUserId = toCopy.mUserId;
             this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
             this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported;
+            this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
             this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
             this.mLocationEnabled = toCopy.mLocationEnabled;
             this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
@@ -262,7 +281,7 @@
         }
 
         /**
-         * Sets whether automatic time zone detection is supported on this device.
+         * Sets whether any form of automatic time zone detection is supported on this device.
          */
         public Builder setAutoDetectionSupported(boolean supported) {
             mAutoDetectionSupported = supported;
@@ -270,6 +289,14 @@
         }
 
         /**
+         * Sets whether geolocation time zone detection is supported on this device.
+         */
+        public Builder setGeoDetectionSupported(boolean supported) {
+            mGeoDetectionSupported = supported;
+            return this;
+        }
+
+        /**
          * Sets the value of the automatic time zone detection enabled setting for this device.
          */
         public Builder setAutoDetectionEnabled(boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index 964dbec..941be0e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -119,13 +119,13 @@
 
     @Override
     public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
-        boolean geoDetectionEnabled = mGeoDetectionFeatureEnabled && isGeoDetectionEnabled(userId);
         return new ConfigurationInternal.Builder(userId)
                 .setUserConfigAllowed(isUserConfigAllowed(userId))
                 .setAutoDetectionSupported(isAutoDetectionSupported())
+                .setGeoDetectionSupported(isGeoDetectionSupported())
                 .setAutoDetectionEnabled(isAutoDetectionEnabled())
                 .setLocationEnabled(isLocationEnabled(userId))
-                .setGeoDetectionEnabled(geoDetectionEnabled)
+                .setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
                 .build();
     }
 
@@ -170,7 +170,11 @@
             final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled();
             setAutoDetectionEnabled(autoDetectionEnabled);
 
-            if (mGeoDetectionFeatureEnabled) {
+            // Avoid writing the geo detection enabled setting for devices that do not support geo
+            // time zone detection: if we wrote it down then we'd set the value explicitly, which
+            // would prevent detecting "default" later. That might influence what happens on later
+            // releases that support geo detection on the same hardware.
+            if (isGeoDetectionSupported()) {
                 final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
                 setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
             }
@@ -183,7 +187,11 @@
     }
 
     private boolean isAutoDetectionSupported() {
-        return deviceHasTelephonyNetwork() || mGeoDetectionFeatureEnabled;
+        return deviceHasTelephonyNetwork() || isGeoDetectionSupported();
+    }
+
+    private boolean isGeoDetectionSupported() {
+        return mGeoDetectionFeatureEnabled;
     }
 
     private boolean isAutoDetectionEnabled() {
@@ -199,10 +207,10 @@
     }
 
     private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
-        final boolean locationEnabled = isLocationEnabled(userId);
+        final boolean geoDetectionEnabledByDefault = false;
         return Settings.Secure.getIntForUser(mCr,
                 Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
-                locationEnabled ? 1 : 0 /* defaultValue */, userId) != 0;
+                (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0;
     }
 
     private void setGeoDetectionEnabled(@UserIdInt int userId, boolean enabled) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index fb06a9c..9d08b1b 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -397,6 +397,13 @@
                     return -1;
             }
         }
+
+        PackageOptimizationInfo getPackageOptimizationInfo(ArtManagerInternal artManagerInternal) {
+            return artManagerInternal == null || launchedActivityAppRecordRequiredAbi == null
+                    ? PackageOptimizationInfo.createWithNoInfo()
+                    : artManagerInternal.getPackageOptimizationInfo(applicationInfo,
+                            launchedActivityAppRecordRequiredAbi, launchedActivityName);
+        }
     }
 
     ActivityMetricsLogger(ActivityStackSupervisor supervisor, Looper looper) {
@@ -857,14 +864,8 @@
                     info.bindApplicationDelayMs);
         }
         builder.addTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, info.windowsDrawnDelayMs);
-        final ArtManagerInternal artManagerInternal = getArtManagerInternal();
         final PackageOptimizationInfo packageOptimizationInfo =
-                (artManagerInternal == null) || (info.launchedActivityAppRecordRequiredAbi == null)
-                ? PackageOptimizationInfo.createWithNoInfo()
-                : artManagerInternal.getPackageOptimizationInfo(
-                        info.applicationInfo,
-                        info.launchedActivityAppRecordRequiredAbi,
-                        info.launchedActivityName);
+                info.getPackageOptimizationInfo(getArtManagerInternal());
         builder.addTaggedData(PACKAGE_OPTIMIZATION_COMPILATION_REASON,
                 packageOptimizationInfo.getCompilationReason());
         builder.addTaggedData(PACKAGE_OPTIMIZATION_COMPILATION_FILTER,
@@ -985,6 +986,8 @@
         builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING,
                 info.mProcessRunning ? 1 : 0);
         mMetricsLogger.write(builder);
+        final PackageOptimizationInfo packageOptimizationInfo =
+                infoSnapshot.getPackageOptimizationInfo(getArtManagerInternal());
         FrameworkStatsLog.write(
                 FrameworkStatsLog.APP_START_FULLY_DRAWN,
                 info.mLastLaunchedActivity.info.applicationInfo.uid,
@@ -995,6 +998,8 @@
                 info.mLastLaunchedActivity.info.name,
                 info.mProcessRunning,
                 startupTimeMs,
+                packageOptimizationInfo.getCompilationReason(),
+                packageOptimizationInfo.getCompilationFilter(),
                 info.mSourceType,
                 info.mSourceEventDelayMs);
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9868fc1..8f2e60e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3222,6 +3222,7 @@
         getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
         mWmService.mTaskSnapshotController.onAppRemoved(this);
         mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
+        mStackSupervisor.mStoppingActivities.remove(this);
         waitingToShow = false;
 
         // TODO(b/169035022): move to a more-appropriate place.
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 8bd42f0..e4f8d8b 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -41,6 +41,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
 
+import java.io.PrintWriter;
 import java.util.Comparator;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -71,6 +72,13 @@
     IDisplayAreaOrganizer mOrganizer;
     private final Configuration mTmpConfiguration = new Configuration();
 
+    /**
+     * Whether this {@link DisplayArea} should ignore fixed-orientation request. If {@code true}, it
+     * can never specify orientation, but shows the fixed-orientation apps below it in the
+     * letterbox; otherwise, it rotates based on the fixed-orientation request.
+     */
+    protected boolean mIgnoreOrientationRequest;
+
     DisplayArea(WindowManagerService wms, Type type, String name) {
         this(wms, type, name, FEATURE_UNDEFINED);
     }
@@ -127,6 +135,52 @@
         }
     }
 
+    @Override
+    int getOrientation(int candidate) {
+        mLastOrientationSource = null;
+        if (mIgnoreOrientationRequest) {
+            return SCREEN_ORIENTATION_UNSET;
+        }
+
+        return super.getOrientation(candidate);
+    }
+
+    /**
+     * Sets whether this {@link DisplayArea} should ignore fixed-orientation request from apps and
+     * windows below it.
+     *
+     * @return Whether the display orientation changed after calling this method.
+     */
+    boolean setIgnoreOrientationRequest(boolean ignoreOrientationRequest) {
+        if (mIgnoreOrientationRequest == ignoreOrientationRequest) {
+            return false;
+        }
+        mIgnoreOrientationRequest = ignoreOrientationRequest;
+
+        // Check whether we should notify Display to update orientation.
+        if (mDisplayContent == null) {
+            return false;
+        }
+
+        // The orientation request from this DA may now be respected.
+        if (!ignoreOrientationRequest) {
+            return mDisplayContent.updateOrientation();
+        }
+
+        final int lastOrientation = mDisplayContent.getLastOrientation();
+        final WindowContainer lastOrientationSource = mDisplayContent.getLastOrientationSource();
+        if (lastOrientation == SCREEN_ORIENTATION_UNSET
+                || lastOrientation == SCREEN_ORIENTATION_UNSPECIFIED) {
+            // Orientation won't be changed.
+            return false;
+        }
+        if (lastOrientationSource == null || lastOrientationSource.isDescendantOf(this)) {
+            // Try update if the orientation may be affected.
+            return mDisplayContent.updateOrientation();
+        }
+        return false;
+    }
+
     /**
      * When a {@link DisplayArea} is repositioned, it should only be moved among its siblings of the
      * same {@link Type}.
@@ -200,6 +254,14 @@
     }
 
     @Override
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        super.dump(pw, prefix, dumpAll);
+        if (mIgnoreOrientationRequest) {
+            pw.println(prefix + "mIgnoreOrientationRequest=true");
+        }
+    }
+
+    @Override
     long getProtoFieldId() {
         return DISPLAY_AREA;
     }
@@ -370,6 +432,9 @@
                 Comparator.comparingInt(WindowToken::getWindowLayerFromType);
 
         private final Predicate<WindowState> mGetOrientingWindow = w -> {
+            if (!w.isVisible() || !w.mLegacyPolicyVisibilityAfterAnim) {
+                return false;
+            }
             final WindowManagerPolicy policy = mWmService.mPolicy;
             if (policy.isKeyguardHostWindow(w.mAttrs)) {
                 if (mWmService.mKeyguardGoingAway) {
@@ -405,6 +470,11 @@
 
         @Override
         int getOrientation(int candidate) {
+            mLastOrientationSource = null;
+            if (mIgnoreOrientationRequest) {
+                return SCREEN_ORIENTATION_UNSET;
+            }
+
             // Find a window requesting orientation.
             final WindowState win = getWindow(mGetOrientingWindow);
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e7f0e3e..0bade7d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -28,8 +28,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -2347,6 +2347,13 @@
     @Override
     int getOrientation() {
         mLastOrientationSource = null;
+        if (mIgnoreOrientationRequest) {
+            // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation
+            ProtoLog.v(WM_DEBUG_ORIENTATION,
+                    "Display id=%d is ignoring all orientation requests, return %d",
+                    mDisplayId, SCREEN_ORIENTATION_UNSPECIFIED);
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
 
         if (mWmService.mDisplayFrozen) {
             if (mWmService.mPolicy.isKeyguardLocked()) {
@@ -2363,19 +2370,15 @@
         }
 
         final int orientation = super.getOrientation();
-        if (orientation != SCREEN_ORIENTATION_UNSET && orientation != SCREEN_ORIENTATION_BEHIND) {
+        if (orientation == SCREEN_ORIENTATION_UNSET) {
+            // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation
             ProtoLog.v(WM_DEBUG_ORIENTATION,
-                    "App is requesting an orientation, return %d for display id=%d",
-                    orientation, mDisplayId);
-            return orientation;
+                    "No app or window is requesting an orientation, return %d for display id=%d",
+                    SCREEN_ORIENTATION_UNSPECIFIED, mDisplayId);
+            return SCREEN_ORIENTATION_UNSPECIFIED;
         }
 
-        ProtoLog.v(WM_DEBUG_ORIENTATION,
-                "No app is requesting an orientation, return %d for display id=%d",
-                getLastOrientation(), mDisplayId);
-        // The next app has not been requested to be visible, so we keep the current orientation
-        // to prevent freezing/unfreezing the display too early.
-        return getLastOrientation();
+        return orientation;
     }
 
     void updateDisplayInfo() {
@@ -4243,6 +4246,10 @@
 
         @Override
         int getOrientation(int candidate) {
+            if (mIgnoreOrientationRequest) {
+                return SCREEN_ORIENTATION_UNSET;
+            }
+
             // IME does not participate in orientation.
             return candidate;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 3ff369a3..4e7e0ba 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1040,9 +1040,12 @@
         }
 
         if (attrs.providesInsetsTypes != null) {
-            mContext.enforcePermission(
-                    android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
-                    "DisplayPolicy");
+            // Recents component is allowed to add inset types.
+            if (!mService.mAtmInternal.isCallerRecents(callingUid)) {
+                mContext.enforcePermission(
+                        android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+                        "DisplayPolicy");
+            }
             enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providesInsetsTypes);
 
             for (@InternalInsetsType int insetType : attrs.providesInsetsTypes) {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index dda1e2d..4ad2575 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -389,9 +389,7 @@
         final int taskCount = visibleTasks.size();
         for (int i = 0; i < taskCount; i++) {
             final Task task = visibleTasks.get(i);
-            final WindowConfiguration config = task.getWindowConfiguration();
-            if (config.tasksAreFloating()
-                    || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            if (skipAnimation(task)) {
                 continue;
             }
             addAnimation(task, !recentTaskIds.get(task.mTaskId));
@@ -434,6 +432,19 @@
         }
     }
 
+
+    /**
+     * Whether a task should be filtered from the recents animation. This can be true for tasks
+     * being displayed outside of recents.
+     */
+    private boolean skipAnimation(Task task) {
+        final WindowConfiguration config = task.getWindowConfiguration();
+        return task.isAlwaysOnTop()
+                || config.tasksAreFloating()
+                || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+    }
+
+
     @VisibleForTesting
     AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
         return addAnimation(task, isRecentTaskInvisible, false /* hidden */,
@@ -529,8 +540,9 @@
 
     void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
         if (mRunner != null) {
-            // No need to send task appeared when the task target already exists.
-            if (isAnimatingTask(task)) {
+            // No need to send task appeared when the task target already exists, or when the
+            // task is being managed as a multi-window mode outside of recents (e.g. bubbles).
+            if (isAnimatingTask(task) || skipAnimation(task)) {
                 return;
             }
             final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback);
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 25732e7..7ed22a1 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -203,16 +203,14 @@
                     .setCallsite("ScreenRotationAnimation")
                     .build();
 
-            // In case display bounds change, screenshot buffer and surface may mismatch so set a
-            // scaling mode.
-            SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
-            t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);
-            t2.apply(true /* sync */);
-
             // Capture a screenshot into the surface we just created.
             final int displayId = displayContent.getDisplayId();
             final Surface surface = mService.mSurfaceFactory.get();
+            // In case display bounds change, screenshot buffer and surface may mismatch so set a
+            // scaling mode.
             surface.copyFrom(mScreenshotLayer);
+            surface.setScalingMode(Surface.SCALING_MODE_SCALE_TO_WINDOW);
+
             SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                     mService.mDisplayManagerInternal.systemScreenshot(displayId);
             if (screenshotBuffer != null) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 249fe03..a66a2c4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1474,14 +1474,6 @@
         // Update task bounds if needed.
         adjustBoundsForDisplayChangeIfNeeded(getDisplayContent());
 
-        if (getWindowConfiguration().windowsAreScaleable()) {
-            // We force windows out of SCALING_MODE_FREEZE so that we can continue to animate them
-            // while a resize is pending.
-            forceWindowsScaleable(true /* force */);
-        } else {
-            forceWindowsScaleable(false /* force */);
-        }
-
         mRootWindowContainer.updateUIDsPresentOnDisplay();
 
         // Resume next focusable stack after reparenting to another display if we aren't removing
@@ -3780,17 +3772,6 @@
         positionChildAt(position, child, false /* includeParents */);
     }
 
-    void forceWindowsScaleable(boolean force) {
-        mWmService.openSurfaceTransaction();
-        try {
-            for (int i = mChildren.size() - 1; i >= 0; i--) {
-                mChildren.get(i).forceWindowsScaleableInTransaction(force);
-            }
-        } finally {
-            mWmService.closeSurfaceTransaction("forceWindowsScaleable");
-        }
-    }
-
     void setTaskDescription(TaskDescription taskDescription) {
         mTaskDescription = taskDescription;
     }
@@ -4792,9 +4773,11 @@
             // If the task is not yet visible when it is added to the task organizer, then we should
             // hide it to allow the task organizer to show it when it is properly reparented. We
             // skip this for tasks created by the organizer because they can synchronously update
-            // the leash before new children are added to the task.
+            // the leash before new children are added to the task.  Also skip this if the task
+            // has already been sent to the organizer which can happen before the first draw if
+            // an existing task is reported to the organizer when it first registers.
             if (!mAtmService.getTransitionController().isShellTransitionsEnabled()
-                    && !mCreatedByOrganizer
+                    && !mCreatedByOrganizer && !mTaskAppearedSent
                     && mTaskOrganizer != null && !prevHasBeenVisible) {
                 getSyncTransaction().hide(getSurfaceControl());
                 commitPendingTransaction();
@@ -4846,6 +4829,11 @@
 
     @VisibleForTesting
     boolean setTaskOrganizer(ITaskOrganizer organizer) {
+        return setTaskOrganizer(organizer, false /* skipTaskAppeared */);
+    }
+
+    @VisibleForTesting
+    boolean setTaskOrganizer(ITaskOrganizer organizer, boolean skipTaskAppeared) {
         if (mTaskOrganizer == organizer) {
             return false;
         }
@@ -4858,7 +4846,9 @@
         sendTaskVanished(prevOrganizer);
 
         if (mTaskOrganizer != null) {
-            sendTaskAppeared();
+            if (!skipTaskAppeared) {
+                sendTaskAppeared();
+            }
         } else {
             // No longer managed by any organizer.
             mTaskAppearedSent = false;
@@ -4871,6 +4861,10 @@
         return true;
     }
 
+    boolean updateTaskOrganizerState(boolean forceUpdate) {
+        return updateTaskOrganizerState(forceUpdate, false /* skipTaskAppeared */);
+    }
+
     /**
      * Called when the task state changes (ie. from windowing mode change) an the task organizer
      * state should also be updated.
@@ -4878,9 +4872,10 @@
      * @param forceUpdate Updates the task organizer to the one currently specified in the task
      *                    org controller for the task's windowing mode, ignoring the cached
      *                    windowing mode checks.
+     * @param skipTaskAppeared Skips calling taskAppeared for the new organizer if it has changed
      * @return {@code true} if task organizer changed.
      */
-    boolean updateTaskOrganizerState(boolean forceUpdate) {
+    boolean updateTaskOrganizerState(boolean forceUpdate, boolean skipTaskAppeared) {
         if (getSurfaceControl() == null) {
             // Can't call onTaskAppeared without a surfacecontrol, so defer this until after one
             // is created.
@@ -4896,7 +4891,7 @@
         if (!forceUpdate && mTaskOrganizer == organizer) {
             return false;
         }
-        return setTaskOrganizer(organizer);
+        return setTaskOrganizer(organizer, skipTaskAppeared);
     }
 
     @Override
@@ -5883,7 +5878,11 @@
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
 
         // If the top activity is the resumed one, nothing to do.
+        // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible
+        // we still want to proceed if the visibility of other windows have changed (e.g. bringing
+        // a fullscreen window forward to cover another freeform activity.)
         if (mResumedActivity == next && next.isState(RESUMED)
+                && taskDisplayArea.getWindowingMode() != WINDOWING_MODE_FREEFORM
                 && taskDisplayArea.allResumedActivitiesComplete()) {
             // Make sure we have executed any pending transitions, since there
             // should be nothing left to do at this point.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index d69fb0b..830ad5d 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -29,10 +29,12 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
@@ -150,13 +152,6 @@
     private boolean mRemoved;
 
     /**
-     * Whether the task display area should ignore fixed-orientation request. If {@code true}, it
-     * can never specify orientation, but show the fixed-orientation apps in the letterbox;
-     * otherwise, it rotates based on the fixed-orientation request when it has the focus.
-     */
-    private boolean mIgnoreOrientationRequest;
-
-    /**
      * The id of a leaf task that most recently being moved to front.
      */
     private int mLastLeafTaskToFrontId;
@@ -654,28 +649,9 @@
         }
     }
 
-    /**
-     * Sets whether the task display area should ignore fixed-orientation request from apps.
-     *
-     * @return Whether the display orientation changed
-     */
-    boolean setIgnoreOrientationRequest(boolean ignoreOrientationRequest) {
-        if (mIgnoreOrientationRequest == ignoreOrientationRequest) {
-            return false;
-        }
-
-        mIgnoreOrientationRequest = ignoreOrientationRequest;
-        if (isLastFocused()) {
-            // Update orientation if this TDA is the last focused, otherwise it shouldn't affect
-            // the display.
-            return mDisplayContent.updateOrientation();
-        }
-
-        return false;
-    }
-
     @Override
     int getOrientation(int candidate) {
+        mLastOrientationSource = null;
         // Only allow to specify orientation if this TDA is not set to ignore orientation request,
         // and it has the focus.
         if (mIgnoreOrientationRequest || !isLastFocused()) {
@@ -708,7 +684,21 @@
             return SCREEN_ORIENTATION_UNSPECIFIED;
         }
 
-        return super.getOrientation(candidate);
+        final int orientation = super.getOrientation(candidate);
+        if (orientation != SCREEN_ORIENTATION_UNSET
+                && orientation != SCREEN_ORIENTATION_BEHIND) {
+            ProtoLog.v(WM_DEBUG_ORIENTATION,
+                    "App is requesting an orientation, return %d for display id=%d",
+                    orientation, mDisplayContent.mDisplayId);
+            return orientation;
+        }
+
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "No app is requesting an orientation, return %d for display id=%d",
+                mDisplayContent.getLastOrientation(), mDisplayContent.mDisplayId);
+        // The next app has not been requested to be visible, so we keep the current orientation
+        // to prevent freezing/unfreezing the display too early.
+        return mDisplayContent.getLastOrientation();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 8201d10..48550ed0 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -32,6 +32,7 @@
 import android.app.WindowConfiguration;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ParceledListSlice;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -39,6 +40,7 @@
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
 import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -76,8 +78,6 @@
             WINDOWING_MODE_FREEFORM
     };
 
-    private final WindowManagerGlobalLock mGlobalLock;
-
     private class DeathRecipient implements IBinder.DeathRecipient {
         ITaskOrganizer mTaskOrganizer;
 
@@ -103,39 +103,38 @@
      * transaction before they are presented to the task org.
      */
     private class TaskOrganizerCallbacks {
-        final WindowManagerService mService;
         final ITaskOrganizer mTaskOrganizer;
         final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
-        private final SurfaceControl.Transaction mTransaction;
-
-        TaskOrganizerCallbacks(WindowManagerService wm, ITaskOrganizer taskOrg,
+        TaskOrganizerCallbacks(ITaskOrganizer taskOrg,
                 Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
-            mService = wm;
             mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
             mTaskOrganizer = taskOrg;
-            mTransaction = wm.mTransactionFactory.get();
         }
 
         IBinder getBinder() {
             return mTaskOrganizer.asBinder();
         }
 
+        SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
+            SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason);
+            if (!task.mCreatedByOrganizer && !visible) {
+                // To prevent flashes, we hide the task prior to sending the leash to the
+                // task org if the task has previously hidden (ie. when entering PIP)
+                mTransaction.hide(outSurfaceControl);
+                mTransaction.apply();
+            }
+            return outSurfaceControl;
+        }
+
         void onTaskAppeared(Task task) {
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task appeared taskId=%d", task.mTaskId);
             final boolean visible = task.isVisible();
             final RunningTaskInfo taskInfo = task.getTaskInfo();
             mDeferTaskOrgCallbacksConsumer.accept(() -> {
                 try {
-                    SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(),
-                            "TaskOrganizerController.onTaskAppeared");
-                    if (!task.mCreatedByOrganizer && !visible) {
-                        // To prevent flashes, we hide the task prior to sending the leash to the
-                        // task org if the task has previously hidden (ie. when entering PIP)
-                        mTransaction.hide(outSurfaceControl);
-                        mTransaction.apply();
-                    }
-                    mTaskOrganizer.onTaskAppeared(taskInfo, outSurfaceControl);
+                    mTaskOrganizer.onTaskAppeared(taskInfo, prepareLeash(task, visible,
+                            "TaskOrganizerController.onTaskAppeared"));
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Exception sending onTaskAppeared callback", e);
                 }
@@ -208,8 +207,7 @@
                     mDeferTaskOrgCallbacksConsumer != null
                             ? mDeferTaskOrgCallbacksConsumer
                             : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
-            mOrganizer = new TaskOrganizerCallbacks(mService.mWindowManager, organizer,
-                    deferTaskOrgCallbacksConsumer);
+            mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
             mDeathRecipient = new DeathRecipient(organizer);
             try {
                 organizer.asBinder().linkToDeath(mDeathRecipient, 0);
@@ -219,6 +217,18 @@
             mUid = uid;
         }
 
+        /**
+         * Register this task with this state, but doesn't trigger the task appeared callback to
+         * the organizer.
+         */
+        SurfaceControl addTaskWithoutCallback(Task t, String reason) {
+            t.mTaskAppearedSent = true;
+            if (!mOrganizedTasks.contains(t)) {
+                mOrganizedTasks.add(t);
+            }
+            return mOrganizer.prepareLeash(t, t.isVisible(), reason);
+        }
+
         void addTask(Task t) {
             if (t.mTaskAppearedSent) return;
 
@@ -265,6 +275,9 @@
         }
     }
 
+    private final ActivityTaskManagerService mService;
+    private final WindowManagerGlobalLock mGlobalLock;
+
     // List of task organizers by priority
     private final LinkedList<ITaskOrganizer> mTaskOrganizers = new LinkedList<>();
     private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();
@@ -273,8 +286,7 @@
     // Set of organized tasks (by taskId) that dispatch back pressed to their organizers
     private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet();
 
-    private final ActivityTaskManagerService mService;
-
+    private SurfaceControl.Transaction mTransaction;
     private RunningTaskInfo mTmpTaskInfo;
     private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
@@ -299,7 +311,7 @@
      * Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
      */
     @Override
-    public void registerTaskOrganizer(ITaskOrganizer organizer) {
+    public ParceledListSlice<TaskAppearedInfo> registerTaskOrganizer(ITaskOrganizer organizer) {
         enforceStackPermission("registerTaskOrganizer()");
         final int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
@@ -307,17 +319,36 @@
             synchronized (mGlobalLock) {
                 ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d",
                         organizer.asBinder(), uid);
+
+                // Defer initializing the transaction since the transaction factory can be set up
+                // by the tests after construction of the controller
+                if (mTransaction == null) {
+                    mTransaction = mService.mWindowManager.mTransactionFactory.get();
+                }
+
                 if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
                     mTaskOrganizers.add(organizer);
                     mTaskOrganizerStates.put(organizer.asBinder(),
                             new TaskOrganizerState(organizer, uid));
                 }
+
+                final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
+                final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
                 mService.mRootWindowContainer.forAllTasks((task) -> {
                     if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, task.getWindowingMode())) {
                         return;
                     }
-                    task.updateTaskOrganizerState(true /* forceUpdate */);
+
+                    boolean returnTask = !task.mCreatedByOrganizer;
+                    task.updateTaskOrganizerState(true /* forceUpdate */,
+                            returnTask /* skipTaskAppeared */);
+                    if (returnTask) {
+                        SurfaceControl outSurfaceControl = state.addTaskWithoutCallback(task,
+                                "TaskOrganizerController.registerTaskOrganizer");
+                        taskInfos.add(new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl));
+                    }
                 });
+                return new ParceledListSlice<>(taskInfos);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 38ec924..0edaa1d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -861,13 +861,6 @@
         }
     }
 
-    void forceWindowsScaleableInTransaction(boolean force) {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            wc.forceWindowsScaleableInTransaction(force);
-        }
-    }
-
     /**
      * @return {@code true} when an application can override an app transition animation on this
      * container.
@@ -2744,8 +2737,9 @@
             pw.print(prefix); pw.println("ContainerAnimator:");
             mSurfaceAnimator.dump(pw, prefix + "  ");
         }
-        if (mLastOrientationSource != null) {
+        if (mLastOrientationSource != null && this == mDisplayContent) {
             pw.println(prefix + "mLastOrientationSource=" + mLastOrientationSource);
+            pw.println(prefix + "deepestLastOrientationSource=" + getLastOrientationSource());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0b200e2..5e07f51 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -207,7 +207,7 @@
                         final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
                                 entries.next();
                         final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
-                        if (!wc.isAttached()) {
+                        if (wc == null || !wc.isAttached()) {
                             Slog.e(TAG, "Attempt to operate on detached container: " + wc);
                             continue;
                         }
@@ -380,24 +380,18 @@
         return effects;
     }
 
-    private int applyTaskDisplayAreaChanges(TaskDisplayArea taskDisplayArea,
-            WindowContainerTransaction.Change c) {
-        int effects = applyDisplayAreaChanges(taskDisplayArea, c);
-        if ((c.getChangeMask()
-                & WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
-            if (taskDisplayArea.setIgnoreOrientationRequest(c.getIgnoreOrientationRequest())) {
-                effects |= TRANSACT_EFFECTS_LIFECYCLE;
-            }
-        }
-
-        return effects;
-    }
-
-    private int applyDisplayAreaChanges(WindowContainer container,
+    private int applyDisplayAreaChanges(DisplayArea displayArea,
             WindowContainerTransaction.Change c) {
         final int[] effects = new int[1];
 
-        container.forAllTasks(task -> {
+        if ((c.getChangeMask()
+                & WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
+            if (displayArea.setIgnoreOrientationRequest(c.getIgnoreOrientationRequest())) {
+                effects[0] |= TRANSACT_EFFECTS_LIFECYCLE;
+            }
+        }
+
+        displayArea.forAllTasks(task -> {
             Task tr = (Task) task;
             if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
                 if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
@@ -475,10 +469,8 @@
 
         int effects = applyChanges(wc, c);
 
-        if (wc instanceof TaskDisplayArea) {
-            effects |= applyTaskDisplayAreaChanges((TaskDisplayArea) wc, c);
-        } else if (wc instanceof DisplayArea) {
-            effects |= applyDisplayAreaChanges(wc, c);
+        if (wc instanceof DisplayArea) {
+            effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
         } else if (wc instanceof Task) {
             effects |= applyTaskChanges(wc.asTask(), c);
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0b53bf6..3b79241 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2172,16 +2172,7 @@
         }
     }
 
-    @Override
-    void forceWindowsScaleableInTransaction(boolean force) {
-        if (mWinAnimator != null && mWinAnimator.hasSurface()) {
-            mWinAnimator.mSurfaceController.forceScaleableInTransaction(force);
-        }
-
-        super.forceWindowsScaleableInTransaction(force);
-    }
-
-    @Override
+  @Override
     void removeImmediately() {
         super.removeImmediately();
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index f342976..6349e6d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -971,10 +971,6 @@
      * @return Returns true if the surface was successfully shown.
      */
     private boolean showSurfaceRobustlyLocked() {
-        if (mWin.getWindowConfiguration().windowsAreScaleable()) {
-            mSurfaceController.forceScaleableInTransaction(true);
-        }
-
         boolean shown = mSurfaceController.showRobustlyInTransaction();
         if (!shown)
             return false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index cbe0a42..d2c36e2 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -342,11 +342,9 @@
         return false;
     }
 
-    void forceScaleableInTransaction(boolean force) {
-        // -1 means we don't override the default or client specified
-        // scaling mode.
-        int scalingMode = force ? SCALING_MODE_SCALE_TO_WINDOW : -1;
-        mSurfaceControl.setOverrideScalingMode(scalingMode);
+    void deferTransactionUntil(SurfaceControl barrier, long frame) {
+        // TODO: Logging
+        mSurfaceControl.deferTransactionUntil(barrier, frame);
     }
 
     boolean clearWindowContentFrameStats() {
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 09e3bfe..7d17109 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -41,6 +41,7 @@
 import static com.android.server.backup.testing.Utils.transferStreamedData;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -2880,8 +2881,8 @@
     }
 
     private static IterableSubject assertDirectory(Path directory) throws IOException {
-        return assertThat(oneTimeIterable(Files.newDirectoryStream(directory).iterator()))
-                .named("directory " + directory);
+        return assertWithMessage("directory " + directory).that(
+                oneTimeIterable(Files.newDirectoryStream(directory).iterator()));
     }
 
     private static void assertJournalDoesNotContain(
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java
index 3fe1f3f..3114a75 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java
@@ -17,6 +17,7 @@
 package com.android.server.backup.testing;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.robolectric.Shadows.shadowOf;
 
@@ -95,8 +96,8 @@
      * logcat before that.
      */
     public static void assertLogcatAtMost(String tag, int level) {
-        assertThat(ShadowLog.getLogsForTag(tag).stream().allMatch(logItem -> logItem.type <= level))
-                .named("All logs <= " + level)
+        assertWithMessage("All logs <= " + level).that(
+                ShadowLog.getLogsForTag(tag).stream().allMatch(logItem -> logItem.type <= level))
                 .isTrue();
     }
 
@@ -105,8 +106,8 @@
      * logcat before that.
      */
     public static void assertLogcatAtLeast(String tag, int level) {
-        assertThat(ShadowLog.getLogsForTag(tag).stream().anyMatch(logItem -> logItem.type >= level))
-                .named("Any log >= " + level)
+        assertWithMessage("Any log >= " + level).that(
+                ShadowLog.getLogsForTag(tag).stream().anyMatch(logItem -> logItem.type >= level))
                 .isTrue();
     }
 
@@ -121,11 +122,10 @@
      * that uses logcat before that.
      */
     public static void assertLogcat(String tag, int... logs) {
-        assertThat(
+        assertWithMessage("Log items (specified per level)").that(
                         ShadowLog.getLogsForTag(tag).stream()
                                 .map(logItem -> logItem.type)
                                 .collect(toSet()))
-                .named("Log items (specified per level)")
                 .containsExactly(IntStream.of(logs).boxed().toArray());
     }
 
@@ -135,15 +135,13 @@
 
     /** Declare shadow {@link ShadowEventLog} to use this. */
     public static void assertEventLogged(int tag, Object... values) {
-        assertThat(ShadowEventLog.getEntries())
-                .named("Event logs")
+        assertWithMessage("Event logs").that(ShadowEventLog.getEntries())
                 .contains(new ShadowEventLog.Entry(tag, Arrays.asList(values)));
     }
 
     /** Declare shadow {@link ShadowEventLog} to use this. */
     public static void assertEventNotLogged(int tag, Object... values) {
-        assertThat(ShadowEventLog.getEntries())
-                .named("Event logs")
+        assertWithMessage("Event logs").that(ShadowEventLog.getEntries())
                 .doesNotContain(new ShadowEventLog.Entry(tag, Arrays.asList(values)));
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index d61783e..a5f0834 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -48,10 +48,13 @@
 import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
 import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK;
 import static com.android.server.alarm.AlarmManagerService.WORKING_INDEX;
+import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
+import static com.android.server.alarm.Constants.TEST_CALLING_UID;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -82,6 +85,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -108,6 +112,8 @@
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.concurrent.Executor;
@@ -116,9 +122,7 @@
 @RunWith(AndroidJUnit4.class)
 public class AlarmManagerServiceTest {
     private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
-    private static final String TEST_CALLING_PACKAGE = "com.android.framework.test-package";
     private static final int SYSTEM_UI_UID = 12345;
-    private static final int TEST_CALLING_UID = 67890;
     private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
 
     private long mAppStandbyWindow;
@@ -350,19 +354,31 @@
     }
 
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
-        setTestAlarm(type, triggerTime, operation, 0, TEST_CALLING_UID);
+        setTestAlarm(type, triggerTime, operation, 0, AlarmManager.FLAG_STANDALONE,
+                TEST_CALLING_UID);
     }
 
     private void setRepeatingTestAlarm(int type, long firstTrigger, long interval,
             PendingIntent pi) {
-        setTestAlarm(type, firstTrigger, pi, interval, TEST_CALLING_UID);
+        setTestAlarm(type, firstTrigger, pi, interval, AlarmManager.FLAG_STANDALONE,
+                TEST_CALLING_UID);
+    }
+
+    private void setIdleUntilAlarm(int type, long triggerTime, PendingIntent pi) {
+        setTestAlarm(type, triggerTime, pi, 0, AlarmManager.FLAG_IDLE_UNTIL, TEST_CALLING_UID);
+    }
+
+    private void setWakeFromIdle(int type, long triggerTime, PendingIntent pi) {
+        // Note: Only alarm clock alarms are allowed to include this flag in the actual service.
+        // But this is a unit test so we'll only test the flag for granularity and convenience.
+        setTestAlarm(type, triggerTime, pi, 0,
+                AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE, TEST_CALLING_UID);
     }
 
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
-            int callingUid) {
-        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval,
-                operation, null, "test", AlarmManager.FLAG_STANDALONE, null, null,
-                callingUid, TEST_CALLING_PACKAGE);
+            int flags, int callingUid) {
+        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval, operation, null,
+                "test", flags, null, null, callingUid, TEST_CALLING_PACKAGE);
     }
 
     private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
@@ -1002,7 +1018,7 @@
         for (int i = 0; i < numAlarms; i++) {
             int mockUid = UserHandle.getUid(mockUserId, 1234 + i);
             setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10,
-                    getNewMockPendingIntent(mockUid), 0, mockUid);
+                    getNewMockPendingIntent(mockUid), 0, AlarmManager.FLAG_STANDALONE, mockUid);
         }
         assertEquals(numAlarms, mService.mAlarmsPerUid.size());
         mService.removeUserLocked(mockUserId);
@@ -1142,6 +1158,116 @@
         }
     }
 
+    @Test
+    public void singleIdleUntil() {
+        doReturn(0).when(mService).fuzzForDuration(anyLong());
+
+        final PendingIntent idleUntilPi6 = getNewMockPendingIntent();
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, idleUntilPi6);
+
+        assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi6, null));
+        assertEquals(mNowElapsedTest + 6, mTestTimer.getElapsed());
+        assertEquals(mNowElapsedTest + 6, mService.mPendingIdleUntil.getWhenElapsed());
+
+        final PendingIntent idleUntilPi2 = getNewMockPendingIntent();
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, idleUntilPi2);
+
+        // The same mPendingIdleUntil should get updated, even with a different PendingIntent.
+        assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi2, null));
+        assertEquals(mNowElapsedTest + 2, mTestTimer.getElapsed());
+        assertEquals(1, mService.mAlarmStore.size());
+
+        final PendingIntent idleUntilPi10 = getNewMockPendingIntent();
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10, idleUntilPi10);
+
+        // The same thing should happen even when the new alarm is in farther in the future.
+        assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi10, null));
+        assertEquals(mNowElapsedTest + 10, mTestTimer.getElapsed());
+        assertEquals(1, mService.mAlarmStore.size());
+    }
+
+    @Test
+    public void nextWakeFromIdle() throws Exception {
+        assertNull(mService.mNextWakeFromIdle);
+
+        final PendingIntent wakeFromIdle6 = getNewMockPendingIntent();
+        final long trigger6 = mNowElapsedTest + 6;
+        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger6, wakeFromIdle6);
+
+        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
+        assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());
+        assertEquals(trigger6, mTestTimer.getElapsed());
+
+        final PendingIntent wakeFromIdle10 = getNewMockPendingIntent();
+        final long trigger10 = mNowElapsedTest + 10;
+        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger10, wakeFromIdle10);
+
+        // mNextWakeFromIdle should not get updated.
+        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
+        assertEquals(trigger6, mTestTimer.getElapsed());
+        assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());
+
+        final PendingIntent wakeFromIdle3 = getNewMockPendingIntent();
+        final long trigger3 = mNowElapsedTest + 3;
+        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger3, wakeFromIdle3);
+
+        // mNextWakeFromIdle should always reflect the next earliest wake_from_idle alarm.
+        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle3, null));
+        assertEquals(trigger3, mTestTimer.getElapsed());
+        assertEquals(trigger3, mService.mNextWakeFromIdle.getWhenElapsed());
+
+        mNowElapsedTest = trigger3;
+        mTestTimer.expire();
+
+        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
+        assertEquals(trigger6, mTestTimer.getElapsed());
+        assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());
+
+        mService.removeLocked(wakeFromIdle6, null);
+
+        assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle10, null));
+        assertEquals(trigger10, mTestTimer.getElapsed());
+        assertEquals(trigger10, mService.mNextWakeFromIdle.getWhenElapsed());
+
+        mService.removeLocked(wakeFromIdle10, null);
+        assertNull(mService.mNextWakeFromIdle);
+    }
+
+    @Test
+    public void idleUntilBeforeWakeFromIdle() {
+        doReturn(0).when(mService).fuzzForDuration(anyLong());
+
+        final PendingIntent idleUntilPi = getNewMockPendingIntent();
+        final long requestedIdleUntil = mNowElapsedTest + 10;
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, requestedIdleUntil, idleUntilPi);
+
+        assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed());
+
+        final PendingIntent wakeFromIdle5 = getNewMockPendingIntent();
+        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, wakeFromIdle5);
+        assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());
+
+        final PendingIntent wakeFromIdle8 = getNewMockPendingIntent();
+        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 8, wakeFromIdle8);
+        assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());
+
+        final PendingIntent wakeFromIdle12 = getNewMockPendingIntent();
+        setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 12, wakeFromIdle12);
+        assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());
+
+        mService.removeLocked(wakeFromIdle5, null);
+        assertEquals(mNowElapsedTest + 8, mService.mPendingIdleUntil.getWhenElapsed());
+
+        mService.removeLocked(wakeFromIdle8, null);
+        assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed());
+
+        mService.removeLocked(idleUntilPi, null);
+        assertNull(mService.mPendingIdleUntil);
+
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15, idleUntilPi);
+        assertEquals(mNowElapsedTest + 12, mService.mPendingIdleUntil.getWhenElapsed());
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {
@@ -1149,4 +1275,12 @@
         }
         LocalServices.removeServiceForTest(AlarmManagerInternal.class);
     }
+
+    private void dumpAllAlarms(String tag, ArrayList<Alarm> alarms) {
+        System.out.println(tag + ": ");
+        IndentingPrintWriter ipw = new IndentingPrintWriter(new PrintWriter(System.out));
+        AlarmManagerService.dumpAlarmList(ipw, alarms, mNowElapsedTest,
+                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
+        ipw.close();
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
index 9e43b4a..f0490ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.alarm;
 
+import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
+import static com.android.server.alarm.Constants.TEST_CALLING_UID;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -35,9 +38,6 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class AlarmStoreTest {
-    private static final int TEST_CALLING_UID = 12345;
-    private static final String TEST_CALLING_PACKAGE = "android.alarm.unit.test";
-
     private AlarmStore mAlarmStore;
 
     @Before
@@ -45,22 +45,22 @@
         mAlarmStore = new BatchingAlarmStore(null);
     }
 
-    private static Alarm createAlarm(long whenElapsed, long windowLength, PendingIntent mockPi,
+    private static Alarm createAlarm(long whenElapsed, long windowLength,
             AlarmManager.AlarmClockInfo alarmClock) {
-        return createAlarm(AlarmManager.ELAPSED_REALTIME, whenElapsed, windowLength, mockPi,
+        return createAlarm(AlarmManager.ELAPSED_REALTIME, whenElapsed, windowLength,
                 alarmClock);
     }
 
     private static Alarm createWakeupAlarm(long whenElapsed, long windowLength,
-            PendingIntent mockPi, AlarmManager.AlarmClockInfo alarmClock) {
-        return createAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength, mockPi,
+            AlarmManager.AlarmClockInfo alarmClock) {
+        return createAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength,
                 alarmClock);
     }
 
     private static Alarm createAlarm(int type, long whenElapsed, long windowLength,
-            PendingIntent mockPi, AlarmManager.AlarmClockInfo alarmClock) {
-        return new Alarm(type, whenElapsed, whenElapsed, windowLength, whenElapsed + windowLength,
-                0, mockPi, null, null, null, 0, alarmClock, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+            AlarmManager.AlarmClockInfo alarmClock) {
+        return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class),
+                null, null, null, 0, alarmClock, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
     }
 
     private void addAlarmsToStore(Alarm... alarms) {
@@ -71,11 +71,11 @@
 
     @Test
     public void add() {
-        final Alarm a1 = createAlarm(1, 0, mock(PendingIntent.class), null);
+        final Alarm a1 = createAlarm(1, 0, null);
         mAlarmStore.add(a1);
         assertEquals(1, mAlarmStore.size());
 
-        final Alarm a2 = createAlarm(2, 0, mock(PendingIntent.class), null);
+        final Alarm a2 = createAlarm(2, 0, null);
         mAlarmStore.add(a2);
         assertEquals(2, mAlarmStore.size());
 
@@ -86,17 +86,17 @@
 
     @Test
     public void remove() {
-        final Alarm a1 = createAlarm(1, 0, mock(PendingIntent.class), null);
-        final Alarm a2 = createAlarm(2, 0, mock(PendingIntent.class), null);
-        final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
+        final Alarm a1 = createAlarm(1, 0, null);
+        final Alarm a2 = createAlarm(2, 0, null);
+        final Alarm a5 = createAlarm(5, 0, null);
         addAlarmsToStore(a1, a2, a5);
 
-        ArrayList<Alarm> removed = mAlarmStore.remove(a -> (a.whenElapsed < 4));
+        ArrayList<Alarm> removed = mAlarmStore.remove(a -> (a.getWhenElapsed() < 4));
         assertEquals(2, removed.size());
         assertEquals(1, mAlarmStore.size());
         assertTrue(removed.contains(a1) && removed.contains(a2));
 
-        final Alarm a8 = createAlarm(8, 0, mock(PendingIntent.class), null);
+        final Alarm a8 = createAlarm(8, 0, null);
         addAlarmsToStore(a8, a2, a1);
 
         removed = mAlarmStore.remove(unused -> false);
@@ -110,10 +110,10 @@
 
     @Test
     public void removePendingAlarms() {
-        final Alarm a1_11 = createAlarm(1, 10, mock(PendingIntent.class), null);
-        final Alarm a2_5 = createAlarm(2, 3, mock(PendingIntent.class), null);
-        final Alarm a6_9 = createAlarm(6, 3, mock(PendingIntent.class), null);
-        addAlarmsToStore(a2_5, a6_9, a1_11);
+        final Alarm a1to11 = createAlarm(1, 10, null);
+        final Alarm a2to5 = createAlarm(2, 3, null);
+        final Alarm a6to9 = createAlarm(6, 3, null);
+        addAlarmsToStore(a2to5, a6to9, a1to11);
 
         final ArrayList<Alarm> pendingAt0 = mAlarmStore.removePendingAlarms(0);
         assertEquals(0, pendingAt0.size());
@@ -121,24 +121,24 @@
 
         final ArrayList<Alarm> pendingAt3 = mAlarmStore.removePendingAlarms(3);
         assertEquals(2, pendingAt3.size());
-        assertTrue(pendingAt3.contains(a1_11) && pendingAt3.contains(a2_5));
+        assertTrue(pendingAt3.contains(a1to11) && pendingAt3.contains(a2to5));
         assertEquals(1, mAlarmStore.size());
 
-        addAlarmsToStore(a2_5, a1_11);
+        addAlarmsToStore(a2to5, a1to11);
         final ArrayList<Alarm> pendingAt7 = mAlarmStore.removePendingAlarms(7);
         assertEquals(3, pendingAt7.size());
-        assertTrue(pendingAt7.contains(a1_11) && pendingAt7.contains(a2_5) && pendingAt7.contains(
-                a6_9));
+        assertTrue(pendingAt7.contains(a1to11) && pendingAt7.contains(a2to5) && pendingAt7.contains(
+                a6to9));
         assertEquals(0, mAlarmStore.size());
     }
 
     @Test
     public void getNextWakeupDeliveryTime() {
-        final Alarm a1_10 = createAlarm(1, 9, mock(PendingIntent.class), null);
-        final Alarm a3_8_wakeup = createWakeupAlarm(3, 5, mock(PendingIntent.class), null);
-        final Alarm a6_wakeup = createWakeupAlarm(6, 0, mock(PendingIntent.class), null);
-        final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
-        addAlarmsToStore(a5, a6_wakeup, a3_8_wakeup, a1_10);
+        final Alarm a1to10 = createAlarm(1, 9, null);
+        final Alarm a3to8wakeup = createWakeupAlarm(3, 5, null);
+        final Alarm a6wakeup = createWakeupAlarm(6, 0, null);
+        final Alarm a5 = createAlarm(5, 0, null);
+        addAlarmsToStore(a5, a6wakeup, a3to8wakeup, a1to10);
 
         // The wakeup alarms are [6] and [3, 8], hence 6 is the latest time till when we can
         // defer delivering any wakeup alarm.
@@ -155,11 +155,11 @@
 
     @Test
     public void getNextDeliveryTime() {
-        final Alarm a1_10 = createAlarm(1, 9, mock(PendingIntent.class), null);
-        final Alarm a3_8_wakeup = createWakeupAlarm(3, 5, mock(PendingIntent.class), null);
-        final Alarm a6_wakeup = createWakeupAlarm(6, 0, mock(PendingIntent.class), null);
-        final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
-        addAlarmsToStore(a5, a6_wakeup, a3_8_wakeup, a1_10);
+        final Alarm a1to10 = createAlarm(1, 9, null);
+        final Alarm a3to8wakeup = createWakeupAlarm(3, 5, null);
+        final Alarm a6wakeup = createWakeupAlarm(6, 0, null);
+        final Alarm a5 = createAlarm(5, 0, null);
+        addAlarmsToStore(a5, a6wakeup, a3to8wakeup, a1to10);
 
         assertTrue(mAlarmStore.getNextDeliveryTime() <= 5);
 
@@ -168,24 +168,22 @@
     }
 
     @Test
-    public void recalculateAlarmDeliveries() {
-        final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
-        final Alarm a8 = createAlarm(8, 0, mock(PendingIntent.class), null);
-        final Alarm a10 = createAlarm(10, 0, mock(PendingIntent.class), null);
+    public void updateAlarmDeliveries() {
+        final Alarm a5 = createAlarm(5, 0, null);
+        final Alarm a8 = createAlarm(8, 0, null);
+        final Alarm a10 = createAlarm(10, 0, null);
         addAlarmsToStore(a8, a10, a5);
 
         assertEquals(5, mAlarmStore.getNextDeliveryTime());
 
-        mAlarmStore.recalculateAlarmDeliveries(a -> {
-            a.whenElapsed += 3;
-            a.maxWhenElapsed = a.whenElapsed;
+        mAlarmStore.updateAlarmDeliveries(a -> {
+            a.setPolicyElapsed(Alarm.REQUESTER_POLICY_INDEX, a.getWhenElapsed() + 3);
             return true;
         });
         assertEquals(8, mAlarmStore.getNextDeliveryTime());
 
-        mAlarmStore.recalculateAlarmDeliveries(a -> {
-            a.whenElapsed = 20 - a.whenElapsed;
-            a.maxWhenElapsed = a.whenElapsed;
+        mAlarmStore.updateAlarmDeliveries(a -> {
+            a.setPolicyElapsed(Alarm.REQUESTER_POLICY_INDEX, 20 - a.getWhenElapsed());
             return true;
         });
         assertEquals(7, mAlarmStore.getNextDeliveryTime());
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
new file mode 100644
index 0000000..efcfae3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.server.alarm;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME;
+
+import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
+import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
+import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
+import static com.android.server.alarm.Constants.TEST_CALLING_UID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.app.PendingIntent;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AlarmTest {
+
+    private Alarm createDefaultAlarm(long requestedElapsed, long windowLength) {
+        return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0,
+                mock(PendingIntent.class), null, null, null, 0, null, TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE);
+    }
+
+    @Test
+    public void initSetsOnlyRequesterPolicy() {
+        final Alarm a = createDefaultAlarm(4567, 2);
+        assertEquals(4567, a.getPolicyElapsed(REQUESTER_POLICY_INDEX));
+        assertEquals(0, a.getPolicyElapsed(APP_STANDBY_POLICY_INDEX));
+    }
+
+    @Test
+    public void whenElapsed() {
+        final Alarm a = createDefaultAlarm(0, 0);
+
+        a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4);
+        a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10);
+        assertEquals(10, a.getWhenElapsed());
+
+        a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 12);
+        assertEquals(12, a.getWhenElapsed());
+
+        a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 7);
+        assertEquals(10, a.getWhenElapsed());
+
+        a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 2);
+        assertEquals(7, a.getWhenElapsed());
+
+        a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 7);
+        assertEquals(7, a.getWhenElapsed());
+    }
+
+    @Test
+    public void maxWhenElapsed() {
+        final Alarm a = createDefaultAlarm(10, 12);
+        assertEquals(22, a.getMaxWhenElapsed());
+
+        a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15);
+        assertEquals(27, a.getMaxWhenElapsed());
+
+        a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 2);
+        assertEquals(14, a.getMaxWhenElapsed());
+
+        a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 5);
+        assertEquals(14, a.getMaxWhenElapsed());
+
+        a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 16);
+        assertEquals(16, a.getMaxWhenElapsed());
+
+        a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 12);
+        assertEquals(14, a.getMaxWhenElapsed());
+    }
+
+    @Test
+    public void setPolicyElapsed() {
+        final Alarm exactAlarm = createDefaultAlarm(10, 0);
+
+        assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
+        assertTrue(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
+
+        assertFalse(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 8));
+        assertFalse(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 10));
+        assertFalse(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8));
+
+        assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 7));
+
+        final Alarm inexactAlarm = createDefaultAlarm(10, 5);
+
+        assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
+        assertTrue(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
+
+        // whenElapsed won't change, but maxWhenElapsed will.
+        assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 8));
+        assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 10));
+
+        assertFalse(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
index 6465739..5bb6a42 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
@@ -45,7 +45,7 @@
         }
         uidAlarms.add(new Alarm(
                 removeIt ? RTC : RTC_WAKEUP,
-                0, 0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
+                0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
         return all;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/Constants.java b/services/tests/mockingservicestests/src/com/android/server/alarm/Constants.java
new file mode 100644
index 0000000..2552db8
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/Constants.java
@@ -0,0 +1,22 @@
+/*
+ * 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.server.alarm;
+
+public interface Constants {
+    String TEST_CALLING_PACKAGE = "com.android.framework.test-package";
+    int TEST_CALLING_UID = 67890;
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
index a0f48c6..d67eddd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
@@ -79,7 +79,7 @@
             Location coarse = mFudger.createCoarse(fine);
 
             assertThat(coarse).isNotNull();
-            assertThat(coarse).isNotSameAs(fine);
+            assertThat(coarse).isNotSameInstanceAs(fine);
             assertThat(coarse.hasBearing()).isFalse();
             assertThat(coarse.hasSpeed()).isFalse();
             assertThat(coarse.hasAltitude()).isFalse();
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index dbaa482..9ee1205 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -20,7 +20,6 @@
 
 import static com.android.server.attention.AttentionManagerService.ATTENTION_CACHE_BUFFER_SIZE;
 import static com.android.server.attention.AttentionManagerService.DEFAULT_STALE_AFTER_MILLIS;
-import static com.android.server.attention.AttentionManagerService.DEVICE_CONFIG_MAX_STALENESS_MILLIS;
 import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFTER_MILLIS;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -40,7 +39,6 @@
 import android.os.IThermalService;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.provider.DeviceConfig;
 import android.service.attention.IAttentionCallback;
 import android.service.attention.IAttentionService;
@@ -118,7 +116,7 @@
 
     @Test
     public void testCheckAttention_returnFalseWhenPowerManagerNotInteract() throws RemoteException {
-        doReturn(true).when(mSpyAttentionManager).isAttentionServiceSupported();
+        mSpyAttentionManager.mIsServiceEnabled = true;
         doReturn(false).when(mMockIPowerManager).isInteractive();
         AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class);
         assertThat(mSpyAttentionManager.checkAttention(mTimeout, callback)).isFalse();
@@ -126,7 +124,7 @@
 
     @Test
     public void testCheckAttention_callOnSuccess() throws RemoteException {
-        doReturn(true).when(mSpyAttentionManager).isAttentionServiceSupported();
+        mSpyAttentionManager.mIsServiceEnabled = true;
         doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
         doReturn(true).when(mMockIPowerManager).isInteractive();
         doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
@@ -207,38 +205,6 @@
                 DEFAULT_STALE_AFTER_MILLIS);
     }
 
-    @Test
-    public void testEnsureDeviceConfigCachedValuesFreshness_doesNotCallDeviceConfigTooFrequently() {
-        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
-                KEY_STALE_AFTER_MILLIS, String.valueOf(DEFAULT_STALE_AFTER_MILLIS), false);
-        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
-                DEFAULT_STALE_AFTER_MILLIS);
-
-        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
-                KEY_STALE_AFTER_MILLIS, "123", false);
-
-        // New value is ignored
-        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
-                DEFAULT_STALE_AFTER_MILLIS);
-    }
-
-
-    @Test
-    public void testEnsureDeviceConfigCachedValuesFreshness_refreshesWhenStale() {
-        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
-                KEY_STALE_AFTER_MILLIS, String.valueOf(DEFAULT_STALE_AFTER_MILLIS), false);
-        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
-                DEFAULT_STALE_AFTER_MILLIS);
-
-        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
-                KEY_STALE_AFTER_MILLIS, "123", false);
-        mSpyAttentionManager.mLastReadDeviceConfigMillis =
-                SystemClock.elapsedRealtime() - (DEVICE_CONFIG_MAX_STALENESS_MILLIS + 1);
-
-        // Values are refreshed
-        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(123);
-    }
-
     private class MockIAttentionService implements IAttentionService {
         public void checkAttention(IAttentionCallback callback) throws RemoteException {
             callback.onSuccess(0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 59d4e2a..058794a 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.devicestate;
 
-import static com.android.server.devicestate.DeviceStateManagerService.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index b69cc47..ec747ac 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -83,8 +83,8 @@
                 BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
                 INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
                 DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
-                mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mContext,
-                mDisplayDeviceConfig);
+                mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mContext
+        );
         controller.setLoggingEnabled(true);
 
         // Configure the brightness controller and grab an instance of the sensor listener,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 7cbf571..ef98b98 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -814,7 +814,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
-        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+        assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
     }
 
     @Test
@@ -834,7 +834,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
-        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+        assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
     }
 
     @Test
@@ -853,7 +853,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
-        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+        assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
     }
 
     @Test
@@ -872,7 +872,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
-        assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+        assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java
index cdff97b..9ab762a 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java
@@ -76,7 +76,7 @@
 
         // Act and assert
         assertThat(MultiClientInputMethodManagerService.resolveMultiClientImeService(
-                asList(imeService))).isSameAs(imeService);
+                asList(imeService))).isSameInstanceAs(imeService);
     }
 
     private ResolveInfo buildResolveInfo(String permission, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 41be54a..f26e094 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -194,7 +194,7 @@
         assertThat(rulesFetched.size())
                 .isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount);
         assertThat(rulesFetched)
-                .containsAllOf(
+                .containsAtLeast(
                         getPackageNameIndexedRule(installedPackageName),
                         getAppCertificateIndexedRule(installedAppCertificate));
     }
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
index 192ade7..4810563 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
@@ -45,6 +45,7 @@
         return new ConfigurationInternal.Builder(userId)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionSupported(true)
+                .setGeoDetectionSupported(true)
                 .setAutoDetectionEnabled(true)
                 .setLocationEnabled(true)
                 .setGeoDetectionEnabled(geoDetectionEnabled)
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java
index 6921bb2..8d5687c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java
@@ -71,7 +71,7 @@
         Map<String, Pair<SecretKey, byte[]>> filteredKeys =
                 mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
         assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
-        assertThat(filteredKeys.entrySet()).containsAllIn(rawKeys.entrySet());
+        assertThat(filteredKeys.entrySet()).containsAtLeastElementsIn(rawKeys.entrySet());
     }
 
     @Test
@@ -85,7 +85,7 @@
         Map<String, Pair<SecretKey, byte[]>> filteredKeys =
                 mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
         assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
-        assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
+        assertThat(rawKeys.entrySet()).containsAtLeastElementsIn(filteredKeys.entrySet());
     }
 
     @Test
@@ -100,7 +100,7 @@
         Map<String, Pair<SecretKey, byte[]>> filteredKeys =
                 mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
         assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
-        assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
+        assertThat(rawKeys.entrySet()).containsAtLeastElementsIn(filteredKeys.entrySet());
     }
 
     @Test
@@ -122,7 +122,7 @@
         Map<String, Pair<SecretKey, byte[]>> filteredKeys =
                 mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
         assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
-        assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
+        assertThat(rawKeys.entrySet()).containsAtLeastElementsIn(filteredKeys.entrySet());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
index 9836c64..b0cb2ea 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
@@ -70,7 +70,7 @@
         CertXml certXml = CertXml.parse(certXmlBytes);
         List<X509Certificate> endpointCerts = certXml.getAllEndpointCerts();
         assertThat(endpointCerts).hasSize(3);
-        assertThat(endpointCerts).containsAllOf(TestData.LEAF_CERT_1, TestData.LEAF_CERT_2);
+        assertThat(endpointCerts).containsAtLeast(TestData.LEAF_CERT_1, TestData.LEAF_CERT_2);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 154d42c..3a292de 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -72,7 +72,8 @@
         @JvmStatic
         fun checkAllCasesHandled() {
             // Assert that all states have been tested at least once.
-            assertThat(CASES.map { it.state }.distinct()).containsAllIn(ActorState.values())
+            assertThat(CASES.map { it.state }.distinct())
+                    .containsAtLeastElementsIn(ActorState.values())
         }
 
         @BeforeClass
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
index 62e135b..c4c2f68 100644
--- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -146,7 +146,7 @@
         // Test that package is now unstartable
         assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
         assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_TRANSPORT,
+        assertEquals(PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR,
                 mUnstartableReason.get());
     }
 
@@ -160,7 +160,7 @@
         // Test that package is now unstartable
         assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
         assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_TRANSPORT,
+        assertEquals(PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR,
                 mUnstartableReason.get());
     }
 
@@ -181,7 +181,7 @@
         // Test that package is now unstartable
         assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
         assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_STORAGE,
+        assertEquals(PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE,
                 mUnstartableReason.get());
     }
 
@@ -202,7 +202,7 @@
         // Test that package is now unstartable
         assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
         assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_DATALOADER_STORAGE,
+        assertEquals(PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE,
                 mUnstartableReason.get());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 44bb58f..22b0715 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -639,7 +639,7 @@
         UserInfo user1 = createUser("User 1", 0);
         UserInfo user2 = createUser("User 2", 0);
         long[] serialNumbersOfUsers = mUserManager.getSerialNumbersOfUsers(false);
-        assertThat(serialNumbersOfUsers).asList().containsAllOf(
+        assertThat(serialNumbersOfUsers).asList().containsAtLeast(
                 (long) user1.serialNumber, (long) user2.serialNumber);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index b6a0979..eedc978 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -46,41 +46,25 @@
     private static final String INSTALLER = "some.installer";
 
     private static final Correspondence<VersionedPackage, VersionedPackage> VER_PKG_CORR =
-            new Correspondence<VersionedPackage, VersionedPackage>() {
-                @Override
-                public boolean compare(VersionedPackage a, VersionedPackage b) {
-                    if (a == null || b == null) {
-                        return a == b;
-                    }
-                    return a.equals(b);
+            Correspondence.from((VersionedPackage a, VersionedPackage b) -> {
+                if (a == null || b == null) {
+                    return a == b;
                 }
-
-                @Override
-                public String toString() {
-                    return "is the same as";
-                }
-            };
+                return a.equals(b);
+            }, "is the same as");
 
     private static final Correspondence<PackageRollbackInfo.RestoreInfo,
             PackageRollbackInfo.RestoreInfo>
             RESTORE_INFO_CORR =
-            new Correspondence<PackageRollbackInfo.RestoreInfo, PackageRollbackInfo.RestoreInfo>() {
-                @Override
-                public boolean compare(PackageRollbackInfo.RestoreInfo a,
-                        PackageRollbackInfo.RestoreInfo b) {
-                    if (a == null || b == null) {
-                        return a == b;
-                    }
-                    return a.userId == b.userId
-                            && a.appId == b.appId
-                            && Objects.equals(a.seInfo, b.seInfo);
+            Correspondence.from((PackageRollbackInfo.RestoreInfo a,
+                    PackageRollbackInfo.RestoreInfo b) -> {
+                if (a == null || b == null) {
+                    return a == b;
                 }
-
-                @Override
-                public String toString() {
-                    return "is the same as";
-                }
-            };
+                return a.userId == b.userId
+                        && a.appId == b.appId
+                        && Objects.equals(a.seInfo, b.seInfo);
+            }, "is the same as");
 
     private static final String JSON_ROLLBACK_NO_EXT = "{'info':{'rollbackId':123,'packages':"
             + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
index 46224cb..8fb2e68 100644
--- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
@@ -132,7 +132,7 @@
                     appSizes.getLong(i), cacheSizes.getLong(i));
             apps.add(app);
         }
-        assertThat(apps).containsAllOf(new AppSizeGrouping("com.test.app", 1100, 20),
+        assertThat(apps).containsAtLeast(new AppSizeGrouping("com.test.app", 1100, 20),
                 new AppSizeGrouping("com.test.app2", 11, 2));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 3fc294d..682a80c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -47,6 +47,7 @@
         ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionSupported(true)
+                .setGeoDetectionSupported(true)
                 .setAutoDetectionEnabled(true)
                 .setLocationEnabled(true)
                 .setGeoDetectionEnabled(true)
@@ -108,6 +109,7 @@
         ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(false)
                 .setAutoDetectionSupported(true)
+                .setGeoDetectionSupported(true)
                 .setAutoDetectionEnabled(true)
                 .setLocationEnabled(true)
                 .setGeoDetectionEnabled(true)
@@ -169,6 +171,7 @@
         ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionSupported(false)
+                .setGeoDetectionSupported(false)
                 .setAutoDetectionEnabled(true)
                 .setLocationEnabled(true)
                 .setGeoDetectionEnabled(true)
@@ -220,4 +223,67 @@
             assertTrue(configuration.isGeoDetectionEnabled());
         }
     }
+
+    /**
+     * Tests when {@link ConfigurationInternal#isAutoDetectionSupported()} is true, but
+     * {@link ConfigurationInternal#isGeoDetectionSupported()} is false.
+     */
+    @Test
+    public void test_geoDetectNotSupported() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setAutoDetectionSupported(true)
+                .setGeoDetectionSupported(false)
+                .setAutoDetectionEnabled(true)
+                .setLocationEnabled(true)
+                .setGeoDetectionEnabled(true)
+                .build();
+        {
+            ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(true)
+                    .build();
+            assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+            assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOnConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_POSSESSED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_APPLICABLE,
+                    capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertTrue(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
+        }
+        {
+            ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabled(false)
+                    .build();
+            assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+            assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+            assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    autoOffConfig.createCapabilitiesAndConfig();
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            assertEquals(CAPABILITY_POSSESSED,
+                    capabilities.getConfigureAutoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_NOT_SUPPORTED,
+                    capabilities.getConfigureGeoDetectionEnabledCapability());
+            assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            assertFalse(configuration.isAutoDetectionEnabled());
+            assertTrue(configuration.isGeoDetectionEnabled());
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index cb27657..d2452ea 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -365,6 +365,7 @@
         final boolean geoDetectionEnabled = autoDetectionEnabled;
         return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
                 .setAutoDetectionSupported(true)
+                .setGeoDetectionSupported(true)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionEnabled(autoDetectionEnabled)
                 .setLocationEnabled(geoDetectionEnabled)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 296aa73..a6ffd20 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -91,6 +91,7 @@
             new ConfigurationInternal.Builder(USER_ID)
                     .setUserConfigAllowed(false)
                     .setAutoDetectionSupported(true)
+                    .setGeoDetectionSupported(true)
                     .setAutoDetectionEnabled(false)
                     .setLocationEnabled(true)
                     .setGeoDetectionEnabled(false)
@@ -100,6 +101,7 @@
             new ConfigurationInternal.Builder(USER_ID)
                     .setUserConfigAllowed(false)
                     .setAutoDetectionSupported(true)
+                    .setGeoDetectionSupported(true)
                     .setAutoDetectionEnabled(true)
                     .setLocationEnabled(true)
                     .setGeoDetectionEnabled(true)
@@ -109,32 +111,36 @@
             new ConfigurationInternal.Builder(USER_ID)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionSupported(false)
+                    .setGeoDetectionSupported(false)
                     .setAutoDetectionEnabled(false)
                     .setLocationEnabled(true)
                     .setGeoDetectionEnabled(false)
                     .build();
 
+    private static final ConfigurationInternal CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED =
+            new ConfigurationInternal.Builder(USER_ID)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionSupported(true)
+                    .setGeoDetectionSupported(false)
+                    .setAutoDetectionEnabled(true)
+                    .setLocationEnabled(true)
+                    .setGeoDetectionEnabled(true)
+                    .build();
+
     private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionSupported(true)
+                    .setGeoDetectionSupported(true)
                     .setAutoDetectionEnabled(false)
                     .setLocationEnabled(true)
                     .setGeoDetectionEnabled(false)
                     .build();
 
-    private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_ENABLED =
-            new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(true)
-                    .setAutoDetectionSupported(true)
-                    .setAutoDetectionEnabled(false)
-                    .setLocationEnabled(true)
-                    .setGeoDetectionEnabled(true)
-                    .build();
-
     private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
                     .setAutoDetectionSupported(true)
+                    .setGeoDetectionSupported(true)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabled(true)
                     .setLocationEnabled(true)
@@ -144,6 +150,7 @@
     private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED =
             new ConfigurationInternal.Builder(USER_ID)
                     .setAutoDetectionSupported(true)
+                    .setGeoDetectionSupported(true)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabled(true)
                     .setLocationEnabled(true)
@@ -223,14 +230,14 @@
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
-        // Update the configuration with auto detection enabled.
+        // Try to update the configuration with auto detection enabled.
         script.simulateUpdateConfiguration(
                 USER_ID, CONFIG_AUTO_ENABLED,  false /* expectedResult */);
 
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
-        // Try to  update the configuration to enable geolocation time zone detection.
+        // Try to update the configuration to enable geolocation time zone detection.
         script.simulateUpdateConfiguration(
                 USER_ID, CONFIG_GEO_DETECTION_ENABLED,  false /* expectedResult */);
 
@@ -249,7 +256,7 @@
         // The settings should not have been changed: user shouldn't have the capabilities.
         script.verifyConfigurationNotChanged();
 
-        // Update the configuration with auto detection enabled.
+        // Try to update the configuration with auto detection enabled.
         script.simulateUpdateConfiguration(
                 USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
 
@@ -258,6 +265,38 @@
     }
 
     @Test
+    public void testUpdateConfiguration_autoDetectSupportedGeoNotSupported() {
+        Script script = new Script().initializeConfig(CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED);
+
+        // Update the configuration with auto detection disabled.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
+
+        // The settings should have been changed and the StrategyListener onChange() called.
+        ConfigurationInternal expectedConfig =
+                new ConfigurationInternal.Builder(CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED)
+                        .setAutoDetectionEnabled(false)
+                        .build();
+        script.verifyConfigurationChangedAndReset(expectedConfig);
+
+        // Try to update the configuration with geo detection disabled.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_DISABLED, false /* expectedResult */);
+
+        // The settings should not have been changed: user shouldn't have the capability to modify
+        // the setting when the feature is disabled.
+        script.verifyConfigurationNotChanged();
+
+        // Try to update the configuration with geo detection enabled.
+        script.simulateUpdateConfiguration(
+                USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
+
+        // The settings should not have been changed: user shouldn't have the capability to modify
+        // the setting when the feature is disabled.
+        script.verifyConfigurationNotChanged();
+    }
+
+    @Test
     public void testEmptyTelephonySuggestions() {
         TelephonyTimeZoneSuggestion slotIndex1TimeZoneSuggestion =
                 createEmptySlotIndex1Suggestion();
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 7b07102..1055069 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -81,9 +81,7 @@
 
     // A correspondence to compare a FrontendResource and a TunerFrontendInfo.
     private static final Correspondence<FrontendResource, TunerFrontendInfo> FR_TFI_COMPARE =
-            new Correspondence<FrontendResource, TunerFrontendInfo>() {
-            @Override
-            public boolean compare(FrontendResource actual, TunerFrontendInfo expected) {
+            Correspondence.from((FrontendResource actual, TunerFrontendInfo expected) -> {
                 if (actual == null || expected == null) {
                     return (actual == null) && (expected == null);
                 }
@@ -91,13 +89,7 @@
                 return actual.getId() == expected.getId()
                         && actual.getType() == expected.getFrontendType()
                         && actual.getExclusiveGroupId() == expected.getExclusiveGroupId();
-            }
-
-            @Override
-            public String toString() {
-                return "is correctly configured from ";
-            }
-        };
+            },  "is correctly configured from ");
 
     @Before
     public void setUp() throws Exception {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index d7e431f..e304083 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -690,6 +690,39 @@
     }
 
     @Test
+    public void unbindOtherUserServices() throws PackageManager.NameNotFoundException {
+        Context context = mock(Context.class);
+        PackageManager pm = mock(PackageManager.class);
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        when(context.getPackageName()).thenReturn(mContext.getPackageName());
+        when(context.getUserId()).thenReturn(mContext.getUserId());
+        when(context.getPackageManager()).thenReturn(pm);
+        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_COMPONENT);
+        ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            ServiceConnection sc = (ServiceConnection) args[1];
+            sc.onServiceConnected(cn, mock(IBinder.class));
+            return true;
+        });
+
+        service.registerService(cn, 0);
+        service.registerService(cn, 10);
+        service.registerService(cn, 11);
+        service.unbindOtherUserServices(11);
+
+        assertFalse(service.isBound(cn, 0));
+        assertFalse(service.isBound(cn, 10));
+        assertTrue(service.isBound(cn, 11));
+    }
+
+    @Test
     public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ed6a20b..6ee5831 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -43,6 +43,9 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_MUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -110,6 +113,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IIntentSender;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -248,6 +252,11 @@
     Resources mResources;
     @Mock
     RankingHandler mRankingHandler;
+    @Mock
+    ActivityManagerInternal mAmi;
+
+    @Mock
+    IIntentSender pi1;
 
     private static final int MAX_POST_DELAY = 1000;
 
@@ -392,7 +401,6 @@
 
         DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class);
         when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L);
-        ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class);
 
         LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
         LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
@@ -403,7 +411,7 @@
         LocalServices.removeServiceForTest(DeviceIdleInternal.class);
         LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
-        LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal);
+        LocalServices.addService(ActivityManagerInternal.class, mAmi);
 
         doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
 
@@ -477,7 +485,7 @@
                 mGroupHelper, mAm, mAtm, mAppUsageStats,
                 mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                 mAppOpsManager, mUm, mHistoryManager, mStatsManager,
-                mock(TelephonyManager.class));
+                mock(TelephonyManager.class), mAmi);
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
 
         mService.setAudioManager(mAudioManager);
@@ -674,7 +682,8 @@
         }
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .addAction(new Notification.Action.Builder(null, "test", null).build());
         if (extender != null) {
             nb.extend(extender);
         }
@@ -810,6 +819,7 @@
         PendingIntent pendingIntent = mock(PendingIntent.class);
         Intent intent = mock(Intent.class);
         when(pendingIntent.getIntent()).thenReturn(intent);
+        when(pendingIntent.getTarget()).thenReturn(pi1);
 
         ActivityInfo info = new ActivityInfo();
         info.resizeMode = RESIZE_MODE_RESIZEABLE;
@@ -7134,4 +7144,159 @@
         inOrder.verify(parent).recordDismissalSentiment(anyInt());
         inOrder.verify(child).recordDismissalSentiment(anyInt());
     }
+
+    @Test
+    public void testImmutableBubbleIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(pi1))
+                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        NotificationRecord r = generateMessageBubbleNotifRecord(true,
+                mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false);
+        try {
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+                    r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+            waitForIdle();
+            fail("Allowed a bubble with an immutable intent to be posted");
+        } catch (IllegalArgumentException e) {
+            // good
+        }
+    }
+
+    @Test
+    public void testMutableBubbleIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(pi1))
+                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
+        NotificationRecord r = generateMessageBubbleNotifRecord(true,
+                mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+                r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(r.getSbn().getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testImmutableDirectReplyActionIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        NotificationRecord r = generateMessageBubbleNotifRecord(false,
+                mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false);
+        try {
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+                    r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+            waitForIdle();
+            fail("Allowed a direct reply with an immutable intent to be posted");
+        } catch (IllegalArgumentException e) {
+            // good
+        }
+    }
+
+    @Test
+    public void testMutableDirectReplyActionIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
+        NotificationRecord r = generateMessageBubbleNotifRecord(false,
+                mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+                r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(r.getSbn().getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testImmutableDirectReplyContextualActionIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        ArrayList<Notification.Action> extraAction = new ArrayList<>();
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        extraAction.add(replyAction);
+        Bundle signals = new Bundle();
+        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
+        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+                r.getUser());
+        r.addAdjustment(adjustment);
+        r.applyAdjustments();
+
+        try {
+            mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
+                    r.getSbn().getTag(), r,false);
+            fail("Allowed a contextual direct reply with an immutable intent to be posted");
+        } catch (IllegalArgumentException e) {
+            // good
+        }
+    }
+
+    @Test
+    public void testMutableDirectReplyContextualActionIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+                .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        ArrayList<Notification.Action> extraAction = new ArrayList<>();
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        extraAction.add(replyAction);
+        Bundle signals = new Bundle();
+        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
+        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+                r.getUser());
+        r.addAdjustment(adjustment);
+        r.applyAdjustments();
+
+        mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
+                r.getSbn().getTag(), r,false);
+    }
+
+    @Test
+    public void testImmutableActionIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+                r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+        waitForIdle();
+        StatusBarNotification[] notifs =
+                mBinderService.getActiveNotifications(r.getSbn().getPackageName());
+        assertEquals(1, notifs.length);
+    }
+
+    @Test
+    public void testImmutableContextualActionIntent() throws Exception {
+        when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+                .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        ArrayList<Notification.Action> extraAction = new ArrayList<>();
+        extraAction.add(new Notification.Action(0, "hello", null));
+        Bundle signals = new Bundle();
+        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
+        Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+                r.getUser());
+        r.addAdjustment(adjustment);
+        r.applyAdjustments();
+
+        mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
+                    r.getSbn().getTag(), r,false);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 3281c3f..a80f62a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IUriGrantsManager;
@@ -154,7 +155,8 @@
                     mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
                     mock(UriGrantsManagerInternal.class),
                     mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
-                    mock(StatsManager.class), mock(TelephonyManager.class));
+                    mock(StatsManager.class), mock(TelephonyManager.class),
+                    mock(ActivityManagerInternal.class));
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                 throw e;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index eca71b6..e5ae2d3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -305,6 +305,7 @@
         //when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
          //       anyString(), anyInt(), any())).thenReturn(true);
 
-        assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isSameAs(si);
+        assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM))
+                .isSameInstanceAs(si);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index e17601e..01c1f1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -23,6 +23,8 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
 import static com.android.server.wm.DisplayArea.Type.ANY;
@@ -37,14 +39,18 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
 
 import com.google.android.collect.Lists;
 
@@ -405,6 +411,55 @@
                 childBounds1, windowToken.getMaxBounds());
     }
 
+    @Test
+    public void testGetOrientation() {
+        final DisplayArea.Tokens area = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "test");
+        final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
+        spyOn(token);
+        doReturn(mock(DisplayContent.class)).when(token).getDisplayContent();
+        doNothing().when(token).setParent(any());
+        final WindowState win = createWindowState(token);
+        spyOn(win);
+        doNothing().when(win).setParent(any());
+        win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+        token.addChild(win, 0);
+        area.addChild(token);
+
+        doReturn(true).when(win).isVisible();
+
+        assertEquals("Visible window can request orientation",
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
+                area.getOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR));
+
+        doReturn(false).when(win).isVisible();
+
+        assertEquals("Invisible window cannot request orientation",
+                ActivityInfo.SCREEN_ORIENTATION_NOSENSOR,
+                area.getOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR));
+    }
+
+    @Test
+    public void testSetIgnoreOrientationRequest() {
+        final DisplayArea.Tokens area = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "test");
+        final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
+        spyOn(token);
+        doReturn(mock(DisplayContent.class)).when(token).getDisplayContent();
+        doNothing().when(token).setParent(any());
+        final WindowState win = createWindowState(token);
+        spyOn(win);
+        doNothing().when(win).setParent(any());
+        win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+        token.addChild(win, 0);
+        area.addChild(token);
+        doReturn(true).when(win).isVisible();
+
+        assertEquals(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, area.getOrientation());
+
+        area.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, area.getOrientation());
+    }
+
     private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
         private TestDisplayArea(WindowManagerService wms, Rect bounds) {
             super(wms, ANY, "half display area");
@@ -417,6 +472,13 @@
         }
     }
 
+    private WindowState createWindowState(WindowToken token) {
+        return new WindowState(mWms, mock(Session.class), new TestIWindow(), token,
+                null /* parentWindow */, 0 /* appOp */, new WindowManager.LayoutParams(),
+                View.VISIBLE, 0 /* ownerId */, 0 /* showUserId */,
+                false /* ownerCanAddInternalSystemWindow */);
+    }
+
     private WindowToken createWindowToken(int type) {
         return new WindowToken(mWmsRule.getWindowManagerService(), new Binder(),
                 type, false /* persist */, null /* displayContent */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index d0a5644..ecbfac8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -163,12 +163,6 @@
     }
 
     @Override
-    public SurfaceControl.Transaction setOverrideScalingMode(SurfaceControl sc,
-            int overrideScalingMode) {
-        return this;
-    }
-
-    @Override
     public SurfaceControl.Transaction setColor(SurfaceControl sc, float[] color) {
         return this;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index d2b7ac4..7975899 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -1002,14 +1002,18 @@
     public void testNotSpecifyOrientationByFloatingTask() {
         final Task task = getTestTask();
         final ActivityRecord activity = task.getTopMostActivity();
+        final WindowContainer<?> parentContainer = task.getParent();
         final TaskDisplayArea taskDisplayArea = task.getDisplayArea();
         activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parentContainer.getOrientation());
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
 
         task.setWindowingMode(WINDOWING_MODE_PINNED);
 
-        assertEquals(SCREEN_ORIENTATION_UNSET, taskDisplayArea.getOrientation());
+        // TDA returns the last orientation when child returns UNSET
+        assertEquals(SCREEN_ORIENTATION_UNSET, parentContainer.getOrientation());
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index dc85904..db5c796 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -197,19 +197,19 @@
     }
 
     @Override
-    public void screenTurningOn(ScreenOnListener screenOnListener) {
+    public void screenTurningOn(int displayId, ScreenOnListener screenOnListener) {
     }
 
     @Override
-    public void screenTurnedOn() {
+    public void screenTurnedOn(int displayId) {
     }
 
     @Override
-    public void screenTurningOff(ScreenOffListener screenOffListener) {
+    public void screenTurningOff(int displayId, ScreenOffListener screenOffListener) {
     }
 
     @Override
-    public void screenTurnedOff() {
+    public void screenTurnedOff(int displayId) {
     }
 
     @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 0152fc6..aac8397 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -54,12 +54,14 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.PictureInPictureParams;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
@@ -72,6 +74,7 @@
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainerTransactionCallback;
+import android.window.TaskAppearedInfo;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.filters.SmallTest;
@@ -79,8 +82,10 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -93,14 +98,27 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class WindowOrganizerTests extends WindowTestsBase {
-    private ITaskOrganizer registerMockOrganizer() {
+
+    private ITaskOrganizer createMockOrganizer() {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         when(organizer.asBinder()).thenReturn(new Binder());
-
-        mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer);
         return organizer;
     }
 
+    private ITaskOrganizer registerMockOrganizer(ArrayList<TaskAppearedInfo> existingTasks) {
+        final ITaskOrganizer organizer = createMockOrganizer();
+        ParceledListSlice<TaskAppearedInfo> tasks =
+                mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer);
+        if (existingTasks != null) {
+            existingTasks.addAll(tasks.getList());
+        }
+        return organizer;
+    }
+
+    private ITaskOrganizer registerMockOrganizer() {
+        return registerMockOrganizer(null);
+    }
+
     Task createTask(Task stack, boolean fakeDraw) {
         final Task task = createTaskInStack(stack, 0);
 
@@ -128,27 +146,21 @@
 
     @Test
     public void testAppearVanish() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack);
-        final ITaskOrganizer organizer = registerMockOrganizer();
 
-        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        stack.setTaskOrganizer(organizer);
         verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
 
-
         stack.removeImmediately();
         verify(organizer).onTaskVanished(any());
     }
 
     @Test
     public void testAppearWaitsForVisibility() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack, false);
-        final ITaskOrganizer organizer = registerMockOrganizer();
-
-        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        stack.setTaskOrganizer(organizer);
 
         verify(organizer, never())
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
@@ -163,9 +175,9 @@
 
     @Test
     public void testNoVanishedIfNoAppear() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack, false /* hasBeenVisible */);
-        final ITaskOrganizer organizer = registerMockOrganizer();
 
         // In this test we skip making the Task visible, and verify
         // that even though a TaskOrganizer is set remove doesn't emit
@@ -179,28 +191,25 @@
 
     @Test
     public void testTaskNoDraw() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack, false /* fakeDraw */);
-        final ITaskOrganizer organizer = registerMockOrganizer();
 
-        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         verify(organizer, never())
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
         assertTrue(stack.isOrganized());
 
         mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
-        verify(organizer, never()).onTaskVanished(any());
+        assertTaskVanished(organizer, false /* expectVanished */, stack);
         assertFalse(stack.isOrganized());
     }
 
     @Test
     public void testClearOrganizer() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack);
-        final ITaskOrganizer organizer = registerMockOrganizer();
 
-        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        stack.setTaskOrganizer(organizer);
         verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
         assertTrue(stack.isOrganized());
 
@@ -211,16 +220,15 @@
 
     @Test
     public void testUnregisterOrganizer() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack);
-        final ITaskOrganizer organizer = registerMockOrganizer();
 
-        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
         assertTrue(stack.isOrganized());
 
         mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
-        verify(organizer).onTaskVanished(any());
+        assertTaskVanished(organizer, true /* expectVanished */, stack);
         assertFalse(stack.isOrganized());
     }
 
@@ -232,37 +240,47 @@
         final Task task2 = createTask(stack2);
         final Task stack3 = createStack();
         final Task task3 = createTask(stack3);
-        final ITaskOrganizer organizer = registerMockOrganizer();
+        final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
+        final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
 
-        // verify that tasks are appeared on registration
-        verify(organizer, times(3))
-                .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
+        // verify that tasks are returned and taskAppeared is not called
+        assertContainsTasks(existingTasks, stack, stack2, stack3);
+        verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
+                any(SurfaceControl.class));
+        verify(organizer, times(0)).onTaskVanished(any());
         assertTrue(stack.isOrganized());
 
-        // Now we replace the registration and1 verify the new organizer receives tasks
-        final ITaskOrganizer organizer2 = registerMockOrganizer();
-        verify(organizer2, times(3))
-                .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
+        // Now we replace the registration and verify the new organizer receives existing tasks
+        final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>();
+        final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2);
+        assertContainsTasks(existingTasks2, stack, stack2, stack3);
+        verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
+                any(SurfaceControl.class));
         verify(organizer2, times(0)).onTaskVanished(any());
-        // One for task
-        verify(organizer, times(3)).onTaskVanished(any());
+        // Removed tasks from the original organizer
+        assertTaskVanished(organizer, true /* expectVanished */, stack, stack2, stack3);
         assertTrue(stack2.isOrganized());
 
         // Now we unregister the second one, the first one should automatically be reregistered
         // so we verify that it's now seeing changes.
         mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2);
-        verify(organizer, times(6))
+        verify(organizer, times(3))
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-        verify(organizer2, times(3)).onTaskVanished(any());
+        assertTaskVanished(organizer2, true /* expectVanished */, stack, stack2, stack3);
     }
 
     @Test
     public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException {
         final Task stack = createStack();
         final Task task = createTask(stack);
+        final Task stack2 = createStack();
+        final Task task2 = createTask(stack2);
+        ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
+        final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
+        assertContainsTasks(existingTasks, stack, stack2);
 
-        final ITaskOrganizer organizer = registerMockOrganizer();
-        verify(organizer, times(1))
+        // Verify we don't get onTaskAppeared if we are returned the tasks
+        verify(organizer, never())
                 .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
     }
 
@@ -366,7 +384,7 @@
     }
 
     @Test
-    public void testSetIgnoreOrientationRequest() {
+    public void testSetIgnoreOrientationRequest_taskDisplayArea() {
         removeGlobalMinSizeRestriction();
         final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
         final Task stack = taskDisplayArea.createStack(
@@ -378,7 +396,7 @@
         activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
         // TDA returns UNSET when ignoreOrientationRequest == true
-        // DC is UNSPECIFIED because it is using the previous (default) when TDA returns UNSET.
+        // DC is UNSPECIFIED when child returns UNSET
         assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET);
         assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
 
@@ -399,8 +417,40 @@
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
 
         // TDA returns UNSET when ignoreOrientationRequest == true
-        // DC is LANDSCAPE because it is using the previous when TDA returns UNSET.
+        // DC is UNSPECIFIED when child returns UNSET
         assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET);
+        assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    @Test
+    public void testSetIgnoreOrientationRequest_displayContent() {
+        removeGlobalMinSizeRestriction();
+        final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+        final Task stack = taskDisplayArea.createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setStack(stack).build();
+        mDisplayContent.setFocusedApp(activity);
+        activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+        // DC uses the orientation request from app
+        assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
+
+        WindowContainerTransaction t = new WindowContainerTransaction();
+        t.setIgnoreOrientationRequest(
+                mDisplayContent.mRemoteToken.toWindowContainerToken(),
+                true /* ignoreOrientationRequest */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+
+        // DC returns UNSPECIFIED when ignoreOrientationRequest == true
+        assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
+
+        t.setIgnoreOrientationRequest(
+                mDisplayContent.mRemoteToken.toWindowContainerToken(),
+                false /* ignoreOrientationRequest */);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+
+        // DC uses the orientation request from app after mIgnoreOrientationRequest is set to false
         assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
     }
 
@@ -922,9 +972,9 @@
 
     @Test
     public void testPreventDuplicateAppear() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack, false /* fakeDraw */);
-        final ITaskOrganizer organizer = registerMockOrganizer();
 
         stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         stack.setTaskOrganizer(organizer);
@@ -945,17 +995,14 @@
 
     @Test
     public void testInterceptBackPressedOnTaskRoot() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stack = createStack();
         final Task task = createTask(stack);
         final ActivityRecord activity = createActivityRecordInTask(stack.mDisplayContent, task);
         final Task stack2 = createStack();
         final Task task2 = createTask(stack2);
         final ActivityRecord activity2 = createActivityRecordInTask(stack.mDisplayContent, task2);
-        final ITaskOrganizer organizer = registerMockOrganizer();
 
-        // Setup the task to be controlled by the MW mode organizer
-        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         assertTrue(stack.isOrganized());
         assertTrue(stack2.isOrganized());
 
@@ -982,9 +1029,9 @@
 
     @Test
     public void testBLASTCallbackWithMultipleWindows() throws Exception {
+        final ITaskOrganizer organizer = registerMockOrganizer();
         final Task stackController = createStack();
         final Task task = createTask(stackController);
-        final ITaskOrganizer organizer = registerMockOrganizer();
         final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1");
         final WindowState w2 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 2");
         makeWindowVisible(w1);
@@ -1035,4 +1082,37 @@
             assertFalse(daTask.isForceHidden());
         });
     }
+
+    /**
+     * Verifies that task vanished is called for a specific task.
+     */
+    private void assertTaskVanished(ITaskOrganizer organizer, boolean expectVanished, Task... tasks)
+            throws RemoteException {
+        ArgumentCaptor<RunningTaskInfo> arg = ArgumentCaptor.forClass(RunningTaskInfo.class);
+        verify(organizer, atLeastOnce()).onTaskVanished(arg.capture());
+        List<RunningTaskInfo> taskInfos = arg.getAllValues();
+
+        HashSet<Integer> vanishedTaskIds = new HashSet<>();
+        for (int i = 0; i < taskInfos.size(); i++) {
+            vanishedTaskIds.add(taskInfos.get(i).taskId);
+        }
+        HashSet<Integer> taskIds = new HashSet<>();
+        for (int i = 0; i < tasks.length; i++) {
+            taskIds.add(tasks[i].mTaskId);
+        }
+
+        assertTrue(expectVanished
+                ? vanishedTaskIds.containsAll(taskIds)
+                : !vanishedTaskIds.removeAll(taskIds));
+    }
+
+    private void assertContainsTasks(List<TaskAppearedInfo> taskInfos, Task... expectedTasks) {
+        HashSet<Integer> taskIds = new HashSet<>();
+        for (int i = 0; i < taskInfos.size(); i++) {
+            taskIds.add(taskInfos.get(i).getTaskInfo().taskId);
+        }
+        for (int i = 0; i < expectedTasks.length; i++) {
+            assertTrue(taskIds.contains(expectedTasks[i].mTaskId));
+        }
+    }
 }
diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
index 8fa0cde..150577a 100644
--- a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
+++ b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
@@ -124,7 +124,7 @@
     data class InputData<T : Parcelable>(val valid: T, val validCopy: T, val validOther: T) {
         val kls = valid.javaClass
         init {
-            assertThat(valid).isNotSameAs(validCopy)
+            assertThat(valid).isNotSameInstanceAs(validCopy)
             // Don't use isInstanceOf because of phantom warnings in intellij about Class!
             assertThat(validCopy.javaClass).isEqualTo(valid.javaClass)
             assertThat(validOther.javaClass).isEqualTo(valid.javaClass)
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index fb6f994..aff2f01 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -405,7 +405,8 @@
         // Change the callerInfo number ONLY if it is an emergency number
         // or if it is the voicemail number.  If it is either, take a
         // shortcut and skip the query.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        if (tm.isEmergencyNumber(number)) {
             return new CallerInfo().markAsEmergency(context);
         } else if (PhoneNumberUtils.isVoiceMailNumber(null, subId, number)) {
             return new CallerInfo().markAsVoiceMail(context, subId);
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index 4a81a8e..a9e1a8f 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -34,6 +34,7 @@
 import android.provider.ContactsContract.PhoneLookup;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
 import java.util.ArrayList;
@@ -481,7 +482,8 @@
         cw.subId = subId;
 
         // check to see if these are recognized numbers, and use shortcuts if we can.
-        if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        if (tm.isEmergencyNumber(number)) {
             cw.event = EVENT_EMERGENCY_NUMBER;
         } else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
             cw.event = EVENT_VOICEMAIL_NUMBER;
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 11fae0c..a67273c 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -227,6 +227,25 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+    method public long getIdleTimeMillis();
+    method public static int getNumTxPowerLevels();
+    method public long getReceiveTimeMillis();
+    method public long getSleepTimeMillis();
+    method public long getTimestampMillis();
+    method public long getTransmitDurationMillisAtPowerLevel(int);
+    method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+    field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+    field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+    field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+    field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
     method public int getRegistrationState();
@@ -693,7 +712,6 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDeviceSoftwareVersion(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index debb119..881d85c 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -16,8 +16,12 @@
 
 package android.telephony;
 
+import android.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -25,46 +29,50 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
- * Reports modem activity information.
+ * Contains information about the modem's activity. May be useful for power stats reporting.
  * @hide
  */
+@SystemApi
+@TestApi
 public final class ModemActivityInfo implements Parcelable {
+    private static final int TX_POWER_LEVELS = 5;
+
     /**
-     * Tx(transmit) power level. see power index below
-     * <ul>
-     *   <li> index 0 = tx_power < 0dBm. </li>
-     *   <li> index 1 = 0dBm < tx_power < 5dBm. </li>
-     *   <li> index 2 = 5dBm < tx_power < 15dBm. </li>
-     *   <li> index 3 = 15dBm < tx_power < 20dBm. </li>
-     *   <li> index 4 = tx_power > 20dBm. </li>
-     * </ul>
-     */
-    public static final int TX_POWER_LEVELS = 5;
-    /**
-     * Tx(transmit) power level 0: tx_power < 0dBm
+     * Corresponds to transmit power of less than 0dBm.
      */
     public static final int TX_POWER_LEVEL_0 = 0;
+
     /**
-     * Tx(transmit) power level 1: 0dBm < tx_power < 5dBm
+     * Corresponds to transmit power between 0dBm and 5dBm.
      */
     public static final int TX_POWER_LEVEL_1 = 1;
+
     /**
-     * Tx(transmit) power level 2: 5dBm < tx_power < 15dBm
+     * Corresponds to transmit power between 5dBm and 15dBm.
      */
     public static final int TX_POWER_LEVEL_2 = 2;
+
     /**
-     * Tx(transmit) power level 3: 15dBm < tx_power < 20dBm.
+     * Corresponds to transmit power between 15dBm and 20dBm.
      */
     public static final int TX_POWER_LEVEL_3 = 3;
+
     /**
-     * Tx(transmit) power level 4: tx_power > 20dBm
+     * Corresponds to transmit power above 20dBm.
      */
     public static final int TX_POWER_LEVEL_4 = 4;
 
+    /**
+     * The number of transmit power levels. Fixed by HAL definition.
+     */
+    public static int getNumTxPowerLevels() {
+        return TX_POWER_LEVELS;
+    }
+
     /** @hide */
     @IntDef(prefix = {"TX_POWER_LEVEL_"}, value = {
             TX_POWER_LEVEL_0,
@@ -82,34 +90,39 @@
         new Range<>(5, 15),
         new Range<>(15, 20),
         new Range<>(20, Integer.MAX_VALUE)
-
     };
 
     private long mTimestamp;
     private int mSleepTimeMs;
     private int mIdleTimeMs;
-    private List<TransmitPower> mTransmitPowerInfo = new ArrayList<>(TX_POWER_LEVELS);
+    private int[] mTxTimeMs;
     private int mRxTimeMs;
 
+    /**
+     * @hide
+     */
+    @TestApi
     public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs,
                         @NonNull int[] txTimeMs, int rxTimeMs) {
+        Objects.requireNonNull(txTimeMs);
+        if (txTimeMs.length != TX_POWER_LEVELS) {
+            throw new IllegalArgumentException("txTimeMs must have length == TX_POWER_LEVELS");
+        }
         mTimestamp = timestamp;
         mSleepTimeMs = sleepTimeMs;
         mIdleTimeMs = idleTimeMs;
-        populateTransmitPowerRange(txTimeMs);
+        mTxTimeMs = txTimeMs;
         mRxTimeMs = rxTimeMs;
     }
 
-    /** helper API to populate tx power range for each bucket **/
-    private void populateTransmitPowerRange(@NonNull int[] transmitPowerMs) {
-        int i = 0;
-        for ( ; i < Math.min(transmitPowerMs.length, TX_POWER_LEVELS); i++) {
-            mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], transmitPowerMs[i]));
-        }
-        // Make sure that mTransmitPowerInfo is fully initialized.
-        for ( ; i < TX_POWER_LEVELS; i++) {
-            mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], 0));
-        }
+    /**
+     * Provided for convenience in manipulation since the API exposes long values but internal
+     * representations are ints.
+     * @hide
+     */
+    public ModemActivityInfo(long timestamp, long sleepTimeMs, long idleTimeMs,
+            @NonNull int[] txTimeMs, long rxTimeMs) {
+        this(timestamp, (int) sleepTimeMs, (int) idleTimeMs, txTimeMs, (int) rxTimeMs);
     }
 
     @Override
@@ -118,7 +131,7 @@
             + " mTimestamp=" + mTimestamp
             + " mSleepTimeMs=" + mSleepTimeMs
             + " mIdleTimeMs=" + mIdleTimeMs
-            + " mTransmitPowerInfo[]=" + mTransmitPowerInfo.toString()
+            + " mTxTimeMs[]=" + mTxTimeMs
             + " mRxTimeMs=" + mRxTimeMs
             + "}";
     }
@@ -129,14 +142,12 @@
 
     public static final @android.annotation.NonNull Parcelable.Creator<ModemActivityInfo> CREATOR =
             new Parcelable.Creator<ModemActivityInfo>() {
-        public ModemActivityInfo createFromParcel(Parcel in) {
+        public ModemActivityInfo createFromParcel(@NonNull Parcel in) {
             long timestamp = in.readLong();
             int sleepTimeMs = in.readInt();
             int idleTimeMs = in.readInt();
             int[] txTimeMs = new int[TX_POWER_LEVELS];
-            for (int i = 0; i < TX_POWER_LEVELS; i++) {
-                txTimeMs[i] = in.readInt();
-            }
+            in.readIntArray(txTimeMs);
             int rxTimeMs = in.readInt();
             return new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs,
                                 txTimeMs, rxTimeMs);
@@ -147,21 +158,25 @@
         }
     };
 
-    public void writeToParcel(Parcel dest, int flags) {
+    /**
+     * @param dest The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeLong(mTimestamp);
         dest.writeInt(mSleepTimeMs);
         dest.writeInt(mIdleTimeMs);
-        for (int i = 0; i < TX_POWER_LEVELS; i++) {
-            dest.writeInt(mTransmitPowerInfo.get(i).getTimeInMillis());
-        }
+        dest.writeIntArray(mTxTimeMs);
         dest.writeInt(mRxTimeMs);
     }
 
     /**
-     * @return milliseconds since boot, including mTimeInMillis spent in sleep.
-     * @see SystemClock#elapsedRealtime()
+     * Gets the timestamp at which this modem activity info was recorded.
+     *
+     * @return The timestamp, as returned by {@link SystemClock#elapsedRealtime()}, when this
+     * {@link ModemActivityInfo} was recorded.
      */
-    public long getTimestamp() {
+    public @ElapsedRealtimeLong long getTimestampMillis() {
         return mTimestamp;
     }
 
@@ -171,35 +186,48 @@
     }
 
     /**
-     * @return an arrayList of {@link TransmitPower} with each element representing the total time where
-     * transmitter is awake time (in ms) for a given power range (in dbm).
+     * Gets the amount of time the modem spent transmitting at a certain power level.
      *
-     * @see #TX_POWER_LEVELS
+     * @param powerLevel The power level to query.
+     * @return The amount of time, in milliseconds, that the modem spent transmitting at the
+     * given power level.
      */
-    @NonNull
-    public List<TransmitPower> getTransmitPowerInfo() {
-        return mTransmitPowerInfo;
+    public @DurationMillisLong long getTransmitDurationMillisAtPowerLevel(
+            @TxPowerLevel int powerLevel) {
+        return mTxTimeMs[powerLevel];
+    }
+
+    /**
+     * Gets the range of transmit powers corresponding to a certain power level.
+     *
+     * @param powerLevel The power level to query
+     * @return A {@link Range} object representing the range of intensities (in dBm) to which this
+     * power level corresponds.
+     */
+    public @NonNull Range<Integer> getTransmitPowerRange(@TxPowerLevel int powerLevel) {
+        return TX_POWER_RANGES[powerLevel];
     }
 
     /** @hide */
     public void setTransmitTimeMillis(int[] txTimeMs) {
-        populateTransmitPowerRange(txTimeMs);
-    }
-
-    /** @hide */
-    @NonNull
-    public int[] getTransmitTimeMillis() {
-        int[] transmitTimeMillis = new int[TX_POWER_LEVELS];
-        for (int i = 0; i < transmitTimeMillis.length; i++) {
-            transmitTimeMillis[i] = mTransmitPowerInfo.get(i).getTimeInMillis();
-        }
-        return transmitTimeMillis;
+        mTxTimeMs = Arrays.copyOf(txTimeMs, TX_POWER_LEVELS);
     }
 
     /**
-     * @return total mTimeInMillis (in ms) when modem is in a low power or sleep state.
+     * @return The raw array of transmit power durations
+     * @hide
      */
-    public int getSleepTimeMillis() {
+    @NonNull
+    public int[] getTransmitTimeMillis() {
+        return mTxTimeMs;
+    }
+
+    /**
+     * Gets the amount of time (in milliseconds) when the modem is in a low power or sleep state.
+     *
+     * @return Time in milliseconds.
+     */
+    public @DurationMillisLong long getSleepTimeMillis() {
         return mSleepTimeMs;
     }
 
@@ -209,10 +237,44 @@
     }
 
     /**
-     * @return total mTimeInMillis (in ms) when modem is awake but neither the transmitter nor receiver are
-     * active.
+     * Provided for convenience, since the API surface needs to return longs but internal
+     * representations are ints.
+     * @hide
      */
-    public int getIdleTimeMillis() {
+    public void setSleepTimeMillis(long sleepTimeMillis) {
+        mSleepTimeMs = (int) sleepTimeMillis;
+    }
+
+    /**
+     * Computes the difference between this instance of {@link ModemActivityInfo} and another
+     * instance.
+     *
+     * This method should be used to compute the amount of activity that has happened between two
+     * samples of modem activity taken at separate times. The sample passed in as an argument to
+     * this method should be the one that's taken later in time (and therefore has more activity).
+     * @param other The other instance of {@link ModemActivityInfo} to diff against.
+     * @return An instance of {@link ModemActivityInfo} representing the difference in modem
+     * activity.
+     */
+    public @NonNull ModemActivityInfo getDelta(@NonNull ModemActivityInfo other) {
+        int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
+        for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
+            txTimeMs[i] = other.mTxTimeMs[i] - mTxTimeMs[i];
+        }
+        return new ModemActivityInfo(other.getTimestampMillis(),
+                other.getSleepTimeMillis() - getSleepTimeMillis(),
+                other.getIdleTimeMillis() - getIdleTimeMillis(),
+                txTimeMs,
+                other.getReceiveTimeMillis() - getReceiveTimeMillis());
+    }
+
+    /**
+     * Gets the amount of time (in milliseconds) when the modem is awake but neither transmitting
+     * nor receiving.
+     *
+     * @return Time in milliseconds.
+     */
+    public @DurationMillisLong long getIdleTimeMillis() {
         return mIdleTimeMs;
     }
 
@@ -222,9 +284,20 @@
     }
 
     /**
-     * @return rx(receive) mTimeInMillis in ms.
+     * Provided for convenience, since the API surface needs to return longs but internal
+     * representations are ints.
+     * @hide
      */
-    public int getReceiveTimeMillis() {
+    public void setIdleTimeMillis(long idleTimeMillis) {
+        mIdleTimeMs = (int) idleTimeMillis;
+    }
+
+    /**
+     * Gets the amount of time (in milliseconds) when the modem is awake and receiving data.
+     *
+     * @return Time in milliseconds.
+     */
+    public @DurationMillisLong long getReceiveTimeMillis() {
         return mRxTimeMs;
     }
 
@@ -234,71 +307,56 @@
     }
 
     /**
+     * Provided for convenience, since the API surface needs to return longs but internal
+     * representations are ints.
+     * @hide
+     */
+    public void setReceiveTimeMillis(long receiveTimeMillis) {
+        mRxTimeMs = (int) receiveTimeMillis;
+    }
+
+    /**
      * Indicates if the modem has reported valid {@link ModemActivityInfo}.
      *
      * @return {@code true} if this {@link ModemActivityInfo} record is valid,
      * {@code false} otherwise.
+     *  TODO: remove usages of this outside Telephony by always returning a valid (or null) result
+     *  from telephony.
+     * @hide
      */
+    @TestApi
     public boolean isValid() {
-        for (TransmitPower powerInfo : getTransmitPowerInfo()) {
-            if(powerInfo.getTimeInMillis() < 0) {
-                return false;
-            }
-        }
+        boolean isTxPowerValid = Arrays.stream(mTxTimeMs).allMatch((i) -> i >= 0);
 
-        return ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0)
+        return isTxPowerValid && ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0)
                 && (getReceiveTimeMillis() >= 0) && !isEmpty());
     }
 
     private boolean isEmpty() {
-        for (TransmitPower txVal : getTransmitPowerInfo()) {
-            if(txVal.getTimeInMillis() != 0) {
-                return false;
-            }
-        }
+        boolean isTxPowerEmpty = mTxTimeMs == null || mTxTimeMs.length == 0
+                || Arrays.stream(mTxTimeMs).allMatch((i) -> i == 0);
 
-        return ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0)
+        return isTxPowerEmpty && ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0)
                 && (getReceiveTimeMillis() == 0));
     }
 
-    /**
-     * Transmit power Information, including the power range in dbm and the total time (in ms) where
-     * the transmitter is active/awake for this power range.
-     * e.g, range: 0dbm(lower) ~ 5dbm(upper)
-     *      time: 5ms
-     */
-    public class TransmitPower {
-        private int mTimeInMillis;
-        private Range<Integer> mPowerRangeInDbm;
-        /** @hide */
-        public TransmitPower(@NonNull Range<Integer> range, int time) {
-            this.mTimeInMillis = time;
-            this.mPowerRangeInDbm = range;
-        }
 
-        /**
-         * @return the total time in ms where the transmitter is active/wake for this power range
-         * {@link #getPowerRangeInDbm()}.
-         */
-        public int getTimeInMillis() {
-            return mTimeInMillis;
-        }
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ModemActivityInfo that = (ModemActivityInfo) o;
+        return mTimestamp == that.mTimestamp
+                && mSleepTimeMs == that.mSleepTimeMs
+                && mIdleTimeMs == that.mIdleTimeMs
+                && mRxTimeMs == that.mRxTimeMs
+                && Arrays.equals(mTxTimeMs, that.mTxTimeMs);
+    }
 
-        /**
-         * @return the power range in dbm. e.g, range: 0dbm(lower) ~ 5dbm(upper)
-         */
-        @NonNull
-        public Range<Integer> getPowerRangeInDbm() {
-            return mPowerRangeInDbm;
-        }
-
-        @Override
-        public String toString() {
-            return "TransmitPower{"
-                + " mTimeInMillis=" + mTimeInMillis
-                + " mPowerRangeInDbm={" + mPowerRangeInDbm.getLower()
-                + "," + mPowerRangeInDbm.getUpper()
-                + "}}";
-        }
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mTimestamp, mSleepTimeMs, mIdleTimeMs, mRxTimeMs);
+        result = 31 * result + Arrays.hashCode(mTxTimeMs);
+        return result;
     }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index cab6209..d3fca3e 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2604,13 +2604,12 @@
     /**
      * Send an MMS message
      *
-     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
-     * dialog. If this method is called on a device that has multiple active subscriptions, this
-     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
-     * default subscription is defined, the subscription ID associated with this message will be
-     * INVALID, which will result in the operation being completed on the subscription associated
-     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
-     * operation is performed on the correct subscription.
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail sending the MMS message because no
+     * suitable default subscription could be found. In this case, if {@code sentIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+     * conditions where this operation may fail.
      * </p>
      *
      * @param context application context
@@ -2629,21 +2628,30 @@
         }
         MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
         if (m != null) {
-            m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
-                    sentIntent, 0L /* messageId */);
+            resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                @Override
+                public void onSuccess(int subId) {
+                    m.sendMultimediaMessage(subId, contentUri, locationUrl, configOverrides,
+                            sentIntent, 0L /* messageId */);
+                }
+
+                @Override
+                public void onFailure() {
+                    notifySmsError(sentIntent, RESULT_NO_DEFAULT_SMS_APP);
+                }
+            });
         }
     }
 
     /**
      * Download an MMS message from carrier by a given location URL
      *
-     * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
-     * dialog. If this method is called on a device that has multiple active subscriptions, this
-     * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
-     * default subscription is defined, the subscription ID associated with this message will be
-     * INVALID, which will result in the operation being completed on the subscription associated
-     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
-     * operation is performed on the correct subscription.
+     * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+     * manager on a multi-SIM device, this operation may fail downloading the MMS message because no
+     * suitable default subscription could be found. In this case, if {@code downloadedIntent} is
+     * non-null, then the {@link PendingIntent} will be sent with an error code
+     * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+     * conditions where this operation may fail.
      * </p>
      *
      * @param context application context
@@ -2666,8 +2674,18 @@
         }
         MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
         if (m != null) {
-            m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
-                    configOverrides, downloadedIntent, 0L /* messageId */);
+            resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+                @Override
+                public void onSuccess(int subId) {
+                    m.downloadMultimediaMessage(subId, locationUrl, contentUri, configOverrides,
+                            downloadedIntent, 0L /* messageId */);
+                }
+
+                @Override
+                public void onFailure() {
+                    notifySmsError(downloadedIntent, RESULT_NO_DEFAULT_SMS_APP);
+                }
+            });
         }
     }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ceaa425..4f4a133 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10397,19 +10397,25 @@
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
      * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+     *
+     * May return {@code null} when the subscription is inactive or when there was an error
+     * communicating with the phone process.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(allOf = {
             Manifest.permission.READ_PHONE_STATE,
             Manifest.permission.ACCESS_COARSE_LOCATION
     })
-    public ServiceState getServiceState() {
+    public @Nullable ServiceState getServiceState() {
         return getServiceStateForSubscriber(getSubId());
     }
 
     /**
      * Returns the service state information on specified subscription. Callers require
      * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+     *
+     * May return {@code null} when the subscription is inactive or when there was an error
+     * communicating with the phone process.
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -10436,9 +10442,9 @@
      * @param accountHandle The handle for the {@link PhoneAccount} for which to retrieve the
      * voicemail ringtone.
      * @return The URI for the ringtone to play when receiving a voicemail from a specific
-     * PhoneAccount.
+     * PhoneAccount. May be {@code null} if no ringtone is set.
      */
-    public Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) {
+    public @Nullable Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
@@ -13682,9 +13688,7 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws SecurityException if the caller doesn't have the permission.
      *
-     * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public @NonNull List<String> getEquivalentHomePlmns() {
         try {
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index d430db5..3b9bec9 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -54,4 +54,18 @@
         "launcher-aosp-tapl",
         "platform-test-annotations",
     ],
+}
+
+java_library {
+    name: "wm-flicker-common-assertions",
+    platform_apis: true,
+    srcs: ["src/**/*Assertions.java", "src/**/*Assertions.kt"],
+    exclude_srcs: [
+        "**/helpers/*",
+    ],
+    static_libs: [
+        "flickerlib",
+        "truth-prebuilt",
+        "app-helpers-core"
+    ],
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 69b1187..8457039 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -21,13 +21,17 @@
 import com.android.server.wm.flicker.dsl.WmAssertion
 import com.android.server.wm.flicker.helpers.WindowUtils
 
+const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
+const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
+
 @JvmOverloads
 fun WmAssertion.statusBarWindowIsAlwaysVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     all("statusBarWindowIsAlwaysVisible", enabled, bugId) {
-        this.showsAboveAppWindow(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
+        this.showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
     }
 }
 
@@ -37,7 +41,7 @@
     enabled: Boolean = bugId == 0
 ) {
     all("navBarWindowIsAlwaysVisible", enabled, bugId) {
-        this.showsAboveAppWindow(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
+        this.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
     }
 }
 
@@ -77,7 +81,7 @@
     enabled: Boolean = bugId == 0
 ) {
     all("navBarLayerIsAlwaysVisible", enabled, bugId) {
-        this.showsLayer(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
+        this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
     }
 }
 
@@ -87,7 +91,7 @@
     enabled: Boolean = bugId == 0
 ) {
     all("statusBarLayerIsAlwaysVisible", enabled, bugId) {
-        this.showsLayer(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
+        this.showsLayer(STATUS_BAR_WINDOW_TITLE)
     }
 }
 
@@ -102,15 +106,15 @@
     val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
 
     start("navBarLayerRotatesAndScales_StartingPos", enabled, bugId) {
-        this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+        this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
     }
     end("navBarLayerRotatesAndScales_EndingPost", enabled, bugId) {
-        this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+        this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
     }
 
     if (startingPos == endingPos) {
         all("navBarLayerRotatesAndScales", enabled = false, bugId = 167747321) {
-            this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+            this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
         }
     }
 }
@@ -126,10 +130,10 @@
     val endingPos = WindowUtils.getStatusBarPosition(endRotation)
 
     start("statusBarLayerRotatesScales_StartingPos", enabled, bugId) {
-        this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, startingPos)
+        this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
     }
     end("statusBarLayerRotatesScales_EndingPos", enabled, bugId) {
-        this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, endingPos)
+        this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos)
     }
 }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt
deleted file mode 100644
index abe7dbc..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt
+++ /dev/null
@@ -1,129 +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.server.wm.flicker
-
-import android.os.RemoteException
-import android.os.SystemClock
-import android.platform.helpers.IAppHelper
-import android.view.Surface
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-
-/**
- * Base class of all Flicker test that performs common functions for all flicker tests:
- *
- *
- * - Caches transitions so that a transition is run once and the transition results are used by
- * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods
- * multiple times.
- * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed.
- * - Fails tests if results are not available for any test due to jank.
- */
-abstract class FlickerTestBase {
-    val instrumentation by lazy {
-        InstrumentationRegistry.getInstrumentation()
-    }
-    val uiDevice by lazy {
-        UiDevice.getInstance(instrumentation)
-    }
-
-    /**
-     * Build a test tag for the test
-     * @param testName Name of the transition(s) being tested
-     * @param app App being launcher
-     * @param rotation Initial screen rotation
-     *
-     * @return test tag with pattern <NAME>__<APP>__<ROTATION>
-    </ROTATION></APP></NAME> */
-    protected fun buildTestTag(testName: String, app: IAppHelper, rotation: Int): String {
-        return buildTestTag(
-                testName, app, rotation, rotation, app2 = null, extraInfo = "")
-    }
-
-    /**
-     * Build a test tag for the test
-     * @param testName Name of the transition(s) being tested
-     * @param app App being launcher
-     * @param beginRotation Initial screen rotation
-     * @param endRotation End screen rotation (if any, otherwise use same as initial)
-     *
-     * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
-    </END_ROTATION></BEGIN_ROTATION></APP></NAME> */
-    protected fun buildTestTag(
-        testName: String,
-        app: IAppHelper,
-        beginRotation: Int,
-        endRotation: Int
-    ): String {
-        return buildTestTag(
-                testName, app, beginRotation, endRotation, app2 = null, extraInfo = "")
-    }
-
-    /**
-     * Build a test tag for the test
-     * @param testName Name of the transition(s) being tested
-     * @param app App being launcher
-     * @param app2 Second app being launched (if any)
-     * @param beginRotation Initial screen rotation
-     * @param endRotation End screen rotation (if any, otherwise use same as initial)
-     * @param extraInfo Additional information to append to the tag
-     *
-     * @return test tag with pattern <NAME>__<APP></APP>(S)>__<ROTATION></ROTATION>(S)>[__<EXTRA>]
-    </EXTRA></NAME> */
-    protected fun buildTestTag(
-        testName: String,
-        app: IAppHelper,
-        beginRotation: Int,
-        endRotation: Int,
-        app2: IAppHelper?,
-        extraInfo: String
-    ): String {
-        var testTag = "${testName}__${app.launcherName}"
-        if (app2 != null) {
-            testTag += "-${app2.launcherName}"
-        }
-        testTag += "__${Surface.rotationToString(beginRotation)}"
-        if (endRotation != beginRotation) {
-            testTag += "-${Surface.rotationToString(endRotation)}"
-        }
-        if (extraInfo.isNotEmpty()) {
-            testTag += "__$extraInfo"
-        }
-        return testTag
-    }
-
-    protected fun Flicker.setRotation(rotation: Int) {
-        try {
-            when (rotation) {
-                Surface.ROTATION_270 -> device.setOrientationLeft()
-                Surface.ROTATION_90 -> device.setOrientationRight()
-                Surface.ROTATION_0 -> device.setOrientationNatural()
-                else -> device.setOrientationNatural()
-            }
-            // Wait for animation to complete
-            SystemClock.sleep(1000)
-        } catch (e: RemoteException) {
-            throw RuntimeException(e)
-        }
-    }
-
-    companion object {
-        const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
-        const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
-        const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
-    }
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt
deleted file mode 100644
index e7d1f8e..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt
+++ /dev/null
@@ -1,36 +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.server.wm.flicker
-
-import android.view.Surface
-import org.junit.runners.Parameterized
-
-abstract class NonRotationTestBase(
-    protected val rotationName: String,
-    protected val rotation: Int
-) : FlickerTestBase() {
-    companion object {
-        const val SCREENSHOT_LAYER = "RotationLayer"
-
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt
deleted file mode 100644
index 3b67727..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt
+++ /dev/null
@@ -1,49 +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.server.wm.flicker
-
-import android.view.Surface
-import org.junit.runners.Parameterized
-
-abstract class RotationTestBase(
-    beginRotationName: String,
-    endRotationName: String,
-    protected val beginRotation: Int,
-    protected val endRotation: Int
-) : FlickerTestBase() {
-    companion object {
-        @Parameterized.Parameters(name = "{0}-{1}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            val params: MutableCollection<Array<Any>> = mutableListOf()
-            for (begin in supportedRotations) {
-                for (end in supportedRotations) {
-                    if (begin != end) {
-                        params.add(arrayOf(
-                                Surface.rotationToString(begin),
-                                Surface.rotationToString(end),
-                                begin,
-                                end
-                        ))
-                    }
-                }
-            }
-            return params
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
new file mode 100644
index 0000000..742003a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.server.wm.flicker.helpers
+
+import android.os.Bundle
+import android.os.RemoteException
+import android.os.SystemClock
+import android.platform.helpers.IAppHelper
+import android.view.Surface
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.startRotation
+
+fun Flicker.setRotation(rotation: Int) {
+    try {
+        when (rotation) {
+            Surface.ROTATION_270 -> device.setOrientationLeft()
+            Surface.ROTATION_90 -> device.setOrientationRight()
+            Surface.ROTATION_0 -> device.setOrientationNatural()
+            else -> device.setOrientationNatural()
+        }
+        // Wait for animation to complete
+        SystemClock.sleep(1000)
+    } catch (e: RemoteException) {
+        throw RuntimeException(e)
+    }
+}
+
+/**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param beginRotation Initial screen rotation
+ * @param endRotation End screen rotation (if any, otherwise use same as initial)
+ *
+ * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
+</END_ROTATION></BEGIN_ROTATION></APP></NAME> */
+fun buildTestTag(
+    testName: String,
+    app: IAppHelper,
+    beginRotation: Int,
+    endRotation: Int
+): String {
+    return buildTestTag(
+        testName, app.launcherName, beginRotation, endRotation, app2 = null, extraInfo = "")
+}
+
+/**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param rotation Screen rotation configuration for the test
+ *
+ * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
+</END_ROTATION></BEGIN_ROTATION></APP></NAME> */
+fun buildTestTag(
+    testName: String,
+    app: IAppHelper?,
+    configuration: Bundle
+): String {
+    return buildTestTag(testName, app?.launcherName ?: "", configuration.startRotation,
+        configuration.endRotation, app2 = null, extraInfo = "")
+}
+
+/**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param app2 Second app being launched (if any)
+ * @param beginRotation Initial screen rotation
+ * @param endRotation End screen rotation (if any, otherwise use same as initial)
+ * @param extraInfo Additional information to append to the tag
+ *
+ * @return test tag with pattern <NAME>__<APP></APP>(S)>__<ROTATION></ROTATION>(S)>[__<EXTRA>]
+</EXTRA></NAME> */
+fun buildTestTag(
+    testName: String,
+    app: String,
+    beginRotation: Int,
+    endRotation: Int,
+    app2: String?,
+    extraInfo: String
+): String {
+    var testTag = "${testName}__$app"
+    if (app2 != null) {
+        testTag += "-$app2"
+    }
+    testTag += "__${Surface.rotationToString(beginRotation)}"
+    if (endRotation != beginRotation) {
+        testTag += "-${Surface.rotationToString(endRotation)}"
+    }
+    if (extraInfo.isNotEmpty()) {
+        testTag += "__$extraInfo"
+    }
+    return testTag
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 404c789..a73264d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -16,21 +16,27 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,56 +45,62 @@
  * Test IME window closing back to app window transitions.
  * To run this test: `atest FlickerTests:CloseImeWindowToAppTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class CloseImeAutoOpenWindowToAppTest(
-    rotationName: String,
-    rotation: Int
-) : CloseImeWindowToAppTest(rotationName, rotation) {
-    override val testApp: ImeAppHelper
-        get() = ImeAppAutoFocusHelper(instrumentation)
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
 
-    @Test
-    override fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("imeToAppAutoOpen", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                eachRun {
-                    device.wakeUpAndGoToHomeScreen()
-                    this.setRotation(rotation)
-                    testApp.open()
-                    testApp.openIME(device)
-                }
-            }
-            teardown {
-                eachRun {
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-            transitions {
-                device.pressBack()
-                device.waitForIdle()
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    imeAppWindowIsAlwaysVisible(testApp, bugId = 141458352)
-                }
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = ImeAppAutoFocusHelper(instrumentation)
 
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
-                    navBarLayerRotatesAndScales(rotation)
-                    statusBarLayerRotatesScales(rotation)
-                    imeLayerBecomesInvisible(bugId = 141458352)
-                    imeAppLayerIsAlwaysVisible(testApp, bugId = 141458352)
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTag { buildTestTag("imeToAppAutoOpen", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        eachRun {
+                            device.wakeUpAndGoToHomeScreen()
+                            this.setRotation(configuration.startRotation)
+                            testApp.open()
+                            testApp.openIME(device)
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            testApp.exit()
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                    }
+                    transitions {
+                        device.pressBack()
+                        device.waitForIdle()
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                            imeAppWindowIsAlwaysVisible(testApp, bugId = 141458352)
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible()
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.startRotation)
+                            navBarLayerRotatesAndScales(configuration.startRotation)
+                            statusBarLayerRotatesScales(configuration.startRotation)
+                            imeLayerBecomesInvisible(bugId = 141458352)
+                            imeAppLayerIsAlwaysVisible(testApp, bugId = 141458352)
+                        }
+                    }
                 }
-            }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index c1ba21a..7647802 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -19,19 +19,24 @@
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -45,53 +50,63 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class CloseImeAutoOpenWindowToHomeTest(
-    rotationName: String,
-    rotation: Int
-) : CloseImeWindowToHomeTest(rotationName, rotation) {
-    override val testApp: ImeAppHelper
-        get() = ImeAppAutoFocusHelper(instrumentation)
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
 
-    @Test
-    override fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("imeToHomeAutoOpen", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                eachRun {
-                    device.wakeUpAndGoToHomeScreen()
-                    this.setRotation(rotation)
-                    testApp.open()
-                    testApp.openIME(device)
-                }
-            }
-            teardown {
-                eachRun {
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-            transitions {
-                device.pressHome()
-                device.waitForIdle()
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    imeWindowBecomesInvisible(bugId = 141458352)
-                    imeAppWindowBecomesInvisible(testApp, bugId = 157449248)
-                }
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = ImeAppAutoFocusHelper(instrumentation)
 
-                layersTrace {
-                    navBarLayerIsAlwaysVisible(bugId = 140855415)
-                    statusBarLayerIsAlwaysVisible(bugId = 140855415)
-                    noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
-                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0, bugId = 140855415)
-                    statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
-                    imeLayerBecomesInvisible(bugId = 141458352)
-                    imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTestName {
+                        buildTestTag("imeToHomeAutoOpen", testApp, configuration)
+                    }
+                    repeat { configuration.repetitions }
+                    setup {
+                        eachRun {
+                            device.wakeUpAndGoToHomeScreen()
+                            this.setRotation(configuration.startRotation)
+                            testApp.open()
+                            testApp.openIME(device)
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            testApp.exit()
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                    }
+                    transitions {
+                        device.pressHome()
+                        device.waitForIdle()
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                            imeWindowBecomesInvisible(bugId = 141458352)
+                            imeAppWindowBecomesInvisible(testApp, bugId = 157449248)
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+                                allStates = false)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0, bugId = 140855415)
+                            statusBarLayerRotatesScales(configuration.startRotation,
+                                Surface.ROTATION_0)
+                            imeLayerBecomesInvisible(bugId = 141458352)
+                            imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+                        }
+                    }
                 }
-            }
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 2c00722..136cf86 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -19,19 +19,24 @@
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -44,52 +49,57 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToAppTest(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    open val testApp = ImeAppHelper(instrumentation)
+class CloseImeWindowToAppTest(
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
 
-    @Test
-    open fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("imeToApp", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                eachRun {
-                    device.wakeUpAndGoToHomeScreen()
-                    this.setRotation(rotation)
-                    testApp.open()
-                    testApp.openIME(device)
-                }
-            }
-            teardown {
-                eachRun {
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-            transitions {
-                device.pressBack()
-                device.waitForIdle()
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    imeAppWindowIsAlwaysVisible(testApp)
-                }
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = ImeAppHelper(instrumentation)
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTestName { buildTestTag("imeToApp", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        eachRun {
+                            device.wakeUpAndGoToHomeScreen()
+                            this.setRotation(configuration.startRotation)
+                            testApp.open()
+                            testApp.openIME(device)
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            testApp.exit()
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                    }
+                    transitions {
+                        device.pressBack()
+                        device.waitForIdle()
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                            imeAppWindowIsAlwaysVisible(testApp)
+                        }
 
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
-                    navBarLayerRotatesAndScales(rotation)
-                    statusBarLayerRotatesScales(rotation)
-                    imeLayerBecomesInvisible(enabled = false)
-                    imeAppLayerIsAlwaysVisible(testApp)
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible()
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.startRotation)
+                            navBarLayerRotatesAndScales(configuration.startRotation)
+                            statusBarLayerRotatesScales(configuration.startRotation)
+                            imeLayerBecomesInvisible(enabled = false)
+                            imeAppLayerIsAlwaysVisible(testApp)
+                        }
+                    }
                 }
-            }
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 4697adc..6cfb282 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -19,21 +19,26 @@
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.openQuickstep
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -45,61 +50,69 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToHomeTest(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    open val testApp = ImeAppHelper(instrumentation)
+class CloseImeWindowToHomeTest(
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
 
-    @Test
-    open fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("imeToHome", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                    this.setRotation(rotation)
-                    testApp.open()
-                }
-                eachRun {
-                    device.openQuickstep()
-                    device.reopenAppFromOverview()
-                    this.setRotation(rotation)
-                    testApp.openIME(device)
-                }
-            }
-            transitions {
-                device.pressHome()
-                device.waitForIdle()
-            }
-            teardown {
-                eachRun {
-                    device.pressHome()
-                }
-                test {
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    imeWindowBecomesInvisible()
-                    imeAppWindowBecomesInvisible(testApp)
-                }
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = ImeAppHelper(instrumentation)
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTestName { buildTestTag("imeToHome", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                            this.setRotation(configuration.startRotation)
+                            testApp.open()
+                        }
+                        eachRun {
+                            device.openQuickstep()
+                            device.reopenAppFromOverview()
+                            this.setRotation(configuration.startRotation)
+                            testApp.openIME(device)
+                        }
+                    }
+                    transitions {
+                        device.pressHome()
+                        device.waitForIdle()
+                    }
+                    teardown {
+                        eachRun {
+                            device.pressHome()
+                        }
+                        test {
+                            testApp.exit()
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                            imeWindowBecomesInvisible()
+                            imeAppWindowBecomesInvisible(testApp)
+                        }
 
-                layersTrace {
-                    navBarLayerIsAlwaysVisible(bugId = 140855415)
-                    statusBarLayerIsAlwaysVisible(bugId = 140855415)
-                    noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
-                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0, bugId = 140855415)
-                    statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
-                    imeLayerBecomesInvisible(bugId = 153739621)
-                    imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                            noUncoveredRegions(configuration.startRotation,
+                                Surface.ROTATION_0, allStates = false)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0, bugId = 140855415)
+                            statusBarLayerRotatesScales(configuration.startRotation,
+                                Surface.ROTATION_0)
+                            imeLayerBecomesInvisible(bugId = 153739621)
+                            imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+                        }
+                    }
                 }
-            }
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ImeAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
similarity index 100%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/ime/ImeAssertions.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 2caa8f3..5767a94 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -19,19 +19,24 @@
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -45,62 +50,65 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenImeWindowTest(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        val testApp = ImeAppHelper(instrumentation)
-
-        flicker(instrumentation) {
-            withTag { buildTestTag("openIme", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                    this.setRotation(rotation)
-                    testApp.open()
-                }
-            }
-            transitions {
-                testApp.openIME(device)
-            }
-            teardown {
-                eachRun {
-                    testApp.closeIME(device)
-                }
-                test {
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-
-                    all("imeWindowBecomesVisible") {
-                        this.skipUntilFirstAssertion()
-                            .hidesNonAppWindow(IME_WINDOW_TITLE)
-                            .then()
-                            .showsNonAppWindow(IME_WINDOW_TITLE)
-                    }
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
-                    navBarLayerRotatesAndScales(rotation)
-                    statusBarLayerRotatesScales(rotation)
-
-                    imeLayerBecomesVisible()
-                }
-            }
-        }
-    }
-
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
     companion object {
         private const val IME_WINDOW_TITLE = "InputMethod"
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = ImeAppHelper(instrumentation)
+
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTestName { buildTestTag("openIme", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                            this.setRotation(configuration.startRotation)
+                            testApp.open()
+                        }
+                    }
+                    transitions {
+                        testApp.openIME(device)
+                    }
+                    teardown {
+                        eachRun {
+                            testApp.closeIME(device)
+                        }
+                        test {
+                            testApp.exit()
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+
+                            all("imeWindowBecomesVisible") {
+                                this.skipUntilFirstAssertion()
+                                    .hidesNonAppWindow(IME_WINDOW_TITLE)
+                                    .then()
+                                    .showsNonAppWindow(IME_WINDOW_TITLE)
+                            }
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible()
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.startRotation)
+                            navBarLayerRotatesAndScales(configuration.startRotation)
+                            statusBarLayerRotatesScales(configuration.startRotation)
+
+                            imeLayerBecomesVisible()
+                        }
+                    }
+                }
+        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
new file mode 100644
index 0000000..7e857f3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.platform.helpers.IAppHelper
+import com.android.server.wm.flicker.dsl.LayersAssertion
+import com.android.server.wm.flicker.dsl.WmAssertion
+
+fun WmAssertion.wallpaperWindowBecomesInvisible(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("wallpaperWindowBecomesInvisible", enabled, bugId) {
+        this.showsBelowAppWindow("Wallpaper")
+                .then()
+                .hidesBelowAppWindow("Wallpaper")
+    }
+}
+
+fun WmAssertion.appWindowReplacesLauncherAsTopWindow(
+    testApp: IAppHelper,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("appWindowReplacesLauncherAsTopWindow", enabled, bugId) {
+        this.showsAppWindowOnTop("Launcher")
+                .then()
+                .showsAppWindowOnTop("Snapshot", testApp.getPackage())
+    }
+}
+
+fun LayersAssertion.wallpaperLayerBecomesInvisible(
+    testApp: IAppHelper,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("wallpaperLayerBecomesInvisible", enabled, bugId) {
+        this.showsLayer("Wallpaper")
+                .then()
+                .replaceVisibleLayer("Wallpaper", testApp.getPackage())
+    }
+}
\ No newline at end of file
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 2c9c8ba..1081414 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
@@ -19,18 +19,26 @@
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -44,53 +52,64 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenAppColdTest(
-    rotationName: String,
-    rotation: Int
-) : OpenAppTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("openAppCold", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                }
-                eachRun {
-                    this.setRotation(rotation)
-                }
-            }
-            transitions {
-                testApp.open()
-            }
-            teardown {
-                eachRun {
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    appWindowReplacesLauncherAsTopWindow()
-                    wallpaperWindowBecomesInvisible()
-                }
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = StandardAppHelper(instrumentation,
+                "com.android.server.wm.flicker.testapp", "SimpleApp")
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTag { buildTestTag("openAppCold", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                        }
+                        eachRun {
+                            this.setRotation(configuration.startRotation)
+                        }
+                    }
+                    transitions {
+                        testApp.open()
+                    }
+                    teardown {
+                        eachRun {
+                            testApp.exit()
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                            appWindowReplacesLauncherAsTopWindow(testApp)
+                            wallpaperWindowBecomesInvisible()
+                        }
 
-                layersTrace {
-                    // During testing the launcher is always in portrait mode
-                    noUncoveredRegions(Surface.ROTATION_0, rotation, bugId = 141361128)
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation)
-                    navBarLayerIsAlwaysVisible(enabled = rotation == Surface.ROTATION_0)
-                    statusBarLayerIsAlwaysVisible(enabled = false)
-                    wallpaperLayerBecomesInvisible()
-                }
+                        layersTrace {
+                            // During testing the launcher is always in portrait mode
+                            noUncoveredRegions(Surface.ROTATION_0, configuration.endRotation,
+                                bugId = 141361128)
+                            navBarLayerRotatesAndScales(Surface.ROTATION_0,
+                                configuration.endRotation)
+                            statusBarLayerRotatesScales(Surface.ROTATION_0,
+                                configuration.endRotation)
+                            navBarLayerIsAlwaysVisible(
+                                enabled = configuration.endRotation == Surface.ROTATION_0)
+                            statusBarLayerIsAlwaysVisible(enabled = false)
+                            wallpaperLayerBecomesInvisible(testApp)
+                        }
 
-                eventLog {
-                    focusChanges("NexusLauncherActivity", testApp.`package`)
+                        eventLog {
+                            focusChanges("NexusLauncherActivity", testApp.`package`)
+                        }
+                    }
                 }
-            }
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTestBase.kt
deleted file mode 100644
index 98e05d5..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTestBase.kt
+++ /dev/null
@@ -1,63 +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.server.wm.flicker.launch
-
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.LayersAssertion
-import com.android.server.wm.flicker.dsl.WmAssertion
-
-abstract class OpenAppTestBase(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    protected val testApp = StandardAppHelper(instrumentation,
-            "com.android.server.wm.flicker.testapp", "SimpleApp")
-
-    protected fun WmAssertion.wallpaperWindowBecomesInvisible(
-        bugId: Int = 0,
-        enabled: Boolean = bugId == 0
-    ) {
-        all("wallpaperWindowBecomesInvisible", enabled, bugId) {
-            this.showsBelowAppWindow("Wallpaper")
-                    .then()
-                    .hidesBelowAppWindow("Wallpaper")
-        }
-    }
-
-    protected fun WmAssertion.appWindowReplacesLauncherAsTopWindow(
-        bugId: Int = 0,
-        enabled: Boolean = bugId == 0
-    ) {
-        all("appWindowReplacesLauncherAsTopWindow", enabled, bugId) {
-            this.showsAppWindowOnTop("Launcher")
-                    .then()
-                    .showsAppWindowOnTop("Snapshot", testApp.getPackage())
-        }
-    }
-
-    protected fun LayersAssertion.wallpaperLayerBecomesInvisible(
-        bugId: Int = 0,
-        enabled: Boolean = bugId == 0
-    ) {
-        all("appWindowReplacesLauncherAsTopWindow", enabled, bugId) {
-            this.showsLayer("Wallpaper")
-                    .then()
-                    .replaceVisibleLayer("Wallpaper", testApp.getPackage())
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index acd141a..2061994 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -16,21 +16,29 @@
 
 package com.android.server.wm.flicker.launch
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,64 +47,73 @@
  * Test warm launch app.
  * To run this test: `atest FlickerTests:OpenAppWarmTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenAppWarmTest(
-    rotationName: String,
-    rotation: Int
-) : OpenAppTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        val testApp = StandardAppHelper(instrumentation,
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = StandardAppHelper(instrumentation,
                 "com.android.server.wm.flicker.testapp", "SimpleApp")
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTag { buildTestTag("openAppWarm", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                            testApp.open()
+                        }
+                        eachRun {
+                            device.pressHome()
+                            this.setRotation(configuration.startRotation)
+                        }
+                    }
+                    transitions {
+                        testApp.open()
+                    }
+                    teardown {
+                        eachRun {
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                        test {
+                            testApp.exit()
+                        }
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                            appWindowReplacesLauncherAsTopWindow(testApp)
+                            wallpaperWindowBecomesInvisible(enabled = false)
+                        }
 
-        flicker(instrumentation) {
-            withTag { buildTestTag("openAppWarm", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                    testApp.open()
-                }
-                eachRun {
-                    device.pressHome()
-                    this.setRotation(rotation)
-                }
-            }
-            transitions {
-                testApp.open()
-            }
-            teardown {
-                eachRun {
-                    this.setRotation(Surface.ROTATION_0)
-                }
-                test {
-                    testApp.exit()
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    appWindowReplacesLauncherAsTopWindow()
-                    wallpaperWindowBecomesInvisible(enabled = false)
-                }
+                        layersTrace {
+                            // During testing the launcher is always in portrait mode
+                            noUncoveredRegions(Surface.ROTATION_0, configuration.endRotation,
+                                bugId = 141361128)
+                            navBarLayerRotatesAndScales(Surface.ROTATION_0,
+                                configuration.endRotation)
+                            statusBarLayerRotatesScales(Surface.ROTATION_0,
+                                configuration.endRotation)
+                            navBarLayerIsAlwaysVisible(
+                                enabled = configuration.endRotation == Surface.ROTATION_0)
+                            statusBarLayerIsAlwaysVisible(enabled = false)
+                            wallpaperLayerBecomesInvisible(testApp)
+                        }
 
-                layersTrace {
-                    // During testing the launcher is always in portrait mode
-                    noUncoveredRegions(Surface.ROTATION_0, rotation, bugId = 141361128)
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation)
-                    navBarLayerIsAlwaysVisible(enabled = rotation == Surface.ROTATION_0)
-                    statusBarLayerIsAlwaysVisible(enabled = false)
-                    wallpaperLayerBecomesInvisible()
+                        eventLog {
+                            focusChanges("NexusLauncherActivity", testApp.`package`)
+                        }
+                    }
                 }
-
-                eventLog {
-                    focusChanges("NexusLauncherActivity", testApp.`package`)
-                }
-            }
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/CommonAssertions.kt
similarity index 63%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/pip/CommonAssertions.kt
index 691db7fb..6bc9dcb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/CommonAssertions.kt
@@ -16,16 +16,4 @@
 
 package com.android.server.wm.flicker.pip
 
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.helpers.PipAppHelper
-
-abstract class PipTestBase(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    protected val testApp = PipAppHelper(instrumentation)
-
-    companion object {
-        const val sPipWindowTitle = "PipMenuActivity"
-    }
-}
\ No newline at end of file
+internal const val PIP_WINDOW_TITLE = "PipMenuActivity"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt
index 9cfc033..89539fd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt
@@ -16,23 +16,31 @@
 
 package com.android.server.wm.flicker.pip
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.closePipWindow
 import com.android.server.wm.flicker.helpers.expandPipWindow
 import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -41,81 +49,85 @@
  * Test Pip launch.
  * To run this test: `atest FlickerTests:PipToAppTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 152738416)
 class EnterPipTest(
-    rotationName: String,
-    rotation: Int
-) : PipTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("enterPip", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                }
-                eachRun {
-                    device.pressHome()
-                    testApp.open()
-                    this.setRotation(rotation)
-                }
-            }
-            teardown {
-                eachRun {
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
-                    testApp.exit()
-                    this.setRotation(Surface.ROTATION_0)
-                }
-                test {
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
-                }
-            }
-            transitions {
-                testApp.clickEnterPipButton(device)
-                device.expandPipWindow()
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    all("pipWindowBecomesVisible") {
-                        this.showsAppWindow(testApp.`package`)
-                                .then()
-                                .showsAppWindow(sPipWindowTitle)
-                    }
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
-                    navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0)
-                    statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
-
-                    all("pipLayerBecomesVisible") {
-                        this.showsLayer(testApp.launcherName)
-                                .then()
-                                .showsLayer(sPipWindowTitle)
-                    }
-                }
-            }
-        }
-    }
-
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = PipAppHelper(instrumentation)
+            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+                .buildTest { configuration ->
+                    withTestName { buildTestTag("enterPip", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                        }
+                        eachRun {
+                            device.pressHome()
+                            testApp.open()
+                            this.setRotation(configuration.startRotation)
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            if (device.hasPipWindow()) {
+                                device.closePipWindow()
+                            }
+                            testApp.exit()
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                        test {
+                            if (device.hasPipWindow()) {
+                                device.closePipWindow()
+                            }
+                        }
+                    }
+                    transitions {
+                        testApp.clickEnterPipButton(device)
+                        device.expandPipWindow()
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+
+                            all("pipWindowBecomesVisible") {
+                                this.showsAppWindow(testApp.`package`)
+                                    .then()
+                                    .showsAppWindow(PIP_WINDOW_TITLE)
+                            }
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+                                enabled = false)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0, bugId = 140855415)
+                            statusBarLayerRotatesScales(configuration.startRotation,
+                                Surface.ROTATION_0)
+                        }
+
+                        layersTrace {
+                            all("pipLayerBecomesVisible") {
+                                this.showsLayer(testApp.launcherName)
+                                    .then()
+                                    .showsLayer(PIP_WINDOW_TITLE)
+                            }
+                        }
+                    }
+                }
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
index deccc90..ac54a0a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
@@ -19,21 +19,28 @@
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.closePipWindow
 import com.android.server.wm.flicker.helpers.expandPipWindow
 import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -47,73 +54,82 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 152738416)
 class PipToAppTest(
-    rotationName: String,
-    rotation: Int
-) : PipTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("exitPipModeToApp", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                    device.pressHome()
-                    testApp.open()
-                }
-                eachRun {
-                    this.setRotation(rotation)
-                    testApp.clickEnterPipButton(device)
-                    device.hasPipWindow()
-                }
-            }
-            teardown {
-                eachRun {
-                    this.setRotation(Surface.ROTATION_0)
-                }
-                test {
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = PipAppHelper(instrumentation)
+            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+                .buildTest { configuration ->
+                    withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                            device.pressHome()
+                            testApp.open()
+                        }
+                        eachRun {
+                            this.setRotation(configuration.startRotation)
+                            testApp.clickEnterPipButton(device)
+                            device.hasPipWindow()
+                        }
                     }
-                    testApp.exit()
-                }
-            }
-            transitions {
-                device.expandPipWindow()
-                device.waitForIdle()
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
+                    teardown {
+                        eachRun {
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                        test {
+                            if (device.hasPipWindow()) {
+                                device.closePipWindow()
+                            }
+                            testApp.exit()
+                        }
+                    }
+                    transitions {
+                        device.expandPipWindow()
+                        device.waitForIdle()
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
 
-                    all("appReplacesPipWindow") {
-                        this.showsAppWindow(sPipWindowTitle)
-                                .then()
-                                .showsAppWindowOnTop(testApp.launcherName)
+                            all("appReplacesPipWindow") {
+                                this.showsAppWindow(PIP_WINDOW_TITLE)
+                                    .then()
+                                    .showsAppWindowOnTop(testApp.launcherName)
+                            }
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+                                enabled = false)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0, bugId = 140855415)
+                            statusBarLayerRotatesScales(configuration.startRotation,
+                                Surface.ROTATION_0)
+
+                            all("appReplacesPipLayer") {
+                                this.showsLayer(PIP_WINDOW_TITLE)
+                                    .then()
+                                    .showsLayer(testApp.launcherName)
+                            }
+                        }
+
+                        eventLog {
+                            focusChanges(
+                                "NexusLauncherActivity", testApp.launcherName,
+                                "NexusLauncherActivity", bugId = 151179149)
+                        }
                     }
                 }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
-                    navBarLayerRotatesAndScales(rotation)
-                    statusBarLayerRotatesScales(rotation)
-
-                    all("appReplacesPipLayer") {
-                        this.showsLayer(sPipWindowTitle)
-                                .then()
-                                .showsLayer(testApp.launcherName)
-                    }
-                }
-
-                eventLog {
-                    focusChanges(
-                            "NexusLauncherActivity", testApp.launcherName, "NexusLauncherActivity",
-                            bugId = 151179149)
-                }
-            }
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
index f40869c..f14a27d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
@@ -19,20 +19,27 @@
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.closePipWindow
 import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -46,83 +53,83 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 152738416)
 class PipToHomeTest(
-    rotationName: String,
-    rotation: Int
-) : PipTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        flicker(instrumentation) {
-            withTag { buildTestTag("exitPipModeToApp", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                    device.pressHome()
-                }
-                eachRun {
-                    testApp.open()
-                    this.setRotation(rotation)
-                    testApp.clickEnterPipButton(device)
-                    device.hasPipWindow()
-                }
-            }
-            teardown {
-                eachRun {
-                    this.setRotation(Surface.ROTATION_0)
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
-                }
-                test {
-                    if (device.hasPipWindow()) {
-                        device.closePipWindow()
-                    }
-                    testApp.exit()
-                }
-            }
-            transitions {
-                testApp.closePipWindow(device)
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-
-                    all("pipWindowBecomesInvisible") {
-                        this.showsAppWindow(sPipWindowTitle)
-                                .then()
-                                .hidesAppWindow(sPipWindowTitle)
-                    }
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    // The final state is the launcher, so always in portrait mode
-                    noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
-                    navBarLayerRotatesAndScales(rotation)
-                    statusBarLayerRotatesScales(rotation)
-
-                    all("pipLayerBecomesInvisible") {
-                        this.showsLayer(sPipWindowTitle)
-                                .then()
-                                .hidesLayer(sPipWindowTitle)
-                    }
-                }
-
-                eventLog {
-                    focusChanges(testApp.launcherName, "NexusLauncherActivity", bugId = 151179149)
-                }
-            }
-        }
-    }
-
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = PipAppHelper(instrumentation)
+            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+                .buildTest { configuration ->
+                    withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                            device.pressHome()
+                        }
+                        eachRun {
+                            testApp.open()
+                            this.setRotation(configuration.startRotation)
+                            testApp.clickEnterPipButton(device)
+                            device.hasPipWindow()
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            this.setRotation(Surface.ROTATION_0)
+                            if (device.hasPipWindow()) {
+                                device.closePipWindow()
+                            }
+                        }
+                        test {
+                            if (device.hasPipWindow()) {
+                                device.closePipWindow()
+                            }
+                            testApp.exit()
+                        }
+                    }
+                    transitions {
+                        testApp.closePipWindow(device)
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+
+                            all("pipWindowBecomesInvisible") {
+                                this.showsAppWindow(PIP_WINDOW_TITLE)
+                                    .then()
+                                    .hidesAppWindow(PIP_WINDOW_TITLE)
+                            }
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+                                enabled = false)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                Surface.ROTATION_0, bugId = 140855415)
+                            statusBarLayerRotatesScales(configuration.startRotation,
+                                Surface.ROTATION_0)
+
+                            all("pipLayerBecomesInvisible") {
+                                this.showsLayer(PIP_WINDOW_TITLE)
+                                    .then()
+                                    .hidesLayer(PIP_WINDOW_TITLE)
+                            }
+                        }
+
+                        eventLog {
+                            focusChanges(testApp.launcherName, "NexusLauncherActivity",
+                                bugId = 151179149)
+                        }
+                    }
+                }
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 99218c2..24ca311 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -16,24 +16,30 @@
 
 package com.android.server.wm.flicker.rotation
 
+import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import android.view.Surface
-import com.android.server.wm.flicker.NonRotationTestBase.Companion.SCREENSHOT_LAYER
-import com.android.server.wm.flicker.RotationTestBase
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.helpers.StandardAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -42,84 +48,95 @@
  * Cycle through supported app rotations.
  * To run this test: `atest FlickerTest:ChangeAppRotationTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class ChangeAppRotationTest(
-    beginRotationName: String,
-    endRotationName: String,
-    beginRotation: Int,
-    endRotation: Int
-) : RotationTestBase(beginRotationName, endRotationName, beginRotation, endRotation) {
-    @Test
-    fun test() {
-        val testApp = StandardAppHelper(instrumentation,
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+    companion object {
+        private const val SCREENSHOT_LAYER = "RotationLayer"
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = StandardAppHelper(instrumentation,
                 "com.android.server.wm.flicker.testapp", "SimpleApp")
-
-        flicker(instrumentation) {
-            withTag {
-                buildTestTag("changeAppRotation", testApp, beginRotation, endRotation)
-            }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                    testApp.open()
-                }
-                eachRun {
-                    this.setRotation(beginRotation)
-                }
-            }
-            teardown {
-                eachRun {
-                    this.setRotation(Surface.ROTATION_0)
-                }
-                test {
-                    testApp.exit()
-                }
-            }
-            transitions {
-                this.setRotation(endRotation)
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible(bugId = 140855415)
-                    statusBarLayerIsAlwaysVisible(bugId = 140855415)
-                    noUncoveredRegions(beginRotation, endRotation, allStates = false)
-                    navBarLayerRotatesAndScales(beginRotation, endRotation)
-                    statusBarLayerRotatesScales(beginRotation, endRotation)
-                }
-
-                layersTrace {
-                    val startingPos = WindowUtils.getDisplayBounds(beginRotation)
-                    val endingPos = WindowUtils.getDisplayBounds(endRotation)
-
-                    start("appLayerRotates_StartingPos") {
-                        this.hasVisibleRegion(testApp.getPackage(), startingPos)
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildRotationTest { configuration ->
+                    withTestName {
+                        buildTestTag(
+                            "changeAppRotation", testApp, configuration)
                     }
-
-                    end("appLayerRotates_EndingPos") {
-                        this.hasVisibleRegion(testApp.getPackage(), endingPos)
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                            testApp.open()
+                        }
+                        eachRun {
+                            this.setRotation(configuration.startRotation)
+                        }
                     }
+                    teardown {
+                        eachRun {
+                            this.setRotation(Surface.ROTATION_0)
+                        }
+                        test {
+                            testApp.exit()
+                        }
+                    }
+                    transitions {
+                        this.setRotation(configuration.endRotation)
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                        }
 
-                    all("screenshotLayerBecomesInvisible") {
-                        this.showsLayer(testApp.getPackage())
-                                .then()
-                                .showsLayer(SCREENSHOT_LAYER)
-                                .then()
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                            noUncoveredRegions(configuration.startRotation,
+                                configuration.endRotation, allStates = false)
+                            navBarLayerRotatesAndScales(configuration.startRotation,
+                                configuration.endRotation)
+                            statusBarLayerRotatesScales(configuration.startRotation,
+                                configuration.endRotation)
+                        }
+
+                        layersTrace {
+                            val startingPos = WindowUtils.getDisplayBounds(
+                                configuration.startRotation)
+                            val endingPos = WindowUtils.getDisplayBounds(
+                                configuration.endRotation)
+
+                            start("appLayerRotates_StartingPos") {
+                                this.hasVisibleRegion(testApp.getPackage(), startingPos)
+                            }
+
+                            end("appLayerRotates_EndingPos") {
+                                this.hasVisibleRegion(testApp.getPackage(), endingPos)
+                            }
+
+                            all("screenshotLayerBecomesInvisible") {
+                                this.showsLayer(testApp.getPackage())
+                                        .then()
+                                        .showsLayer(SCREENSHOT_LAYER)
+                                        .then()
                                 showsLayer(testApp.getPackage())
+                            }
+                        }
+
+                        eventLog {
+                            focusDoesNotChange(bugId = 151179149)
+                        }
                     }
                 }
-
-                eventLog {
-                    focusDoesNotChange(bugId = 151179149)
-                }
-            }
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 33a823d..8ad6c46 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -16,29 +16,36 @@
 
 package com.android.server.wm.flicker.rotation
 
+import android.content.ComponentName
 import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.RotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.focusDoesNotChange
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.stopPackage
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -47,150 +54,142 @@
  * Cycle through supported app rotations using seamless rotations.
  * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 147659548)
 class SeamlessAppRotationTest(
-    testId: String,
-    private val intent: Intent,
-    beginRotationName: String,
-    endRotationName: String,
-    beginRotation: Int,
-    endRotation: Int
-) : RotationTestBase(beginRotationName, endRotationName, beginRotation, endRotation) {
-    @Test
-    fun test() {
-        var intentId = ""
-        if (intent.extras?.getBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD) == true) {
-            intentId = "BUSY_UI_THREAD"
-        }
-
-        flicker(instrumentation) {
-            withTag {
-                "changeAppRotation_" + intentId + "_" +
-                        Surface.rotationToString(beginRotation) + "_" +
-                        Surface.rotationToString(endRotation)
-            }
-            repeat { 1 }
-            setup {
-                eachRun {
-                    device.wakeUpAndGoToHomeScreen()
-                    instrumentation.targetContext.startActivity(intent)
-                    device.wait(Until.hasObject(By.pkg(intent.component?.packageName)
-                            .depth(0)), APP_LAUNCH_TIMEOUT)
-                    this.setRotation(beginRotation)
-                }
-            }
-            teardown {
-                eachRun {
-                    stopPackage(
-                            instrumentation.targetContext,
-                            intent.component?.packageName
-                                    ?: error("Unable to determine package name for intent"))
-                    this.setRotation(Surface.ROTATION_0)
-                }
-            }
-            transitions {
-                this.setRotation(endRotation)
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible(bugId = 140855415)
-                    statusBarWindowIsAlwaysVisible(bugId = 140855415)
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible(bugId = 140855415)
-                    statusBarLayerIsAlwaysVisible(bugId = 140855415)
-                    noUncoveredRegions(beginRotation, endRotation, allStates = true)
-                    navBarLayerRotatesAndScales(beginRotation, endRotation)
-                    statusBarLayerRotatesScales(beginRotation, endRotation, enabled = false)
-                }
-
-                layersTrace {
-                    all("appLayerRotates"/*, bugId = 147659548*/) {
-                        val startingPos = WindowUtils.getDisplayBounds(beginRotation)
-                        val endingPos = WindowUtils.getDisplayBounds(endRotation)
-
-                        if (startingPos == endingPos) {
-                            this.hasVisibleRegion(
-                                    intent.component?.packageName ?: "",
-                                    startingPos)
-                        } else {
-                            this.hasVisibleRegion(intent.component?.packageName ?: "", startingPos)
-                                    .then()
-                                    .hasVisibleRegion(intent.component?.packageName
-                                            ?: "", endingPos)
-                        }
-                    }
-
-                    all("noUncoveredRegions"/*, bugId = 147659548*/) {
-                        val startingBounds = WindowUtils.getDisplayBounds(beginRotation)
-                        val endingBounds = WindowUtils.getDisplayBounds(endRotation)
-                        if (startingBounds == endingBounds) {
-                            this.coversAtLeastRegion(startingBounds)
-                        } else {
-                            this.coversAtLeastRegion(startingBounds)
-                                    .then()
-                                    .coversAtLeastRegion(endingBounds)
-                        }
-                    }
-                }
-
-                eventLog {
-                    focusDoesNotChange(bugId = 151179149)
-                }
-            }
-        }
-    }
-
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
     companion object {
         private const val APP_LAUNCH_TIMEOUT: Long = 10000
 
-        // launch test activity that supports seamless rotation with a busy UI thread to miss frames
-        // when the app is asked to redraw
+        private val Bundle.intent: Intent?
+            get() = this.getParcelable(Intent::class.java.simpleName)
+
+        private val Bundle.intentPackageName: String
+            get() = this.intent?.component?.packageName ?: ""
+
+        private val Bundle.intentId get() = if (this.intent?.getBooleanExtra(
+                ActivityOptions.EXTRA_STARVE_UI_THREAD, false) == true) {
+            "BUSY_UI_THREAD"
+        } else {
+            ""
+        }
+
+        private fun Bundle.createConfig(starveUiThread: Boolean): Bundle {
+            val config = this.deepCopy()
+            val intent = Intent()
+            intent.addCategory(Intent.CATEGORY_LAUNCHER)
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            intent.component = ComponentName("com.android.server.wm.flicker.testapp",
+                "com.android.server.wm.flicker.testapp.SeamlessRotationActivity")
+
+            intent.putExtra(ActivityOptions.EXTRA_STARVE_UI_THREAD, starveUiThread)
+
+            config.putParcelable(Intent::class.java.simpleName, intent)
+            return config
+        }
+
+        @JvmStatic
+        private fun FlickerTestRunnerFactory.getConfigurations(): List<Bundle> {
+            return this.getConfigRotationTests().flatMap {
+                val defaultRun = it.createConfig(starveUiThread = false)
+                val busyUiRun = it.createConfig(starveUiThread = true)
+                listOf(defaultRun, busyUiRun)
+            }
+        }
+
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            val params = mutableListOf<Array<Any>>()
-            val testIntents = mutableListOf<Intent>()
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val factory = FlickerTestRunnerFactory(instrumentation)
+            val configurations = factory.getConfigurations()
+            return factory.buildRotationTest(configurations) { configuration ->
+                withTestName {
+                    buildTestTag("seamlessRotation_" + configuration.intentId,
+                        app = null, configuration = configuration)
+                }
+                repeat { configuration.repetitions }
+                setup {
+                    test {
+                        device.wakeUpAndGoToHomeScreen()
+                        instrumentation.targetContext.startActivity(configuration.intent)
+                        val searchQuery = By.pkg(configuration.intent?.component?.packageName)
+                            .depth(0)
+                        device.wait(Until.hasObject(searchQuery), APP_LAUNCH_TIMEOUT)
+                    }
+                    eachRun {
+                        this.setRotation(configuration.startRotation)
+                    }
+                }
+                teardown {
+                    test {
+                        this.setRotation(Surface.ROTATION_0)
+                        stopPackage(
+                            instrumentation.targetContext,
+                            configuration.intent?.component?.packageName
+                                ?: error("Unable to determine package name for intent"))
+                    }
+                }
+                transitions {
+                    this.setRotation(configuration.endRotation)
+                }
+                assertions {
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible(bugId = 140855415)
+                        statusBarWindowIsAlwaysVisible(bugId = 140855415)
+                    }
 
-            // launch test activity that supports seamless rotation
-            var intent = Intent(Intent.ACTION_MAIN)
-            intent.component = ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME
-            intent.flags = FLAG_ACTIVITY_NEW_TASK
-            testIntents.add(intent)
+                    layersTrace {
+                        navBarLayerIsAlwaysVisible(bugId = 140855415)
+                        statusBarLayerIsAlwaysVisible(bugId = 140855415)
+                        noUncoveredRegions(configuration.startRotation,
+                            configuration.endRotation, allStates = false
+                            /*, bugId = 147659548*/)
+                        navBarLayerRotatesAndScales(configuration.startRotation,
+                            configuration.endRotation)
+                        statusBarLayerRotatesScales(configuration.startRotation,
+                            configuration.endRotation, enabled = false)
+                    }
 
-            // launch test activity that supports seamless rotation with a busy UI thread to miss frames
-            // when the app is asked to redraw
-            intent = Intent(intent)
-            intent.putExtra(ActivityOptions.EXTRA_STARVE_UI_THREAD, true)
-            intent.flags = FLAG_ACTIVITY_NEW_TASK
-            testIntents.add(intent)
-            for (testIntent in testIntents) {
-                for (begin in supportedRotations) {
-                    for (end in supportedRotations) {
-                        if (begin != end) {
-                            var testId: String = Surface.rotationToString(begin) +
-                                    "_" + Surface.rotationToString(end)
-                            if (testIntent.extras?.getBoolean(
-                                            ActivityOptions.EXTRA_STARVE_UI_THREAD) == true) {
-                                testId += "_" + "BUSY_UI_THREAD"
+                    layersTrace {
+                        val startingBounds = WindowUtils
+                            .getDisplayBounds(configuration.startRotation)
+                        val endingBounds = WindowUtils
+                            .getDisplayBounds(configuration.endRotation)
+
+                        all("appLayerRotates"/*, bugId = 147659548*/) {
+                            if (startingBounds == endingBounds) {
+                                this.hasVisibleRegion(
+                                    configuration.intentPackageName, startingBounds)
+                            } else {
+                                this.hasVisibleRegion(configuration.intentPackageName,
+                                    startingBounds)
+                                    .then()
+                                    .hasVisibleRegion(configuration.intentPackageName,
+                                        endingBounds)
                             }
-                            params.add(arrayOf(
-                                    testId,
-                                    testIntent,
-                                    Surface.rotationToString(begin),
-                                    Surface.rotationToString(end),
-                                    begin,
-                                    end))
                         }
+
+                        all("noUncoveredRegions"/*, bugId = 147659548*/) {
+                            if (startingBounds == endingBounds) {
+                                this.coversAtLeastRegion(startingBounds)
+                            } else {
+                                this.coversAtLeastRegion(startingBounds)
+                                    .then()
+                                    .coversAtLeastRegion(endingBounds)
+                            }
+                        }
+                    }
+
+                    eventLog {
+                        focusDoesNotChange(bugId = 151179149)
                     }
                 }
             }
-            return params
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
index 3b5e669..ae9fcf9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
@@ -16,25 +16,32 @@
 
 package com.android.server.wm.flicker.splitscreen
 
-import android.view.Surface
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
 import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.exitSplitScreen
 import com.android.server.wm.flicker.helpers.isInSplitScreen
 import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -43,78 +50,79 @@
  * Test open app to split screen.
  * To run this test: `atest FlickerTests:OpenAppToSplitScreenTest`
  */
+@Presubmit
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 161435597)
 class OpenAppToSplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        val testApp = StandardAppHelper(instrumentation,
-        "com.android.server.wm.flicker.testapp", "SimpleApp")
-
-        flicker(instrumentation) {
-            withTag { buildTestTag("appToSplitScreen", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                }
-                eachRun {
-                    testApp.open()
-                    this.setRotation(rotation)
-                }
-            }
-            teardown {
-                eachRun {
-                    if (device.isInSplitScreen()) {
-                        device.exitSplitScreen()
-                    }
-                }
-                test {
-                    testApp.exit()
-                }
-            }
-            transitions {
-                device.launchSplitScreen()
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible(bugId = 140855415)
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation, enabled = false)
-                    navBarLayerRotatesAndScales(rotation, bugId = 140855415)
-                    statusBarLayerRotatesScales(rotation)
-
-                    all("dividerLayerBecomesVisible") {
-                        this.hidesLayer(DOCKED_STACK_DIVIDER)
-                                .then()
-                                .showsLayer(DOCKED_STACK_DIVIDER)
-                    }
-                }
-
-                eventLog {
-                    focusChanges(testApp.`package`,
-                            "recents_animation_input_consumer", "NexusLauncherActivity",
-                            bugId = 151179149)
-                }
-            }
-        }
-    }
-
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = StandardAppHelper(instrumentation,
+                "com.android.server.wm.flicker.testapp", "SimpleApp")
+
+            return FlickerTestRunnerFactory(instrumentation)
+                .buildTest { configuration ->
+                    withTestName {
+                        buildTestTag("appToSplitScreen", testApp, configuration)
+                    }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                        }
+                        eachRun {
+                            testApp.open()
+                            this.setRotation(configuration.endRotation)
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            if (device.isInSplitScreen()) {
+                                device.exitSplitScreen()
+                            }
+                        }
+                        test {
+                            testApp.exit()
+                        }
+                    }
+                    transitions {
+                        device.launchSplitScreen()
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible(bugId = 140855415)
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.endRotation, enabled = false)
+                            navBarLayerRotatesAndScales(configuration.endRotation,
+                                bugId = 140855415)
+                            statusBarLayerRotatesScales(configuration.endRotation)
+
+                            all("dividerLayerBecomesVisible") {
+                                this.hidesLayer(DOCKED_STACK_DIVIDER)
+                                    .then()
+                                    .showsLayer(DOCKED_STACK_DIVIDER)
+                            }
+                        }
+
+                        eventLog {
+                            focusChanges(testApp.`package`,
+                                "recents_animation_input_consumer", "NexusLauncherActivity",
+                                bugId = 151179149)
+                        }
+                    }
+                }
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
index abf41a1..4b9f024 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
@@ -19,32 +19,39 @@
 import android.graphics.Region
 import android.util.Rational
 import android.view.Surface
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
-import com.android.server.wm.flicker.FlickerTestBase
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
 import com.android.server.wm.flicker.focusDoesNotChange
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.exitSplitScreen
 import com.android.server.wm.flicker.helpers.isInSplitScreen
 import com.android.server.wm.flicker.helpers.launchSplitScreen
 import com.android.server.wm.flicker.helpers.resizeSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
 
 /**
  * Test split screen resizing window transitions.
@@ -53,138 +60,149 @@
  * Currently it runs only in 0 degrees because of b/156100803
  */
 @RequiresDevice
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 159096424)
-class ResizeSplitScreenTest : FlickerTestBase() {
-    @Test
-    fun test() {
-        val testAppTop = StandardAppHelper(instrumentation,
-                "com.android.server.wm.flicker.testapp", "SimpleApp")
-        val testAppBottom = ImeAppHelper(instrumentation)
-
-        flicker(instrumentation) {
-            withTag {
-                val description = (startRatio.toString().replace("/", "-") + "_to_" +
-                        stopRatio.toString().replace("/", "-"))
-                buildTestTag("resizeSplitScreen", testAppTop, rotation,
-                        rotation, testAppBottom, description)
-            }
-            repeat { 1 }
-            setup {
-                eachRun {
-                    device.wakeUpAndGoToHomeScreen()
-                    this.setRotation(rotation)
-                    this.launcherStrategy.clearRecentAppsFromOverview()
-                    testAppBottom.open()
-                    device.pressHome()
-                    testAppTop.open()
-                    device.waitForIdle()
-                    device.launchSplitScreen()
-                    val snapshot = device.findObject(By.res(device.launcherPackageName, "snapshot"))
-                    snapshot.click()
-                    testAppBottom.openIME(device)
-                    device.pressBack()
-                    device.resizeSplitScreen(startRatio)
-                }
-            }
-            teardown {
-                eachRun {
-                    device.exitSplitScreen()
-                    device.pressHome()
-                    testAppTop.exit()
-                    testAppBottom.exit()
-                }
-                test {
-                    if (device.isInSplitScreen()) {
-                        device.exitSplitScreen()
-                    }
-                }
-            }
-            transitions {
-                device.resizeSplitScreen(stopRatio)
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-
-                    all("topAppWindowIsAlwaysVisible", bugId = 156223549) {
-                        this.showsAppWindow(sSimpleActivity)
-                    }
-
-                    all("bottomAppWindowIsAlwaysVisible", bugId = 156223549) {
-                        this.showsAppWindow(sImeActivity)
-                    }
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
-                    navBarLayerRotatesAndScales(rotation)
-                    statusBarLayerRotatesScales(rotation)
-
-                    all("topAppLayerIsAlwaysVisible") {
-                        this.showsLayer(sSimpleActivity)
-                    }
-
-                    all("bottomAppLayerIsAlwaysVisible") {
-                        this.showsLayer(sImeActivity)
-                    }
-
-                    all("dividerLayerIsAlwaysVisible") {
-                        this.showsLayer(DOCKED_STACK_DIVIDER)
-                    }
-
-                    start("appsStartingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.displayBounds
-                        val entry = this.trace.entries.firstOrNull()
-                                ?: throw IllegalStateException("Trace is empty")
-                        val dividerBounds = entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
-
-                        val topAppBounds = Region(0, 0, dividerBounds.right,
-                                dividerBounds.top + WindowUtils.dockedStackDividerInset)
-                        val bottomAppBounds = Region(0,
-                                dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
-                                displayBounds.right,
-                                displayBounds.bottom - WindowUtils.navigationBarHeight)
-                        this.hasVisibleRegion("SimpleActivity", topAppBounds)
-                                .and()
-                                .hasVisibleRegion("ImeActivity", bottomAppBounds)
-                    }
-
-                    end("appsEndingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.displayBounds
-                        val entry = this.trace.entries.lastOrNull()
-                                ?: throw IllegalStateException("Trace is empty")
-                        val dividerBounds = entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
-
-                        val topAppBounds = Region(0, 0, dividerBounds.right,
-                                dividerBounds.top + WindowUtils.dockedStackDividerInset)
-                        val bottomAppBounds = Region(0,
-                                dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
-                                displayBounds.right,
-                                displayBounds.bottom - WindowUtils.navigationBarHeight)
-
-                        this.hasVisibleRegion(sSimpleActivity, topAppBounds)
-                                .and()
-                                .hasVisibleRegion(sImeActivity, bottomAppBounds)
-                    }
-                }
-
-                eventLog {
-                    focusDoesNotChange()
-                }
-            }
-        }
-    }
-
+class ResizeSplitScreenTest(
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
     companion object {
         private const val sSimpleActivity = "SimpleActivity"
         private const val sImeActivity = "ImeActivity"
-        private val rotation = Surface.ROTATION_0
         private val startRatio = Rational(1, 3)
         private val stopRatio = Rational(2, 3)
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testAppTop = StandardAppHelper(instrumentation,
+                "com.android.server.wm.flicker.testapp", "SimpleApp")
+            val testAppBottom = ImeAppHelper(instrumentation)
+
+            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+                .buildTest { configuration ->
+                    withTestName {
+                        val description = (startRatio.toString().replace("/", "-") + "_to_" +
+                            stopRatio.toString().replace("/", "-"))
+                        buildTestTag("resizeSplitScreen", testAppTop.launcherName,
+                            configuration.startRotation, configuration.endRotation,
+                            testAppBottom.launcherName, description)
+                    }
+                    repeat { configuration.repetitions }
+                    setup {
+                        eachRun {
+                            device.wakeUpAndGoToHomeScreen()
+                            this.setRotation(configuration.startRotation)
+                            this.launcherStrategy.clearRecentAppsFromOverview()
+                            testAppBottom.open()
+                            device.pressHome()
+                            testAppTop.open()
+                            device.waitForIdle()
+                            device.launchSplitScreen()
+                            val snapshot =
+                                device.findObject(By.res(device.launcherPackageName, "snapshot"))
+                            snapshot.click()
+                            testAppBottom.openIME(device)
+                            device.pressBack()
+                            device.resizeSplitScreen(startRatio)
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            if (device.isInSplitScreen()) {
+                                device.exitSplitScreen()
+                            }
+                            device.pressHome()
+                            testAppTop.exit()
+                            testAppBottom.exit()
+                        }
+                        test {
+                            if (device.isInSplitScreen()) {
+                                device.exitSplitScreen()
+                            }
+                        }
+                    }
+                    transitions {
+                        device.resizeSplitScreen(stopRatio)
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+
+                            all("topAppWindowIsAlwaysVisible", bugId = 156223549) {
+                                this.showsAppWindow(sSimpleActivity)
+                            }
+
+                            all("bottomAppWindowIsAlwaysVisible", bugId = 156223549) {
+                                this.showsAppWindow(sImeActivity)
+                            }
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible()
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.endRotation)
+                            navBarLayerRotatesAndScales(configuration.endRotation)
+                            statusBarLayerRotatesScales(configuration.endRotation)
+
+                            all("topAppLayerIsAlwaysVisible") {
+                                this.showsLayer(sSimpleActivity)
+                            }
+
+                            all("bottomAppLayerIsAlwaysVisible") {
+                                this.showsLayer(sImeActivity)
+                            }
+
+                            all("dividerLayerIsAlwaysVisible") {
+                                this.showsLayer(DOCKED_STACK_DIVIDER)
+                            }
+
+                            start("appsStartingBounds", enabled = false) {
+                                val displayBounds = WindowUtils.displayBounds
+                                val entry = this.trace.entries.firstOrNull()
+                                    ?: throw IllegalStateException("Trace is empty")
+                                val dividerBounds =
+                                    entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+
+                                val topAppBounds = Region(0, 0, dividerBounds.right,
+                                    dividerBounds.top + WindowUtils.dockedStackDividerInset)
+                                val bottomAppBounds = Region(0,
+                                    dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
+                                    displayBounds.right,
+                                    displayBounds.bottom - WindowUtils.navigationBarHeight)
+                                this.hasVisibleRegion("SimpleActivity", topAppBounds)
+                                    .and()
+                                    .hasVisibleRegion("ImeActivity", bottomAppBounds)
+                            }
+
+                            end("appsEndingBounds", enabled = false) {
+                                val displayBounds = WindowUtils.displayBounds
+                                val entry = this.trace.entries.lastOrNull()
+                                    ?: throw IllegalStateException("Trace is empty")
+                                val dividerBounds =
+                                    entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+
+                                val topAppBounds = Region(0, 0, dividerBounds.right,
+                                    dividerBounds.top + WindowUtils.dockedStackDividerInset)
+                                val bottomAppBounds = Region(0,
+                                    dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
+                                    displayBounds.right,
+                                    displayBounds.bottom - WindowUtils.navigationBarHeight)
+
+                                this.hasVisibleRegion(sSimpleActivity, topAppBounds)
+                                    .and()
+                                    .hasVisibleRegion(sImeActivity, bottomAppBounds)
+                            }
+                        }
+
+                        eventLog {
+                            focusDoesNotChange()
+                        }
+                    }
+                }
+        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
index 7447bda..f966a66 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
@@ -19,23 +19,29 @@
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
+import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.helpers.exitSplitScreen
 import com.android.server.wm.flicker.helpers.isInSplitScreen
 import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
 import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -49,82 +55,80 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class SplitScreenToLauncherTest(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    @Test
-    fun test() {
-        val testApp = StandardAppHelper(instrumentation,
-                "com.android.server.wm.flicker.testapp", "SimpleApp")
-
-        flicker(instrumentation) {
-            withTag { buildTestTag("splitScreenToLauncher", testApp, rotation) }
-            repeat { 1 }
-            setup {
-                test {
-                    device.wakeUpAndGoToHomeScreen()
-                }
-                eachRun {
-                    testApp.open()
-                    this.setRotation(rotation)
-                    device.launchSplitScreen()
-                    device.waitForIdle()
-                }
-            }
-            teardown {
-                eachRun {
-                    testApp.exit()
-                }
-                test {
-                    if (device.isInSplitScreen()) {
-                        device.exitSplitScreen()
-                    }
-                }
-            }
-            transitions {
-                device.exitSplitScreen()
-            }
-            assertions {
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation)
-                    navBarLayerRotatesAndScales(rotation)
-                    statusBarLayerRotatesScales(rotation)
-
-                    // b/161435597 causes the test not to work on 90 degrees
-                    all("dividerLayerBecomesInvisible") {
-                        this.showsLayer(DOCKED_STACK_DIVIDER)
-                                .then()
-                                .hidesLayer(DOCKED_STACK_DIVIDER)
-                    }
-
-                    all("appLayerBecomesInvisible") {
-                        this.showsLayer(testApp.getPackage())
-                            .then()
-                            .hidesLayer(testApp.getPackage())
-                    }
-                }
-
-                eventLog {
-                    focusDoesNotChange(bugId = 151179149)
-                }
-            }
-        }
-    }
-
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = StandardAppHelper(instrumentation,
+                "com.android.server.wm.flicker.testapp", "SimpleApp")
+
             // b/161435597 causes the test not to work on 90 degrees
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+                .buildTest { configuration ->
+                    withTestName {
+                        buildTestTag("splitScreenToLauncher", testApp, configuration)
+                    }
+                    repeat { configuration.repetitions }
+                    setup {
+                        test {
+                            device.wakeUpAndGoToHomeScreen()
+                        }
+                        eachRun {
+                            testApp.open()
+                            this.setRotation(configuration.endRotation)
+                            device.launchSplitScreen()
+                            device.waitForIdle()
+                        }
+                    }
+                    teardown {
+                        eachRun {
+                            testApp.exit()
+                        }
+                        test {
+                            if (device.isInSplitScreen()) {
+                                device.exitSplitScreen()
+                            }
+                        }
+                    }
+                    transitions {
+                        device.exitSplitScreen()
+                    }
+                    assertions {
+                        windowManagerTrace {
+                            navBarWindowIsAlwaysVisible()
+                            statusBarWindowIsAlwaysVisible()
+                        }
+
+                        layersTrace {
+                            navBarLayerIsAlwaysVisible()
+                            statusBarLayerIsAlwaysVisible()
+                            noUncoveredRegions(configuration.endRotation)
+                            navBarLayerRotatesAndScales(configuration.endRotation)
+                            statusBarLayerRotatesScales(configuration.endRotation)
+
+                            // b/161435597 causes the test not to work on 90 degrees
+                            all("dividerLayerBecomesInvisible") {
+                                this.showsLayer(DOCKED_STACK_DIVIDER)
+                                    .then()
+                                    .hidesLayer(DOCKED_STACK_DIVIDER)
+                            }
+
+                            all("appLayerBecomesInvisible") {
+                                this.showsLayer(testApp.getPackage())
+                                    .then()
+                                    .hidesLayer(testApp.getPackage())
+                            }
+                        }
+
+                        eventLog {
+                            focusDoesNotChange(bugId = 151179149)
+                        }
+                    }
+                }
         }
     }
 }
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 226d2d5..6662b0e 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -25,6 +27,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.IFileEntry;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -51,6 +54,7 @@
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class StagedRollbackTest extends BaseHostJUnit4Test {
+    private static final String TAG = "StagedRollbackTest";
     private static final int NATIVE_CRASHES_THRESHOLD = 5;
 
     /**
@@ -272,6 +276,8 @@
         List<String> after = getSnapshotDirectories("/data/misc_ce/0/rollback");
         // Only check directories newly created during the test
         after.removeAll(before);
+        // There should be only one /data/misc_ce/0/rollback/<rollbackId> created during test
+        assertThat(after).hasSize(1);
         after.forEach(dir -> assertDirectoryIsEmpty(dir));
     }
 
@@ -358,6 +364,8 @@
         List<String> after = getSnapshotDirectories("/data/misc/apexrollback");
         // Only check directories newly created during the test
         after.removeAll(before);
+        // There should be only one /data/misc/apexrollback/<rollbackId> created during test
+        assertThat(after).hasSize(1);
         after.forEach(dir -> assertDirectoryIsEmpty(dir));
     }
 
@@ -405,6 +413,8 @@
         List<String> after = getSnapshotDirectories("/data/misc_de/0/apexrollback");
         // Only check directories newly created during the test
         after.removeAll(before);
+        // There should be only one /data/misc_de/0/apexrollback/<rollbackId> created during test
+        assertThat(after).hasSize(1);
         after.forEach(dir -> assertDirectoryIsEmpty(dir));
     }
 
@@ -450,6 +460,8 @@
         List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
         // Only check directories newly created during the test
         after.removeAll(before);
+        // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test
+        assertThat(after).hasSize(1);
         after.forEach(dir -> assertDirectoryIsEmpty(dir));
     }
 
@@ -509,6 +521,8 @@
         List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
         // Only check directories newly created during the test
         after.removeAll(before);
+        // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test
+        assertThat(after).hasSize(1);
         // Expire all rollbacks and check CE snapshot directories are deleted
         runPhase("testCleanUp");
         for (String dir : after) {
@@ -544,16 +558,18 @@
         return String.format("/data/user_de/%d/%s", userId, apexName);
     }
 
-    private List<String> getSnapshotDirectories(String baseDir) {
-        try {
-            return getDevice().getFileEntry(baseDir).getChildren(false)
-                    .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?"))
-                    .map(entry -> entry.getFullPath())
-                    .collect(Collectors.toList());
-        } catch (Exception e) {
-            // Return an empty list if any error
+    private List<String> getSnapshotDirectories(String baseDir) throws Exception {
+        IFileEntry f = getDevice().getFileEntry(baseDir);
+        if (f == null) {
+            Log.d(TAG, "baseDir doesn't exist: " + baseDir);
             return Collections.EMPTY_LIST;
         }
+        List<String> list = f.getChildren(false)
+                .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?"))
+                .map(entry -> entry.getFullPath())
+                .collect(Collectors.toList());
+        Log.d(TAG, "getSnapshotDirectories=" + list);
+        return list;
     }
 
     private void assertDirectoryIsEmpty(String path) {
diff --git a/tests/SilkFX/AndroidManifest.xml b/tests/SilkFX/AndroidManifest.xml
index 050e9c3..c30d761 100644
--- a/tests/SilkFX/AndroidManifest.xml
+++ b/tests/SilkFX/AndroidManifest.xml
@@ -4,9 +4,9 @@
      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.
@@ -40,7 +40,14 @@
             android:label="Glow Examples"/>
 
         <activity android:name=".materials.GlassActivity"
-            android:label="Glass Examples"/>
+            android:label="Glass Examples"
+            android:banner="@drawable/background1"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+            </intent-filter>
+        </activity>
 
     </application>
 </manifest>
diff --git a/tests/SilkFX/res/layout-television/activity_glass.xml b/tests/SilkFX/res/layout-television/activity_glass.xml
new file mode 100644
index 0000000..1f566860
--- /dev/null
+++ b/tests/SilkFX/res/layout-television/activity_glass.xml
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** 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.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:scaleType="matrix"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:srcCompat="@drawable/background1" />
+
+    <com.android.test.silkfx.materials.GlassView
+        android:id="@+id/materialView"
+        android:layout_width="400dp"
+        android:layout_height="100dp"
+        android:layout_marginEnd="64dp"
+        android:layout_marginStart="64dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="@id/background"
+        app:layout_constraintStart_toStartOf="@id/background"
+        app:layout_constraintTop_toTopOf="parent">
+        <TextView
+            android:id="@+id/textOverlay"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18dp"
+            android:layout_gravity="center"
+            android:textColor="#ffffff"
+            android:text="Lorem Ipsum dolor sit amet." />
+    </com.android.test.silkfx.materials.GlassView>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/bottomPanel"
+        android:layout_width="400dp"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/colorBackground"
+        android:paddingTop="24dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+    <SeekBar
+        android:id="@+id/materialOpacity"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="12dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginBottom="16dp"
+        android:max="100"
+        android:progress="12"
+        app:layout_constraintBottom_toTopOf="@+id/scrimOpacityTitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <SeekBar
+        android:id="@+id/zoom"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginStart="12dp"
+        android:min="-100"
+        android:max="100"
+        android:progress="-15"
+        app:layout_constraintBottom_toTopOf="@+id/blurRadiusTitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <SeekBar
+        android:id="@+id/blurRadius"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginStart="12dp"
+        android:max="150"
+        android:progress="40"
+        app:layout_constraintBottom_toTopOf="@+id/materialOpacityTitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <SeekBar
+        android:id="@+id/scrimOpacity"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="12dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginBottom="16dp"
+        android:max="100"
+        android:progress="50"
+        app:layout_constraintBottom_toTopOf="@+id/noiseOpacityTitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="1.0"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <SeekBar
+        android:id="@+id/noiseOpacity"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="12dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginBottom="24dp"
+        android:max="100"
+        android:progress="15"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.0"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <TextView
+        android:id="@+id/scrimOpacityTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="8dp"
+        android:text="Scrim Opacity"
+        android:textColor="@android:color/white"
+        app:layout_constraintBottom_toTopOf="@+id/scrimOpacity"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <TextView
+        android:id="@+id/materialOpacityTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="8dp"
+        android:text="Soft light Opacity"
+        android:textColor="@android:color/white"
+        app:layout_constraintBottom_toTopOf="@+id/materialOpacity"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <TextView
+        android:id="@+id/zoomTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="8dp"
+        android:text="Zoom"
+        android:textColor="@android:color/white"
+        app:layout_constraintBottom_toTopOf="@+id/zoom"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <TextView
+        android:id="@+id/blurRadiusTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="8dp"
+        android:text="Blur Radius"
+        android:textColor="@android:color/white"
+        app:layout_constraintBottom_toTopOf="@+id/blurRadius"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <TextView
+        android:id="@+id/noiseOpacityTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="8dp"
+        android:textColor="@android:color/white"
+        android:text="Noise Opacity"
+        app:layout_constraintBottom_toTopOf="@+id/noiseOpacity"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <ImageView
+        android:id="@+id/background1"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="16dp"
+        android:foreground="?android:attr/selectableItemBackgroundBorderless"
+        android:clickable="true"
+        android:onClick="onBackgroundClick"
+        android:scaleType="centerCrop"
+        app:layout_constraintBottom_toTopOf="@+id/lightMaterialSwitch"
+        app:layout_constraintStart_toStartOf="parent"
+        android:src="@drawable/background1" />
+
+    <ImageView
+        android:id="@+id/background2"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_marginStart="8dp"
+        android:foreground="?android:attr/selectableItemBackgroundBorderless"
+        android:clickable="true"
+        android:onClick="onBackgroundClick"
+        android:scaleType="centerCrop"
+        app:layout_constraintBottom_toBottomOf="@+id/background1"
+        app:layout_constraintStart_toEndOf="@+id/background1"
+        android:src="@drawable/background2" />
+
+    <ImageView
+        android:id="@+id/background3"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_marginStart="8dp"
+        android:scaleType="centerCrop"
+        android:foreground="?android:attr/selectableItemBackgroundBorderless"
+        android:clickable="true"
+        android:onClick="onBackgroundClick"
+        app:layout_constraintBottom_toBottomOf="@+id/background1"
+        app:layout_constraintStart_toEndOf="@+id/background2"
+        android:src="@drawable/background3" />
+
+    <Button
+        android:id="@+id/pickImage"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_marginStart="8dp"
+        android:scaleType="centerCrop"
+        android:foreground="?android:attr/selectableItemBackgroundBorderless"
+        android:clickable="true"
+        android:onClick="onPickImageClick"
+        app:layout_constraintBottom_toBottomOf="@+id/background1"
+        app:layout_constraintStart_toEndOf="@+id/background3"
+        android:text="Pick file" />
+
+    <Switch
+        android:id="@+id/lightMaterialSwitch"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="8dp"
+        android:text="Light Material"
+        app:layout_constraintBottom_toTopOf="@+id/zoomTitle"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <TextView
+        android:id="@+id/blurRadiusValue"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="TextView"
+        android:layout_marginLeft="8dp"
+        app:layout_constraintBottom_toBottomOf="@+id/blurRadiusTitle"
+        app:layout_constraintStart_toEndOf="@+id/blurRadiusTitle" />
+
+    <TextView
+        android:id="@+id/zoomValue"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="TextView"
+        android:layout_marginLeft="8dp"
+        app:layout_constraintBottom_toBottomOf="@+id/zoomTitle"
+        app:layout_constraintStart_toEndOf="@+id/zoomTitle" />
+
+    <TextView
+        android:id="@+id/materialOpacityValue"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="TextView"
+        android:layout_marginLeft="8dp"
+        app:layout_constraintBottom_toBottomOf="@+id/materialOpacityTitle"
+        app:layout_constraintStart_toEndOf="@+id/materialOpacityTitle" />
+
+    <TextView
+        android:id="@+id/noiseOpacityValue"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="TextView"
+        android:layout_marginLeft="8dp"
+        app:layout_constraintBottom_toBottomOf="@+id/noiseOpacityTitle"
+        app:layout_constraintStart_toEndOf="@+id/noiseOpacityTitle" />
+
+
+    <TextView
+        android:id="@+id/scrimOpacityValue"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="TextView"
+        android:layout_marginLeft="8dp"
+        app:layout_constraintBottom_toBottomOf="@+id/scrimOpacityTitle"
+        app:layout_constraintStart_toEndOf="@+id/scrimOpacityTitle" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 7ab4d97..bc3db11 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -1155,7 +1155,7 @@
                     new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret,
                             "name", profile.username, "password", profile.password,
                             "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
-                            "idle", "1800", "mtu", "1400", "mru", "1400" },
+                            "idle", "1800", "mtu", "1270", "mru", "1270" },
                     deps.mtpdArgs.get(10, TimeUnit.SECONDS));
             // Now wait for the runner to be ready before testing for the route.
             legacyRunnerReady.block(10_000);
diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 8f09377..6d2c7dc 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -150,7 +151,7 @@
     }
 
     private void assertRatTypeChangedForSub(String subscriberId, int ratType) {
-        assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType);
+        assertEquals(ratType, mMonitor.getRatTypeForSubscriberId(subscriberId));
         final ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class);
         // Verify callback with the subscriberId and the RAT type should be as expected.
         // It will fail if get a callback with an unexpected RAT type.
@@ -302,26 +303,84 @@
         reset(mDelegate);
 
         // Set IMSI to null again to simulate somehow IMSI is not available, such as
-        // modem crash. Verify service should not unregister listener.
+        // modem crash. Verify service should unregister listener.
         updateSubscriberIdForTestSub(TEST_SUBID1, null);
-        verify(mTelephonyManager, never()).listen(any(), anyInt());
-        assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
+        verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
+                eq(PhoneStateListener.LISTEN_NONE));
+        assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
         reset(mDelegate);
+        clearInvocations(mTelephonyManager);
 
-        // Set RAT type of sim1 to LTE. Verify RAT type of sim1 is still changed even if the IMSI
-        // is not available. The monitor keeps the listener even if the IMSI disappears because
-        // the IMSI can never change for any given subId, therefore even if the IMSI is updated
-        // to null, the monitor should continue accepting updates of the RAT type. However,
-        // telephony is never actually supposed to do this, if the IMSI disappears there should
-        // not be updates, but it's still the right thing to do theoretically.
-        setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
+        // Simulate somehow IMSI is back. Verify service will register with
+        // another listener and fire callback accordingly.
+        final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
+                ArgumentCaptor.forClass(RatTypeListener.class);
+        updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1);
+        verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
+                eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+        assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+        reset(mDelegate);
+        clearInvocations(mTelephonyManager);
+
+        // Set RAT type of sim1 to LTE. Verify RAT type of sim1 still works.
+        setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
                 TelephonyManager.NETWORK_TYPE_LTE);
         assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
         reset(mDelegate);
 
         mMonitor.stop();
+        verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor2.getValue()),
+                eq(PhoneStateListener.LISTEN_NONE));
+        assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+    }
+
+    /**
+     * Verify that when IMSI suddenly changed for a given subId, the service will register a new
+     * listener and unregister the old one, and report changes on updated IMSI. This is for modem
+     * feature that may be enabled for certain carrier, which changes to use a different IMSI while
+     * roaming on certain networks for multi-IMSI SIM cards, but the subId stays the same.
+     */
+    @Test
+    public void testSubscriberIdChanged() {
+        mMonitor.start();
+        // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
+        // before changing RAT type.
+        addTestSub(TEST_SUBID1, TEST_IMSI1);
+        final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
+                ArgumentCaptor.forClass(RatTypeListener.class);
+        verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
+                eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+        assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+
+        // Set RAT type of sim1 to UMTS.
+        // Verify RAT type of sim1 changes accordingly.
+        setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
+                TelephonyManager.NETWORK_TYPE_UMTS);
+        assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
+        reset(mDelegate);
+        clearInvocations(mTelephonyManager);
+
+        // Simulate IMSI of sim1 changed to IMSI2. Verify the service will register with
+        // another listener and remove the old one. The RAT type of new IMSI stays at
+        // NETWORK_TYPE_UNKNOWN until received initial callback from telephony.
+        final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
+                ArgumentCaptor.forClass(RatTypeListener.class);
+        updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI2);
+        verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
+                eq(PhoneStateListener.LISTEN_SERVICE_STATE));
         verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
                 eq(PhoneStateListener.LISTEN_NONE));
         assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+        assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+        reset(mDelegate);
+
+        // Set RAT type of sim1 to UMTS for new listener to simulate the initial callback received
+        // from telephony after registration. Verify RAT type of sim1 changes with IMSI2
+        // accordingly.
+        setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
+                TelephonyManager.NETWORK_TYPE_UMTS);
+        assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+        assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UMTS);
+        reset(mDelegate);
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 7d7d2ba..7877ea1 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -87,12 +87,12 @@
         }
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
-        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isNotSameInstanceAs(original);
         assertThat(unparceled).isEqualTo(original);
         assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
 
         SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
-        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isNotSameInstanceAs(original);
         assertThat(copy).isEqualTo(original);
         assertThat(copy.hashCode()).isEqualTo(original.hashCode());
     }
@@ -111,12 +111,12 @@
         assertThat(original.getMaxNumberOfClients()).isEqualTo(0);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
-        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isNotSameInstanceAs(original);
         assertThat(unparceled).isEqualTo(original);
         assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
 
         SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
-        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isNotSameInstanceAs(original);
         assertThat(copy).isEqualTo(original);
         assertThat(copy.hashCode()).isEqualTo(original.hashCode());
     }
@@ -159,12 +159,12 @@
         }
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
-        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isNotSameInstanceAs(original);
         assertThat(unparceled).isEqualTo(original);
         assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
 
         SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
-        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isNotSameInstanceAs(original);
         assertThat(copy).isEqualTo(original);
         assertThat(copy.hashCode()).isEqualTo(original.hashCode());
     }
@@ -185,12 +185,12 @@
 
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
-        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isNotSameInstanceAs(original);
         assertThat(unparceled).isEqualTo(original);
         assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
 
         SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
-        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isNotSameInstanceAs(original);
         assertThat(copy).isEqualTo(original);
         assertThat(copy.hashCode()).isEqualTo(original.hashCode());
     }
@@ -212,12 +212,12 @@
 
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
-        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isNotSameInstanceAs(original);
         assertThat(unparceled).isEqualTo(original);
         assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
 
         SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
-        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isNotSameInstanceAs(original);
         assertThat(copy).isEqualTo(original);
         assertThat(copy.hashCode()).isEqualTo(original.hashCode());
     }