Merge "Fixes some DPM APIs to use the context user."
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index c706a3a..8237383 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -25,6 +25,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
@@ -127,28 +128,47 @@
    <pre>
 
    digraph {
+     subgraph cluster_legend {
+       label="Legend"
+
+       wakeup_alarm [label="Entering this state requires a wakeup alarm",color=red,shape=box]
+       nonwakeup_alarm [
+         label="This state can be entered from a non-wakeup alarm",color=blue,shape=oval
+       ]
+       no_alarm [label="This state doesn't require an alarm",color=black,shape=diamond]
+     }
+
      subgraph deep {
        label="deep";
 
-       STATE_ACTIVE [label="STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon"]
-       STATE_INACTIVE [label="STATE_INACTIVE\nScreen off AND Not charging"]
+       STATE_ACTIVE [
+         label="STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon",
+         color=black,shape=diamond
+       ]
+       STATE_INACTIVE [
+         label="STATE_INACTIVE\nScreen off AND Not charging",color=black,shape=diamond
+       ]
        STATE_QUICK_DOZE_DELAY [
          label="STATE_QUICK_DOZE_DELAY\n"
              + "Screen off AND Not charging\n"
-             + "Location, motion detection, and significant motion monitoring turned off"
+             + "Location, motion detection, and significant motion monitoring turned off",
+         color=black,shape=diamond
        ]
        STATE_IDLE_PENDING [
-         label="STATE_IDLE_PENDING\nSignificant motion monitoring turned on"
+         label="STATE_IDLE_PENDING\nSignificant motion monitoring turned on",
+         color=red,shape=box
        ]
-       STATE_SENSING [label="STATE_SENSING\nMonitoring for ANY motion"]
+       STATE_SENSING [label="STATE_SENSING\nMonitoring for ANY motion",color=red,shape=box]
        STATE_LOCATING [
-         label="STATE_LOCATING\nRequesting location, motion monitoring still on"
+         label="STATE_LOCATING\nRequesting location, motion monitoring still on",
+         color=red,shape=box
        ]
        STATE_IDLE [
          label="STATE_IDLE\nLocation and motion detection turned off\n"
-             + "Significant motion monitoring state unchanged"
+             + "Significant motion monitoring state unchanged",
+         color=red,shape=box
        ]
-       STATE_IDLE_MAINTENANCE [label="STATE_IDLE_MAINTENANCE\n"]
+       STATE_IDLE_MAINTENANCE [label="STATE_IDLE_MAINTENANCE\n",color=red,shape=box]
 
        STATE_ACTIVE -> STATE_INACTIVE [
          label="becomeInactiveIfAppropriateLocked() AND Quick Doze not enabled"
@@ -213,19 +233,22 @@
        label="light"
 
        LIGHT_STATE_ACTIVE [
-         label="LIGHT_STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon"
+         label="LIGHT_STATE_ACTIVE\nScreen on OR Charging OR Alarm going off soon",
+         color=black,shape=diamond
        ]
-       LIGHT_STATE_INACTIVE [label="LIGHT_STATE_INACTIVE\nScreen off AND Not charging"]
-       LIGHT_STATE_PRE_IDLE [
-         label="LIGHT_STATE_PRE_IDLE\n"
-             + "Delay going into LIGHT_STATE_IDLE due to some running jobs or alarms"
+       LIGHT_STATE_INACTIVE [
+         label="LIGHT_STATE_INACTIVE\nScreen off AND Not charging",
+         color=black,shape=diamond
        ]
-       LIGHT_STATE_IDLE [label="LIGHT_STATE_IDLE\n"]
+       LIGHT_STATE_IDLE [label="LIGHT_STATE_IDLE\n",color=blue,shape=oval]
        LIGHT_STATE_WAITING_FOR_NETWORK [
          label="LIGHT_STATE_WAITING_FOR_NETWORK\n"
-             + "Coming out of LIGHT_STATE_IDLE, waiting for network"
+             + "Coming out of LIGHT_STATE_IDLE, waiting for network",
+         color=black,shape=diamond
        ]
-       LIGHT_STATE_IDLE_MAINTENANCE [label="LIGHT_STATE_IDLE_MAINTENANCE\n"]
+       LIGHT_STATE_IDLE_MAINTENANCE [
+         label="LIGHT_STATE_IDLE_MAINTENANCE\n",color=red,shape=box
+       ]
        LIGHT_STATE_OVERRIDE [
          label="LIGHT_STATE_OVERRIDE\nDevice in deep doze, light no longer changing states"
        ]
@@ -236,16 +259,9 @@
        LIGHT_STATE_ACTIVE -> LIGHT_STATE_OVERRIDE [label="deep goes to STATE_IDLE"]
 
        LIGHT_STATE_INACTIVE -> LIGHT_STATE_ACTIVE [label="becomeActiveLocked()"]
-       LIGHT_STATE_INACTIVE -> LIGHT_STATE_PRE_IDLE [label="active jobs"]
-       LIGHT_STATE_INACTIVE -> LIGHT_STATE_IDLE [label="no active jobs"]
+       LIGHT_STATE_INACTIVE -> LIGHT_STATE_IDLE [label="some time transpires"]
        LIGHT_STATE_INACTIVE -> LIGHT_STATE_OVERRIDE [label="deep goes to STATE_IDLE"]
 
-       LIGHT_STATE_PRE_IDLE -> LIGHT_STATE_ACTIVE [label="becomeActiveLocked()"]
-       LIGHT_STATE_PRE_IDLE -> LIGHT_STATE_IDLE [
-         label="stepLightIdleStateLocked(), exitMaintenanceEarlyIfNeededLocked()"
-       ]
-       LIGHT_STATE_PRE_IDLE -> LIGHT_STATE_OVERRIDE [label="deep goes to STATE_IDLE"]
-
        LIGHT_STATE_IDLE -> LIGHT_STATE_ACTIVE [label="becomeActiveLocked()"]
        LIGHT_STATE_IDLE -> LIGHT_STATE_WAITING_FOR_NETWORK [label="no network"]
        LIGHT_STATE_IDLE -> LIGHT_STATE_IDLE_MAINTENANCE
@@ -421,9 +437,6 @@
     /** Device is inactive (screen off) and we are waiting to for the first light idle. */
     @VisibleForTesting
     static final int LIGHT_STATE_INACTIVE = 1;
-    /** Device is about to go idle for the first time, wait for current work to complete. */
-    @VisibleForTesting
-    static final int LIGHT_STATE_PRE_IDLE = 3;
     /** Device is in the light idle state, trying to stay asleep as much as possible. */
     @VisibleForTesting
     static final int LIGHT_STATE_IDLE = 4;
@@ -434,7 +447,7 @@
     /** Device is in the light idle state, but temporarily out of idle to do regular maintenance. */
     @VisibleForTesting
     static final int LIGHT_STATE_IDLE_MAINTENANCE = 6;
-    /** Device light idle state is overriden, now applying deep doze state. */
+    /** Device light idle state is overridden, now applying deep doze state. */
     @VisibleForTesting
     static final int LIGHT_STATE_OVERRIDE = 7;
 
@@ -443,7 +456,6 @@
         switch (state) {
             case LIGHT_STATE_ACTIVE: return "ACTIVE";
             case LIGHT_STATE_INACTIVE: return "INACTIVE";
-            case LIGHT_STATE_PRE_IDLE: return "PRE_IDLE";
             case LIGHT_STATE_IDLE: return "IDLE";
             case LIGHT_STATE_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK";
             case LIGHT_STATE_IDLE_MAINTENANCE: return "IDLE_MAINTENANCE";
@@ -468,10 +480,10 @@
     @GuardedBy("this")
     private long mNextLightIdleDelay;
     @GuardedBy("this")
-    private long mNextLightIdleDelayFlex;
-    @GuardedBy("this")
     private long mNextLightAlarmTime;
     @GuardedBy("this")
+    private long mNextLightMaintenanceAlarmTime;
+    @GuardedBy("this")
     private long mNextSensingTimeoutAlarmTime;
 
     /** How long a light idle maintenance window should last. */
@@ -658,13 +670,21 @@
         }
     };
 
-    private final AlarmManager.OnAlarmListener mLightAlarmListener
-            = new AlarmManager.OnAlarmListener() {
-        @Override
-        public void onAlarm() {
-            synchronized (DeviceIdleController.this) {
-                stepLightIdleStateLocked("s:alarm");
-            }
+    private final AlarmManager.OnAlarmListener mLightAlarmListener = () -> {
+        if (DEBUG) {
+            Slog.d(TAG, "Light progression alarm fired");
+        }
+        synchronized (DeviceIdleController.this) {
+            stepLightIdleStateLocked("s:alarm");
+        }
+    };
+
+    private final AlarmManager.OnAlarmListener mLightMaintenanceAlarmListener = () -> {
+        if (DEBUG) {
+            Slog.d(TAG, "Light maintenance alarm fired");
+        }
+        synchronized (DeviceIdleController.this) {
+            stepLightIdleStateLocked("s:alarm");
         }
     };
 
@@ -928,11 +948,7 @@
         private static final String KEY_FLEX_TIME_SHORT = "flex_time_short";
         private static final String KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT =
                 "light_after_inactive_to";
-        private static final String KEY_LIGHT_PRE_IDLE_TIMEOUT = "light_pre_idle_to";
         private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to";
-        private static final String KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX =
-                "light_idle_to_initial_flex";
-        private static final String KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX = "light_max_idle_to_flex";
         private static final String KEY_LIGHT_IDLE_FACTOR = "light_idle_factor";
         private static final String KEY_LIGHT_MAX_IDLE_TIMEOUT = "light_max_idle_to";
         private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET =
@@ -978,15 +994,9 @@
         private static final long DEFAULT_FLEX_TIME_SHORT =
                 !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L;
         private static final long DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT =
-                !COMPRESS_TIME ? 60 * 1000L : 15 * 1000L;
-        private static final long DEFAULT_LIGHT_PRE_IDLE_TIMEOUT =
-                !COMPRESS_TIME ? 3 * 60 * 1000L : 30 * 1000L;
+                !COMPRESS_TIME ? 4 * 60 * 1000L : 30 * 1000L;
         private static final long DEFAULT_LIGHT_IDLE_TIMEOUT =
                 !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L;
-        private static final long DEFAULT_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX =
-                !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L;
-        private static final long DEFAULT_LIGHT_MAX_IDLE_TIMEOUT_FLEX =
-                !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L;
         private static final float DEFAULT_LIGHT_IDLE_FACTOR = 2f;
         private static final long DEFAULT_LIGHT_MAX_IDLE_TIMEOUT =
                 !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L;
@@ -1054,15 +1064,6 @@
         public long LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT;
 
         /**
-         * This is amount of time we will wait from the point where we decide we would
-         * like to go idle until we actually do, while waiting for jobs and other current
-         * activity to finish.
-         *
-         * @see #KEY_LIGHT_PRE_IDLE_TIMEOUT
-         */
-        public long LIGHT_PRE_IDLE_TIMEOUT = DEFAULT_LIGHT_PRE_IDLE_TIMEOUT;
-
-        /**
          * This is the initial time that we will run in light idle maintenance mode.
          *
          * @see #KEY_LIGHT_IDLE_TIMEOUT
@@ -1070,21 +1071,6 @@
         public long LIGHT_IDLE_TIMEOUT = DEFAULT_LIGHT_IDLE_TIMEOUT;
 
         /**
-         * This is the initial alarm window size that we will tolerate for light idle maintenance
-         * timing.
-         *
-         * @see #KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX
-         */
-        public long LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = DEFAULT_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX;
-
-        /**
-         * This is the maximum value that {@link #LIGHT_IDLE_TIMEOUT_INITIAL_FLEX} should take.
-         *
-         * @see #KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX
-         */
-        public long LIGHT_MAX_IDLE_TIMEOUT_FLEX = DEFAULT_LIGHT_MAX_IDLE_TIMEOUT_FLEX;
-
-        /**
          * Scaling factor to apply to the light idle mode time each time we complete a cycle.
          *
          * @see #KEY_LIGHT_IDLE_FACTOR
@@ -1327,24 +1313,10 @@
                                     KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
                                     DEFAULT_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
                             break;
-                        case KEY_LIGHT_PRE_IDLE_TIMEOUT:
-                            LIGHT_PRE_IDLE_TIMEOUT = properties.getLong(
-                                    KEY_LIGHT_PRE_IDLE_TIMEOUT, DEFAULT_LIGHT_PRE_IDLE_TIMEOUT);
-                            break;
                         case KEY_LIGHT_IDLE_TIMEOUT:
                             LIGHT_IDLE_TIMEOUT = properties.getLong(
                                     KEY_LIGHT_IDLE_TIMEOUT, DEFAULT_LIGHT_IDLE_TIMEOUT);
                             break;
-                        case KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX:
-                            LIGHT_IDLE_TIMEOUT_INITIAL_FLEX = properties.getLong(
-                                    KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX,
-                                    DEFAULT_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX);
-                            break;
-                        case KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX:
-                            LIGHT_MAX_IDLE_TIMEOUT_FLEX = properties.getLong(
-                                    KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX,
-                                    DEFAULT_LIGHT_MAX_IDLE_TIMEOUT_FLEX);
-                            break;
                         case KEY_LIGHT_IDLE_FACTOR:
                             LIGHT_IDLE_FACTOR = Math.max(1, properties.getFloat(
                                     KEY_LIGHT_IDLE_FACTOR, DEFAULT_LIGHT_IDLE_FACTOR));
@@ -1497,22 +1469,10 @@
             TimeUtils.formatDuration(LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_LIGHT_PRE_IDLE_TIMEOUT); pw.print("=");
-            TimeUtils.formatDuration(LIGHT_PRE_IDLE_TIMEOUT, pw);
-            pw.println();
-
             pw.print("    "); pw.print(KEY_LIGHT_IDLE_TIMEOUT); pw.print("=");
             TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_LIGHT_IDLE_TIMEOUT_INITIAL_FLEX); pw.print("=");
-            TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT_INITIAL_FLEX, pw);
-            pw.println();
-
-            pw.print("    "); pw.print(KEY_LIGHT_MAX_IDLE_TIMEOUT_FLEX); pw.print("=");
-            TimeUtils.formatDuration(LIGHT_MAX_IDLE_TIMEOUT_FLEX, pw);
-            pw.println();
-
             pw.print("    "); pw.print(KEY_LIGHT_IDLE_FACTOR); pw.print("=");
             pw.print(LIGHT_IDLE_FACTOR);
             pw.println();
@@ -3088,7 +3048,7 @@
             if (conn != mNetworkConnected) {
                 mNetworkConnected = conn;
                 if (conn && mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
-                    stepLightIdleStateLocked("network");
+                    stepLightIdleStateLocked("network", /* forceProgression */ true);
                 }
             }
         }
@@ -3343,7 +3303,11 @@
             if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
             resetLightIdleManagementLocked();
             scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
-                    mConstants.FLEX_TIME_SHORT, true);
+                    mConstants.FLEX_TIME_SHORT);
+            // After moving in INACTIVE, the maintenance window should start the time inactive
+            // timeout and a single light idle period.
+            scheduleLightMaintenanceAlarmLocked(
+                    mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.LIGHT_IDLE_TIMEOUT);
             EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
         }
     }
@@ -3364,9 +3328,9 @@
 
     @GuardedBy("this")
     private void resetLightIdleManagementLocked() {
-        mNextLightIdleDelay = 0;
-        mNextLightIdleDelayFlex = 0;
-        mCurLightIdleBudget = 0;
+        mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
+        mMaintenanceStartTime = 0;
+        mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
         cancelLightAlarmLocked();
     }
 
@@ -3401,90 +3365,115 @@
     }
 
     @GuardedBy("this")
-    void stepLightIdleStateLocked(String reason) {
-        if (mLightState == LIGHT_STATE_OVERRIDE) {
+    private void stepLightIdleStateLocked(String reason) {
+        stepLightIdleStateLocked(reason, false);
+    }
+
+    @GuardedBy("this")
+    @VisibleForTesting
+    @SuppressLint("WakelockTimeout")
+    void stepLightIdleStateLocked(String reason, boolean forceProgression) {
+        if (mLightState == LIGHT_STATE_ACTIVE || mLightState == LIGHT_STATE_OVERRIDE) {
             // If we are already in deep device idle mode, then
             // there is nothing left to do for light mode.
             return;
         }
 
-        if (DEBUG) Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + mLightState);
+        if (DEBUG) {
+            Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + lightStateToString(mLightState)
+                    + " force=" + forceProgression);
+        }
         EventLogTags.writeDeviceIdleLightStep();
 
-        switch (mLightState) {
-            case LIGHT_STATE_INACTIVE:
-                mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
-                // Reset the upcoming idle delays.
-                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
-                mNextLightIdleDelayFlex = mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX;
-                mMaintenanceStartTime = 0;
-                if (!isOpsInactiveLocked()) {
-                    // We have some active ops going on...  give them a chance to finish
-                    // before going in to our first idle.
-                    mLightState = LIGHT_STATE_PRE_IDLE;
-                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
-                    scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT,
-                            mConstants.FLEX_TIME_SHORT, true);
-                    break;
+        final long nowElapsed = mInjector.getElapsedRealtime();
+        final boolean crossedMaintenanceTime =
+                mNextLightMaintenanceAlarmTime > 0 && nowElapsed >= mNextLightMaintenanceAlarmTime;
+        final boolean crossedProgressionTime =
+                mNextLightAlarmTime > 0 && nowElapsed >= mNextLightAlarmTime;
+        final boolean enterMaintenance;
+        if (crossedMaintenanceTime) {
+            if (crossedProgressionTime) {
+                enterMaintenance = (mNextLightAlarmTime <= mNextLightMaintenanceAlarmTime);
+            } else {
+                enterMaintenance = true;
+            }
+        } else if (crossedProgressionTime) {
+            enterMaintenance = false;
+        } else if (forceProgression) {
+            // This will happen for adb commands, unit tests,
+            // and when we're in WAITING_FOR_NETWORK and the network connects.
+            enterMaintenance =
+                    mLightState == LIGHT_STATE_IDLE
+                            || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK;
+        } else {
+            Slog.wtfStack(TAG, "stepLightIdleStateLocked called in invalid state");
+            return;
+        }
+
+        if (enterMaintenance) {
+            if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
+                // We have been idling long enough, now it is time to do some work.
+                mActiveIdleOpCount = 1;
+                mActiveIdleWakeLock.acquire();
+                mMaintenanceStartTime = SystemClock.elapsedRealtime();
+                if (mCurLightIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+                    mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+                } else if (mCurLightIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
+                    mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
                 }
-                // Nothing active, fall through to immediately idle.
-            case LIGHT_STATE_PRE_IDLE:
-            case LIGHT_STATE_IDLE_MAINTENANCE:
-                if (mMaintenanceStartTime != 0) {
-                    long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
-                    if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
-                        // We didn't use up all of our minimum budget; add this to the reserve.
-                        mCurLightIdleBudget +=
-                                (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET - duration);
-                    } else {
-                        // We used more than our minimum budget; this comes out of the reserve.
-                        mCurLightIdleBudget -=
-                                (duration - mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
-                    }
-                }
-                mMaintenanceStartTime = 0;
-                scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex, false);
                 mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                         (long) (mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
-                mNextLightIdleDelayFlex = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT_FLEX,
-                        (long) (mNextLightIdleDelayFlex * mConstants.LIGHT_IDLE_FACTOR));
-                if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
-                mLightState = LIGHT_STATE_IDLE;
-                EventLogTags.writeDeviceIdleLight(mLightState, reason);
-                addEvent(EVENT_LIGHT_IDLE, null);
-                mGoingIdleWakeLock.acquire();
-                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
-                break;
-            case LIGHT_STATE_IDLE:
-            case LIGHT_STATE_WAITING_FOR_NETWORK:
-                if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
-                    // We have been idling long enough, now it is time to do some work.
-                    mActiveIdleOpCount = 1;
-                    mActiveIdleWakeLock.acquire();
-                    mMaintenanceStartTime = SystemClock.elapsedRealtime();
-                    if (mCurLightIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
-                        mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
-                    } else if (mCurLightIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
-                        mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
-                    }
-                    scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT, true);
-                    if (DEBUG) Slog.d(TAG,
-                            "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
-                    mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
-                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
-                    addEvent(EVENT_LIGHT_MAINTENANCE, null);
-                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
-                } else {
-                    // We'd like to do maintenance, but currently don't have network
-                    // connectivity...  let's try to wait until the network comes back.
-                    // We'll only wait for another full idle period, however, and then give up.
-                    scheduleLightAlarmLocked(mNextLightIdleDelay,
-                            mNextLightIdleDelayFlex / 2, true);
-                    if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
-                    mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
-                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
+                // We're entering MAINTENANCE. It should end curLightIdleBudget time from now.
+                // The next maintenance window should be curLightIdleBudget + nextLightIdleDelay
+                // time from now.
+                scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT);
+                scheduleLightMaintenanceAlarmLocked(mCurLightIdleBudget + mNextLightIdleDelay);
+                if (DEBUG) {
+                    Slog.d(TAG, "Moved from " + lightStateToString(mLightState)
+                            + " to LIGHT_STATE_IDLE_MAINTENANCE");
                 }
-                break;
+                mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
+                addEvent(EVENT_LIGHT_MAINTENANCE, null);
+                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
+            } else {
+                // We'd like to do maintenance, but currently don't have network
+                // connectivity...  let's try to wait until the network comes back.
+                // We'll only wait for another full idle period, however, and then give up.
+                scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay);
+                mNextLightAlarmTime = 0;
+                if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
+                mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
+                EventLogTags.writeDeviceIdleLight(mLightState, reason);
+            }
+        } else {
+            if (mMaintenanceStartTime != 0) {
+                // Cap duration at budget since the non-wakeup alarm to exit maintenance may
+                // not fire at the exact intended time, but once the system is up, we will stop
+                // more ongoing work.
+                long duration = Math.min(mCurLightIdleBudget,
+                        SystemClock.elapsedRealtime() - mMaintenanceStartTime);
+                if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+                    // We didn't use up all of our minimum budget; add this to the reserve.
+                    mCurLightIdleBudget +=
+                            (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET - duration);
+                } else {
+                    // We used more than our minimum budget; this comes out of the reserve.
+                    mCurLightIdleBudget -=
+                            (duration - mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
+                }
+            }
+            mMaintenanceStartTime = 0;
+            // We're entering IDLE. We may have used less than curLightIdleBudget for the
+            // maintenance window, so reschedule the alarm starting from now.
+            scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay);
+            mNextLightAlarmTime = 0;
+            if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
+            mLightState = LIGHT_STATE_IDLE;
+            EventLogTags.writeDeviceIdleLight(mLightState, reason);
+            addEvent(EVENT_LIGHT_IDLE, null);
+            mGoingIdleWakeLock.acquire();
+            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
         }
     }
 
@@ -3839,8 +3828,7 @@
 
     @GuardedBy("this")
     void exitMaintenanceEarlyIfNeededLocked() {
-        if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE
-                || mLightState == LIGHT_STATE_PRE_IDLE) {
+        if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
             if (isOpsInactiveLocked()) {
                 final long now = SystemClock.elapsedRealtime();
                 if (DEBUG) {
@@ -3853,10 +3841,8 @@
                 }
                 if (mState == STATE_IDLE_MAINTENANCE) {
                     stepIdleStateLocked("s:early");
-                } else if (mLightState == LIGHT_STATE_PRE_IDLE) {
-                    stepLightIdleStateLocked("s:predone");
                 } else {
-                    stepLightIdleStateLocked("s:early");
+                    stepLightIdleStateLocked("s:early", /* forceProgression */ true);
                 }
             }
         }
@@ -3969,6 +3955,10 @@
             mNextLightAlarmTime = 0;
             mAlarmManager.cancel(mLightAlarmListener);
         }
+        if (mNextLightMaintenanceAlarmTime != 0) {
+            mNextLightMaintenanceAlarmTime = 0;
+            mAlarmManager.cancel(mLightMaintenanceAlarmListener);
+        }
     }
 
     @GuardedBy("this")
@@ -4035,26 +4025,54 @@
     }
 
     @GuardedBy("this")
-    void scheduleLightAlarmLocked(long delay, long flex, boolean wakeup) {
+    @VisibleForTesting
+    void scheduleLightAlarmLocked(long delay, long flex) {
         if (DEBUG) {
             Slog.d(TAG, "scheduleLightAlarmLocked(" + delay
                     + (mConstants.USE_WINDOW_ALARMS ? "/" + flex : "")
-                    + ", wakeup=" + wakeup + ")");
+                    + ")");
         }
-        mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
+        mNextLightAlarmTime = mInjector.getElapsedRealtime() + delay;
         if (mConstants.USE_WINDOW_ALARMS) {
             mAlarmManager.setWindow(
-                    wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME,
+                    AlarmManager.ELAPSED_REALTIME,
                     mNextLightAlarmTime, flex,
                     "DeviceIdleController.light", mLightAlarmListener, mHandler);
         } else {
             mAlarmManager.set(
-                    wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME,
+                    AlarmManager.ELAPSED_REALTIME,
                     mNextLightAlarmTime,
                     "DeviceIdleController.light", mLightAlarmListener, mHandler);
         }
     }
 
+    @GuardedBy("this")
+    @VisibleForTesting
+    void scheduleLightMaintenanceAlarmLocked(long delay) {
+        if (DEBUG) {
+            Slog.d(TAG, "scheduleLightMaintenanceAlarmLocked(" + delay + ")");
+        }
+        mNextLightMaintenanceAlarmTime = mInjector.getElapsedRealtime() + delay;
+        mAlarmManager.setWindow(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                mNextLightMaintenanceAlarmTime, mConstants.FLEX_TIME_SHORT,
+                "DeviceIdleController.light", mLightMaintenanceAlarmListener, mHandler);
+    }
+
+    @VisibleForTesting
+    long getNextLightAlarmTimeForTesting() {
+        synchronized (this) {
+            return mNextLightAlarmTime;
+        }
+    }
+
+    @VisibleForTesting
+    long getNextLightMaintenanceAlarmTimeForTesting() {
+        synchronized (this) {
+            return mNextLightMaintenanceAlarmTime;
+        }
+    }
+
     private void scheduleMotionRegistrationAlarmLocked() {
         if (DEBUG) Slog.d(TAG, "scheduleMotionRegistrationAlarmLocked");
         long nextMotionRegistrationAlarmTime =
@@ -4424,7 +4442,7 @@
                         pw.print("Stepped to deep: ");
                         pw.println(stateToString(mState));
                     } else if ("light".equals(arg)) {
-                        stepLightIdleStateLocked("s:shell");
+                        stepLightIdleStateLocked("s:shell", /* forceProgression */ true);
                         pw.print("Stepped to light: "); pw.println(lightStateToString(mLightState));
                     } else {
                         pw.println("Unknown idle mode: " + arg);
@@ -4464,7 +4482,7 @@
                         becomeInactiveIfAppropriateLocked();
                         int curLightState = mLightState;
                         while (curLightState != LIGHT_STATE_IDLE) {
-                            stepLightIdleStateLocked("s:shell");
+                            stepLightIdleStateLocked("s:shell", /* forceProgression */ true);
                             if (curLightState == mLightState) {
                                 pw.print("Unable to go light idle; stopped at ");
                                 pw.println(lightStateToString(mLightState));
@@ -5076,19 +5094,19 @@
             if (mNextLightIdleDelay != 0) {
                 pw.print("  mNextLightIdleDelay=");
                 TimeUtils.formatDuration(mNextLightIdleDelay, pw);
-                if (mConstants.USE_WINDOW_ALARMS) {
-                    pw.print(" (flex=");
-                    TimeUtils.formatDuration(mNextLightIdleDelayFlex, pw);
-                    pw.println(")");
-                } else {
-                    pw.println();
-                }
+                pw.println();
             }
             if (mNextLightAlarmTime != 0) {
                 pw.print("  mNextLightAlarmTime=");
                 TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
+            if (mNextLightMaintenanceAlarmTime != 0) {
+                pw.print("  mNextLightMaintenanceAlarmTime=");
+                TimeUtils.formatDuration(
+                        mNextLightMaintenanceAlarmTime, SystemClock.elapsedRealtime(), pw);
+                pw.println();
+            }
             if (mCurLightIdleBudget != 0) {
                 pw.print("  mCurLightIdleBudget=");
                 TimeUtils.formatDuration(mCurLightIdleBudget, pw);
diff --git a/api/OWNERS b/api/OWNERS
index a027270..4d8ed03 100644
--- a/api/OWNERS
+++ b/api/OWNERS
@@ -4,3 +4,6 @@
 file:platform/packages/modules/common:/OWNERS
 
 per-file Android.bp = file:platform/build/soong:/OWNERS
+
+# For metalava team to disable lint checks in platform
+per-file Android.bp = aurimas@google.com,emberrose@google.com,sjgilbert@google.com
\ No newline at end of file
diff --git a/core/api/current.txt b/core/api/current.txt
index 54c3b84..e5e7d1d1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -111,6 +111,7 @@
     field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
     field public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
     field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
+    field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
     field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
     field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
     field public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
@@ -121,6 +122,7 @@
     field public static final String NFC = "android.permission.NFC";
     field public static final String NFC_PREFERRED_PAYMENT_INFO = "android.permission.NFC_PREFERRED_PAYMENT_INFO";
     field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
+    field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
     field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
     field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
     field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
@@ -3061,6 +3063,7 @@
     method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
     method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
+    method @Nullable public final android.accessibilityservice.InputMethod getInputMethod();
     method @NonNull public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
@@ -3073,6 +3076,7 @@
     method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo);
     method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     method public final android.os.IBinder onBind(android.content.Intent);
+    method @NonNull public android.accessibilityservice.InputMethod onCreateInputMethod();
     method @Deprecated protected boolean onGesture(int);
     method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureEvent);
     method public abstract void onInterrupt();
@@ -3260,6 +3264,7 @@
     field public static final int FEEDBACK_VISUAL = 8; // 0x8
     field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
     field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
+    field public static final int FLAG_INPUT_METHOD_EDITOR = 32768; // 0x8000
     field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
     field public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 8192; // 0x2000
     field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
@@ -3320,6 +3325,28 @@
     method public boolean willContinue();
   }
 
+  public class InputMethod {
+    ctor protected InputMethod(@NonNull android.accessibilityservice.AccessibilityService);
+    method @Nullable public final android.accessibilityservice.InputMethod.AccessibilityInputConnection getCurrentInputConnection();
+    method @Nullable public final android.view.inputmethod.EditorInfo getCurrentInputEditorInfo();
+    method public final boolean getCurrentInputStarted();
+    method public void onFinishInput();
+    method public void onStartInput(@NonNull android.view.inputmethod.EditorInfo, boolean);
+    method public void onUpdateSelection(int, int, int, int, int, int);
+  }
+
+  public final class InputMethod.AccessibilityInputConnection {
+    method public void clearMetaKeyStates(int);
+    method public void commitText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
+    method public void deleteSurroundingText(int, int);
+    method public int getCursorCapsMode(int);
+    method @Nullable public android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
+    method public void performContextMenuAction(int);
+    method public void performEditorAction(int);
+    method public void sendKeyEvent(@NonNull android.view.KeyEvent);
+    method public void setSelection(int, int);
+  }
+
   public final class MagnificationConfig implements android.os.Parcelable {
     method public int describeContents();
     method public float getCenterX();
@@ -4243,6 +4270,7 @@
     method @Deprecated public final void setProgressBarIndeterminate(boolean);
     method @Deprecated public final void setProgressBarIndeterminateVisibility(boolean);
     method @Deprecated public final void setProgressBarVisibility(boolean);
+    method public void setRecentsScreenshotEnabled(boolean);
     method public void setRequestedOrientation(int);
     method public final void setResult(int);
     method public final void setResult(int, android.content.Intent);
@@ -7334,6 +7362,7 @@
     method public int getCurrentFailedPasswordAttempts();
     method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
+    method @Nullable public String getDeviceManagerRoleHolderPackageName();
     method public CharSequence getDeviceOwnerLockScreenInfo();
     method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
     method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>);
@@ -7629,6 +7658,7 @@
     field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE";
     field public static final String EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT";
     field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER";
+    field public static final String EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT = "android.app.extra.PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT";
     field public static final String EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS = "android.app.extra.PROVISIONING_SKIP_EDUCATION_SCREENS";
     field public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
     field @Deprecated public static final String EXTRA_PROVISIONING_SKIP_USER_CONSENT = "android.app.extra.PROVISIONING_SKIP_USER_CONSENT";
@@ -7651,6 +7681,7 @@
     field public static final String EXTRA_RESOURCE_ID = "android.app.extra.RESOURCE_ID";
     field public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = "android.app.extra.RESOURCE_TYPE_DRAWABLE";
     field public static final String EXTRA_RESOURCE_TYPE_STRING = "android.app.extra.RESOURCE_TYPE_STRING";
+    field public static final String EXTRA_RESULT_LAUNCH_INTENT = "android.app.extra.RESULT_LAUNCH_INTENT";
     field public static final int FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY = 1; // 0x1
     field public static final int FLAG_MANAGED_CAN_ACCESS_PARENT = 2; // 0x2
     field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
@@ -11874,6 +11905,7 @@
     field public static final String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
     field public static final String FEATURE_SENSOR_GYROSCOPE_LIMITED_AXES = "android.hardware.sensor.gyroscope_limited_axes";
     field public static final String FEATURE_SENSOR_GYROSCOPE_LIMITED_AXES_UNCALIBRATED = "android.hardware.sensor.gyroscope_limited_axes_uncalibrated";
+    field public static final String FEATURE_SENSOR_HEADING = "android.hardware.sensor.heading";
     field public static final String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate";
     field public static final String FEATURE_SENSOR_HEART_RATE_ECG = "android.hardware.sensor.heartrate.ecg";
     field public static final String FEATURE_SENSOR_HINGE_ANGLE = "android.hardware.sensor.hinge_angle";
@@ -18167,14 +18199,21 @@
     method @Nullable public android.view.Surface getSurface();
     method public int getSurfaceGroupId();
     method @NonNull public java.util.List<android.view.Surface> getSurfaces();
+    method public int getTimestampBase();
     method public void removeSensorPixelModeUsed(int);
     method public void removeSurface(@NonNull android.view.Surface);
     method public void setDynamicRangeProfile(int);
     method public void setPhysicalCameraId(@Nullable String);
     method public void setStreamUseCase(int);
+    method public void setTimestampBase(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
     field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff
+    field public static final int TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED = 4; // 0x4
+    field public static final int TIMESTAMP_BASE_DEFAULT = 0; // 0x0
+    field public static final int TIMESTAMP_BASE_MONOTONIC = 2; // 0x2
+    field public static final int TIMESTAMP_BASE_REALTIME = 3; // 0x3
+    field public static final int TIMESTAMP_BASE_SENSOR = 1; // 0x1
   }
 
   public final class RecommendedStreamConfigurationMap {
@@ -23343,7 +23382,6 @@
     method @NonNull public java.util.List<java.lang.String> getAllowedPackages();
     method @NonNull public java.util.List<java.lang.String> getDeduplicationPackageOrder();
     method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
-    method @NonNull public java.util.List<java.lang.String> getRequiredFeatures();
     method public boolean shouldPerformActiveScan();
     method public boolean shouldRemoveDuplicates();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -23357,7 +23395,6 @@
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setAllowedPackages(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setDeduplicationPackageOrder(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
-    method @NonNull public android.media.RouteDiscoveryPreference.Builder setRequiredFeatures(@NonNull java.util.List<java.lang.String>);
     method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean);
   }
 
@@ -26399,6 +26436,14 @@
     method public int getUid();
   }
 
+  public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    ctor public EthernetNetworkSpecifier(@NonNull String);
+    method public int describeContents();
+    method @Nullable public String getInterfaceName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
+  }
+
   public final class Ikev2VpnProfile extends android.net.PlatformVpnProfile {
     method @NonNull public java.util.List<java.lang.String> getAllowedAlgorithms();
     method public int getMaxMtu();
@@ -26428,6 +26473,7 @@
     method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setMetered(boolean);
     method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo);
+    method @NonNull public android.net.Ikev2VpnProfile.Builder setRequiresInternetValidation(boolean);
   }
 
   public class LocalServerSocket implements java.io.Closeable {
@@ -26502,6 +26548,7 @@
 
   public abstract class PlatformVpnProfile {
     method public final boolean getExcludeLocalRoutes();
+    method public final boolean getRequiresInternetValidation();
     method public final int getType();
     method @NonNull public final String getTypeString();
     field public static final int TYPE_IKEV2_IPSEC_PSK = 7; // 0x7
@@ -26694,7 +26741,8 @@
   public class VpnManager {
     method public void deleteProvisionedVpnProfile();
     method @Nullable public android.content.Intent provisionVpnProfile(@NonNull android.net.PlatformVpnProfile);
-    method public void startProvisionedVpnProfile();
+    method @Deprecated public void startProvisionedVpnProfile();
+    method @NonNull public String startProvisionedVpnProfileSession();
     method public void stopProvisionedVpnProfile();
   }
 
@@ -38821,6 +38869,7 @@
     field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2
     field public static final int REASON_APP_CANCEL = 8; // 0x8
     field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9
+    field public static final int REASON_ASSISTANT_CANCEL = 22; // 0x16
     field public static final int REASON_CANCEL = 2; // 0x2
     field public static final int REASON_CANCEL_ALL = 3; // 0x3
     field public static final int REASON_CHANNEL_BANNED = 17; // 0x11
@@ -39363,6 +39412,7 @@
   public interface RecognitionListener {
     method public void onBeginningOfSpeech();
     method public void onBufferReceived(byte[]);
+    method public default void onEndOfSegmentedSession();
     method public void onEndOfSpeech();
     method public void onError(int);
     method public void onEvent(int, android.os.Bundle);
@@ -39370,6 +39420,7 @@
     method public void onReadyForSpeech(android.os.Bundle);
     method public void onResults(android.os.Bundle);
     method public void onRmsChanged(float);
+    method public default void onSegmentResults(@NonNull android.os.Bundle);
   }
 
   public abstract class RecognitionService extends android.app.Service {
@@ -39387,6 +39438,7 @@
   public class RecognitionService.Callback {
     method public void beginningOfSpeech() throws android.os.RemoteException;
     method public void bufferReceived(byte[]) throws android.os.RemoteException;
+    method public void endOfSegmentedSession() throws android.os.RemoteException;
     method public void endOfSpeech() throws android.os.RemoteException;
     method public void error(int) throws android.os.RemoteException;
     method @NonNull public android.content.AttributionSource getCallingAttributionSource();
@@ -39395,6 +39447,7 @@
     method public void readyForSpeech(android.os.Bundle) throws android.os.RemoteException;
     method public void results(android.os.Bundle) throws android.os.RemoteException;
     method public void rmsChanged(float) throws android.os.RemoteException;
+    method public void segmentResults(@NonNull android.os.Bundle) throws android.os.RemoteException;
   }
 
   public static class RecognitionService.SupportCallback {
@@ -39450,6 +39503,7 @@
     field public static final String EXTRA_RESULTS_PENDINGINTENT = "android.speech.extra.RESULTS_PENDINGINTENT";
     field public static final String EXTRA_RESULTS_PENDINGINTENT_BUNDLE = "android.speech.extra.RESULTS_PENDINGINTENT_BUNDLE";
     field public static final String EXTRA_SECURE = "android.speech.extras.EXTRA_SECURE";
+    field public static final String EXTRA_SEGMENT_SESSION = "android.speech.extra.SEGMENT_SESSION";
     field public static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
     field public static final String EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
     field public static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
@@ -41027,6 +41081,7 @@
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
     field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
+    field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool";
     field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
     field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int";
     field @Deprecated public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
@@ -41329,6 +41384,7 @@
     field public static final String KEY_SIP_TIMER_T2_MILLIS_INT = "ims.sip_timer_t2_millis_int";
     field public static final String KEY_SIP_TIMER_T4_MILLIS_INT = "ims.sip_timer_t4_millis_int";
     field public static final String KEY_SUPPORTED_RATS_INT_ARRAY = "ims.supported_rats_int_array";
+    field public static final String KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL = "ims.use_sip_uri_for_presence_subscribe_bool";
     field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int";
     field public static final int NETWORK_TYPE_HOME = 0; // 0x0
     field public static final int NETWORK_TYPE_ROAMING = 1; // 0x1
@@ -43259,6 +43315,7 @@
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void rebootModem();
     method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
     method public void registerTelephonyCallback(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
@@ -46788,6 +46845,7 @@
 
   public interface DumpableContainer {
     method public boolean addDumpable(@NonNull android.util.Dumpable);
+    method public boolean removeDumpable(@NonNull android.util.Dumpable);
   }
 
   public class EventLog {
@@ -51959,8 +52017,8 @@
     method @NonNull public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle();
     method public boolean isCallCaptioningEnabled();
     method public final boolean isEnabled();
-    method public final boolean isSystemAudioCaptioningRequested();
-    method public final boolean isSystemAudioCaptioningUiRequested();
+    method public final boolean isSystemAudioCaptioningEnabled();
+    method public final boolean isSystemAudioCaptioningUiEnabled();
     method public void removeCaptioningChangeListener(@NonNull android.view.accessibility.CaptioningManager.CaptioningChangeListener);
   }
 
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 36d54f5..5df593d 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -250,14 +250,6 @@
 
 package android.net {
 
-  public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
-    ctor public EthernetNetworkSpecifier(@NonNull String);
-    method public int describeContents();
-    method @Nullable public String getInterfaceName();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.EthernetNetworkSpecifier> CREATOR;
-  }
-
   public class LocalSocket implements java.io.Closeable {
     ctor public LocalSocket(@NonNull java.io.FileDescriptor);
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 01e2414..0dfee9f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -195,7 +195,6 @@
     field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
     field public static final String MANAGE_WALLPAPER_EFFECTS_GENERATION = "android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION";
     field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
-    field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
     field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
     field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
     field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
@@ -227,7 +226,6 @@
     field public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY";
     field public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission.OPEN_ACCESSIBILITY_DETAILS_SETTINGS";
     field public static final String OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD = "android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD";
-    field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
     field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT";
     field public static final String PACKET_KEEPALIVE_OFFLOAD = "android.permission.PACKET_KEEPALIVE_OFFLOAD";
     field public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
@@ -470,6 +468,10 @@
     method public boolean convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions);
     method @Deprecated public boolean isBackgroundVisibleBehind();
     method @Deprecated public void onBackgroundVisibleBehindChanged(boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityAsUser(@NonNull android.content.Intent, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityForResultAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityForResultAsUser(@NonNull android.content.Intent, int, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void startActivityForResultAsUser(@NonNull android.content.Intent, @NonNull String, int, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
   }
 
   public static interface Activity.TranslucentConversionListener {
@@ -1143,11 +1145,16 @@
     field public static final String EXTRA_PROVISIONING_ORGANIZATION_NAME = "android.app.extra.PROVISIONING_ORGANIZATION_NAME";
     field public static final String EXTRA_PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE = "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE";
     field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT = "android.app.extra.PROVISIONING_ROLE_HOLDER_CUSTOM_USER_CONSENT_INTENT";
+    field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE";
+    field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER";
+    field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION";
+    field public static final String EXTRA_PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM";
     field public static final String EXTRA_PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER = "android.app.extra.PROVISIONING_SKIP_OWNERSHIP_DISCLAIMER";
     field public static final String EXTRA_PROVISIONING_SUPPORTED_MODES = "android.app.extra.PROVISIONING_SUPPORTED_MODES";
     field public static final String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL";
     field public static final String EXTRA_PROVISIONING_TRIGGER = "android.app.extra.PROVISIONING_TRIGGER";
     field public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
+    field public static final String EXTRA_ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE = "android.app.extra.ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE";
     field public static final String EXTRA_ROLE_HOLDER_STATE = "android.app.extra.ROLE_HOLDER_STATE";
     field public static final int FLAG_SUPPORTED_MODES_DEVICE_OWNER = 4; // 0x4
     field public static final int FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED = 1; // 0x1
@@ -1355,14 +1362,13 @@
     method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder setOptions(@NonNull android.os.PersistableBundle);
   }
 
-  public final class AmbientContextEventResponse implements android.os.Parcelable {
-    method public int describeContents();
-    method @Nullable public android.app.PendingIntent getActionPendingIntent();
-    method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents();
-    method @NonNull public String getPackageName();
-    method public int getStatusCode();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventResponse> CREATOR;
+  public final class AmbientContextManager {
+    method @NonNull public static java.util.List<android.app.ambientcontext.AmbientContextEvent> getEventsFromIntent(@NonNull android.content.Intent);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void queryAmbientContextServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void startConsentActivity(@NonNull java.util.Set<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver();
+    field public static final String EXTRA_AMBIENT_CONTEXT_EVENTS = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS";
     field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
     field public static final int STATUS_MICROPHONE_DISABLED = 4; // 0x4
     field public static final int STATUS_NOT_SUPPORTED = 2; // 0x2
@@ -1371,27 +1377,14 @@
     field public static final int STATUS_UNKNOWN = 0; // 0x0
   }
 
-  public static final class AmbientContextEventResponse.Builder {
-    ctor public AmbientContextEventResponse.Builder();
-    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent);
-    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse build();
-    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setActionPendingIntent(@NonNull android.app.PendingIntent);
-    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setPackageName(@NonNull String);
-    method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setStatusCode(int);
-  }
-
-  public final class AmbientContextManager {
-    method @Nullable public static android.app.ambientcontext.AmbientContextEventResponse getResponseFromIntent(@NonNull android.content.Intent);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver();
-    field public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE";
-  }
-
 }
 
 package android.app.assist {
 
-  public class ActivityId {
+  public final class ActivityId implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.ActivityId> CREATOR;
   }
 
   public static class AssistStructure.ViewNode {
@@ -2756,7 +2749,7 @@
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
     method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method public void addActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener, @NonNull java.util.concurrent.Executor);
-    method public void close();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(int, int, int, @Nullable android.view.Surface, int, @Nullable android.os.Handler, @Nullable android.hardware.display.VirtualDisplay.Callback);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
@@ -2774,8 +2767,8 @@
     method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
-    field public static final int LOCK_STATE_ALWAYS_LOCKED = 0; // 0x0
     field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
+    field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
   }
 
   public static final class VirtualDeviceParams.Builder {
@@ -10697,14 +10690,45 @@
 
 package android.service.ambientcontext {
 
+  public final class AmbientContextDetectionResult implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents();
+    method @NonNull public String getPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.ambientcontext.AmbientContextDetectionResult> CREATOR;
+  }
+
+  public static final class AmbientContextDetectionResult.Builder {
+    ctor public AmbientContextDetectionResult.Builder();
+    method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent);
+    method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult build();
+    method @NonNull public android.service.ambientcontext.AmbientContextDetectionResult.Builder setPackageName(@NonNull String);
+  }
+
   public abstract class AmbientContextDetectionService extends android.app.Service {
     ctor public AmbientContextDetectionService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.app.ambientcontext.AmbientContextEventResponse>);
+    method @BinderThread public abstract void onQueryServiceStatus(@NonNull int[], @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
+    method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
     method public abstract void onStopDetection(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService";
   }
 
+  public final class AmbientContextDetectionServiceStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getPackageName();
+    method public int getStatusCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.ambientcontext.AmbientContextDetectionServiceStatus> CREATOR;
+  }
+
+  public static final class AmbientContextDetectionServiceStatus.Builder {
+    ctor public AmbientContextDetectionServiceStatus.Builder();
+    method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus build();
+    method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setPackageName(@NonNull String);
+    method @NonNull public android.service.ambientcontext.AmbientContextDetectionServiceStatus.Builder setStatusCode(int);
+  }
+
 }
 
 package android.service.appprediction {
@@ -13252,7 +13276,7 @@
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
@@ -14751,6 +14775,7 @@
     method @Nullable public android.telephony.ims.RcsContactPresenceTuple getCapabilityTuple(@NonNull String);
     method @NonNull public java.util.List<android.telephony.ims.RcsContactPresenceTuple> getCapabilityTuples();
     method @NonNull public android.net.Uri getContactUri();
+    method @Nullable public android.net.Uri getEntityUri();
     method @NonNull public java.util.Set<java.lang.String> getFeatureTags();
     method public int getRequestResult();
     method public int getSourceType();
@@ -14779,6 +14804,7 @@
     method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuple(@NonNull android.telephony.ims.RcsContactPresenceTuple);
     method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuples(@NonNull java.util.List<android.telephony.ims.RcsContactPresenceTuple>);
     method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder setEntityUri(@NonNull android.net.Uri);
   }
 
   public class RcsUceAdapter {
@@ -15525,8 +15551,8 @@
   }
 
   public class CaptioningManager {
-    method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningRequested(boolean);
-    method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningUiRequested(boolean);
+    method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningUiEnabled(boolean);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5bd6ca8..5ad106c7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -569,9 +569,13 @@
 
 package android.app.assist {
 
-  public class ActivityId {
+  public final class ActivityId implements android.os.Parcelable {
+    ctor public ActivityId(int, @Nullable android.os.IBinder);
+    method public int describeContents();
     method public int getTaskId();
     method @Nullable public android.os.IBinder getToken();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.ActivityId> CREATOR;
   }
 
 }
@@ -2862,6 +2866,7 @@
     method public default void setShouldShowSystemDecors(int, boolean);
     method public default void setShouldShowWithInsecureKeyguard(int, boolean);
     method public default boolean shouldShowSystemDecors(int);
+    method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int);
     field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1
     field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2
     field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 50473f1..cf3ca20 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -40,6 +40,8 @@
 import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManager;
+import android.inputmethodservice.IInputMethodSessionWrapper;
+import android.inputmethodservice.RemoteInputConnection;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -65,14 +67,23 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodSession;
 
+import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionWithIdCallback;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -627,6 +638,21 @@
         void onAccessibilityButtonAvailabilityChanged(boolean available);
         /** This is called when the system action list is changed. */
         void onSystemActionsChanged();
+        /** This is called when an app requests ime sessions or when the service is enabled. */
+        void createImeSession(IInputSessionWithIdCallback callback);
+        /**
+         * This is called when InputMethodManagerService requests to set the session enabled or
+         * disabled
+         */
+        void setImeSessionEnabled(InputMethodSession session, boolean enabled);
+        /** This is called when an app binds input or when the service is enabled. */
+        void bindInput(InputBinding binding);
+        /** This is called when an app unbinds input or when the service is disabled. */
+        void unbindInput();
+        /** This is called when an app starts input or when the service is enabled. */
+        void startInput(@Nullable InputConnection inputConnection,
+                @NonNull EditorInfo editorInfo, boolean restarting,
+                @NonNull IBinder startInputToken);
     }
 
     /**
@@ -763,6 +789,8 @@
             new SparseArray<>(0);
 
     private SoftKeyboardController mSoftKeyboardController;
+    private InputMethod mInputMethod;
+    private boolean mInputMethodInitialized = false;
     private final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers =
             new SparseArray<>(0);
 
@@ -797,6 +825,17 @@
             for (int i = 0; i < mMagnificationControllers.size(); i++) {
                 mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
             }
+            AccessibilityServiceInfo info = getServiceInfo();
+            if (info != null) {
+                boolean requestIme = (info.flags
+                        & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0;
+                if (requestIme && !mInputMethodInitialized) {
+                    mInputMethod = onCreateInputMethod();
+                    mInputMethodInitialized = true;
+                }
+            } else {
+                Log.e(LOG_TAG, "AccessibilityServiceInfo is null in dispatchServiceConnected");
+            }
         }
         if (mSoftKeyboardController != null) {
             mSoftKeyboardController.onServiceConnected();
@@ -1849,6 +1888,32 @@
         }
     }
 
+    /**
+     * The default implementation returns our default {@link InputMethod}. Subclasses can override
+     * it to provide their own customized version. Accessibility services need to set the
+     * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs.
+     *
+     * @return the InputMethod.
+     */
+    @NonNull
+    public InputMethod onCreateInputMethod() {
+        return new InputMethod(this);
+    }
+
+    /**
+     * Returns the InputMethod instance after the system calls {@link #onCreateInputMethod()},
+     * which may be used to input text or get editable text selection change notifications. It will
+     * return null if the accessibility service doesn't set the
+     * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag or the system doesn't call
+     * {@link #onCreateInputMethod()}.
+     *
+     * @return the InputMethod instance
+     */
+    @Nullable
+    public final InputMethod getInputMethod() {
+        return mInputMethod;
+    }
+
     private void onSoftKeyboardShowModeChanged(int showMode) {
         if (mSoftKeyboardController != null) {
             mSoftKeyboardController.dispatchSoftKeyboardShowModeChanged(showMode);
@@ -2657,6 +2722,47 @@
             public void onSystemActionsChanged() {
                 AccessibilityService.this.onSystemActionsChanged();
             }
+
+            @Override
+            public void createImeSession(IInputSessionWithIdCallback callback) {
+                if (mInputMethod != null) {
+                    mInputMethod.createImeSession(callback);
+                }
+            }
+
+            @Override
+            public void setImeSessionEnabled(InputMethodSession session, boolean enabled) {
+                if (mInputMethod != null) {
+                    mInputMethod.setImeSessionEnabled(session, enabled);
+                }
+            }
+
+            @Override
+            public void bindInput(InputBinding binding) {
+                if (mInputMethod != null) {
+                    mInputMethod.bindInput(binding);
+                }
+            }
+
+            @Override
+            public void unbindInput() {
+                if (mInputMethod != null) {
+                    mInputMethod.unbindInput();
+                }
+            }
+
+            @Override
+            public void startInput(@Nullable InputConnection inputConnection,
+                    @NonNull EditorInfo editorInfo, boolean restarting,
+                    @NonNull IBinder startInputToken) {
+                if (mInputMethod != null) {
+                    if (restarting) {
+                        mInputMethod.restartInput(inputConnection, editorInfo);
+                    } else {
+                        mInputMethod.startInput(inputConnection, editorInfo);
+                    }
+                }
+            }
         });
     }
 
@@ -2682,6 +2788,11 @@
         private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12;
         private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13;
         private static final int DO_ON_SYSTEM_ACTIONS_CHANGED = 14;
+        private static final int DO_CREATE_IME_SESSION = 15;
+        private static final int DO_SET_IME_SESSION_ENABLED = 16;
+        private static final int DO_BIND_INPUT = 17;
+        private static final int DO_UNBIND_INPUT = 18;
+        private static final int DO_START_INPUT = 19;
 
         private final HandlerCaller mCaller;
 
@@ -2690,6 +2801,22 @@
 
         private int mConnectionId = AccessibilityInteractionClient.NO_ID;
 
+        /**
+         * This is not {@null} only between {@link #bindInput(InputBinding)} and
+         * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if
+         * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary
+         * blocking operations.
+         *
+         * <p>This field must be set and cleared only from the binder thread(s), where the system
+         * guarantees that {@link #bindInput(InputBinding)},
+         * {@link #startInput(IBinder, IInputContext, EditorInfo, boolean)}, and
+         * {@link #unbindInput()} are called with the same order as the original calls
+         * in {@link com.android.server.inputmethod.InputMethodManagerService}.
+         * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
+         */
+        @Nullable
+        CancellationGroup mCancellationGroup = null;
+
         public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                 Callbacks callback) {
             mCallback = callback;
@@ -2783,6 +2910,70 @@
             mCaller.sendMessage(mCaller.obtainMessage(DO_ON_SYSTEM_ACTIONS_CHANGED));
         }
 
+        /** This is called when an app requests ime sessions or when the service is enabled. */
+        public void createImeSession(IInputSessionWithIdCallback callback) {
+            final Message message = mCaller.obtainMessageO(DO_CREATE_IME_SESSION, callback);
+            mCaller.sendMessage(message);
+        }
+
+        /**
+         * This is called when InputMethodManagerService requests to set the session enabled or
+         * disabled
+         */
+        public void setImeSessionEnabled(IInputMethodSession session, boolean enabled) {
+            try {
+                InputMethodSession ls = ((IInputMethodSessionWrapper)
+                        session).getInternalInputMethodSession();
+                if (ls == null) {
+                    Log.w(LOG_TAG, "Session is already finished: " + session);
+                    return;
+                }
+                mCaller.sendMessage(mCaller.obtainMessageIO(
+                        DO_SET_IME_SESSION_ENABLED, enabled ? 1 : 0, ls));
+            } catch (ClassCastException e) {
+                Log.w(LOG_TAG, "Incoming session not of correct type: " + session, e);
+            }
+        }
+
+        /** This is called when an app binds input or when the service is enabled. */
+        public void bindInput(InputBinding binding) {
+            if (mCancellationGroup != null) {
+                Log.e(LOG_TAG, "bindInput must be paired with unbindInput.");
+            }
+            mCancellationGroup = new CancellationGroup();
+            InputConnection ic = new RemoteInputConnection(new WeakReference<>(() -> mContext),
+                    IInputContext.Stub.asInterface(binding.getConnectionToken()),
+                    mCancellationGroup);
+            InputBinding nu = new InputBinding(ic, binding);
+            final Message message = mCaller.obtainMessageO(DO_BIND_INPUT, nu);
+            mCaller.sendMessage(message);
+        }
+
+        /** This is called when an app unbinds input or when the service is disabled. */
+        public void unbindInput() {
+            if (mCancellationGroup != null) {
+                // Signal the flag then forget it.
+                mCancellationGroup.cancelAll();
+                mCancellationGroup = null;
+            } else {
+                Log.e(LOG_TAG, "unbindInput must be paired with bindInput.");
+            }
+            mCaller.sendMessage(mCaller.obtainMessage(DO_UNBIND_INPUT));
+        }
+
+        /** This is called when an app starts input or when the service is enabled. */
+        public void startInput(IBinder startInputToken, IInputContext inputContext,
+                EditorInfo editorInfo, boolean restarting) {
+            if (mCancellationGroup == null) {
+                Log.e(LOG_TAG, "startInput must be called after bindInput.");
+                mCancellationGroup = new CancellationGroup();
+            }
+            final Message message = mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
+                    inputContext, editorInfo, mCancellationGroup, restarting ? 1 : 0,
+                    0 /* unused */);
+            mCaller.sendMessage(message);
+        }
+
         @Override
         public void onMotionEvent(MotionEvent event) {
             final Message message = PooledLambda.obtainMessage(
@@ -2948,6 +3139,50 @@
                     }
                     return;
                 }
+                case DO_CREATE_IME_SESSION: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        IInputSessionWithIdCallback callback =
+                                (IInputSessionWithIdCallback) message.obj;
+                        mCallback.createImeSession(callback);
+                    }
+                    return;
+                }
+                case DO_SET_IME_SESSION_ENABLED: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.setImeSessionEnabled((InputMethodSession) message.obj,
+                                message.arg1 != 0);
+                    }
+                    return;
+                }
+                case DO_BIND_INPUT: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.bindInput((InputBinding) message.obj);
+                    }
+                    return;
+                }
+                case DO_UNBIND_INPUT: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.unbindInput();
+                    }
+                    return;
+                }
+                case DO_START_INPUT: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final IBinder startInputToken = (IBinder) args.arg1;
+                        final IInputContext inputContext = (IInputContext) args.arg2;
+                        final EditorInfo info = (EditorInfo) args.arg3;
+                        final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
+                        final boolean restarting = args.argi5 == 1;
+                        final InputConnection ic = inputContext != null
+                                ? new RemoteInputConnection(new WeakReference<>(() -> mContext),
+                                inputContext, cancellationGroup) : null;
+                        info.makeCompatible(mContext.getApplicationInfo().targetSdkVersion);
+                        mCallback.startInput(ic, info, restarting, startInputToken);
+                        args.recycle();
+                    }
+                    return;
+                }
                 default:
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 6988048..85e7854 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -390,6 +390,15 @@
      */
     public static final int FLAG_SEND_MOTION_EVENTS = 0x0004000;
 
+    /**
+     * This flag makes the AccessibilityService an input method editor with a subset of input
+     * method editor capabilities: get the {@link android.view.inputmethod.InputConnection} and get
+     * text selection change notifications.
+     *
+     * @see AccessibilityService#getInputMethod()
+     */
+    public static final int FLAG_INPUT_METHOD_EDITOR = 0x0008000;
+
     /** {@hide} */
     public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
 
@@ -497,6 +506,7 @@
      * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
      * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
      * @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK
+     * @see #FLAG_INPUT_METHOD_EDITOR
      */
     public int flags;
 
@@ -1356,6 +1366,8 @@
                 return "FLAG_REQUEST_FINGERPRINT_GESTURES";
             case FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK:
                 return "FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK";
+            case FLAG_INPUT_METHOD_EDITOR:
+                return "FLAG_INPUT_METHOD_EDITOR";
             default:
                 return null;
         }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 375383d..94da61f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -24,6 +24,11 @@
 import android.accessibilityservice.MagnificationConfig;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionWithIdCallback;
 
 /**
  * Top-level interface to an accessibility service component.
@@ -63,4 +68,15 @@
     void onAccessibilityButtonAvailabilityChanged(boolean available);
 
     void onSystemActionsChanged();
+
+    void createImeSession(IInputSessionWithIdCallback callback);
+
+    void setImeSessionEnabled(IInputMethodSession session, boolean enabled);
+
+    void bindInput(in InputBinding binding);
+
+    void unbindInput();
+
+    void startInput(in IBinder startInputToken, in IInputContext inputContext,
+                in EditorInfo editorInfo, boolean restarting);
 }
diff --git a/core/java/android/accessibilityservice/InputMethod.java b/core/java/android/accessibilityservice/InputMethod.java
new file mode 100644
index 0000000..001d804
--- /dev/null
+++ b/core/java/android/accessibilityservice/InputMethod.java
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2022 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.accessibilityservice;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.graphics.Rect;
+import android.inputmethodservice.IInputMethodSessionWrapper;
+import android.inputmethodservice.RemoteInputConnection;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSession;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextAttribute;
+
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputSessionWithIdCallback;
+
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides input method APIs. Some public methods such as
+ * @link #onUpdateSelection(int, int, int, int, int, int)} do nothing by default and service
+ * developers should override them as needed. Developers should also override
+ * {@link AccessibilityService#onCreateInputMethod()} to return
+ * their custom InputMethod implementation. Accessibility services also need to set the
+ * {@link AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR} flag to use input method APIs.
+ */
+public class InputMethod {
+    private static final String LOG_TAG = "A11yInputMethod";
+
+    private final AccessibilityService mService;
+    private InputBinding mInputBinding;
+    private InputConnection mInputConnection;
+    private boolean mInputStarted;
+    private InputConnection mStartedInputConnection;
+    private EditorInfo mInputEditorInfo;
+
+    protected InputMethod(@NonNull AccessibilityService service) {
+        mService = service;
+    }
+
+    /**
+     * Retrieve the currently active InputConnection that is bound to
+     * the input method, or null if there is none.
+     */
+    @Nullable
+    public final AccessibilityInputConnection getCurrentInputConnection() {
+        InputConnection ic = mStartedInputConnection;
+        if (ic != null) {
+            return new AccessibilityInputConnection(ic);
+        }
+        if (mInputConnection != null) {
+            return new AccessibilityInputConnection(mInputConnection);
+        }
+        return null;
+    }
+
+    /**
+     * Whether the input has started.
+     */
+    public final boolean getCurrentInputStarted() {
+        return mInputStarted;
+    }
+
+    /**
+     * Get the EditorInfo which describes several attributes of a text editing object
+     * that an accessibility service is communicating with (typically an EditText).
+     */
+    @Nullable
+    public final EditorInfo getCurrentInputEditorInfo() {
+        return mInputEditorInfo;
+    }
+
+    /**
+     * Called to inform the accessibility service that text input has started in an
+     * editor.  You should use this callback to initialize the state of your
+     * input to match the state of the editor given to it.
+     *
+     * @param attribute  The attributes of the editor that input is starting
+     *                   in.
+     * @param restarting Set to true if input is restarting in the same
+     *                   editor such as because the application has changed the text in
+     *                   the editor.  Otherwise will be false, indicating this is a new
+     *                   session with the editor.
+     */
+    public void onStartInput(@NonNull EditorInfo attribute, boolean restarting) {
+        // Intentionally empty
+    }
+
+    /**
+     * Called to inform the accessibility service that text input has finished in
+     * the last editor. At this point there may be a call to
+     * {@link #onStartInput(EditorInfo, boolean)} to perform input in a
+     * new editor, or the accessibility service may be left idle. This method is
+     * <em>not</em> called when input restarts in the same editor.
+     *
+     * <p>The default
+     * implementation uses the InputConnection to clear any active composing
+     * text; you can override this (not calling the base class implementation)
+     * to perform whatever behavior you would like.
+     */
+    public void onFinishInput() {
+        InputConnection ic = mStartedInputConnection != null ? mStartedInputConnection
+                : mInputConnection;
+        if (ic != null) {
+            ic.finishComposingText();
+        }
+    }
+
+    /**
+     * Called when the application has reported a new selection region of
+     * the text. This is called whether or not the accessibility service has requested
+     * extracted text updates, although if so it will not receive this call
+     * if the extracted text has changed as well.
+     *
+     * <p>Be careful about changing the text in reaction to this call with
+     * methods such as setComposingText, commitText or
+     * deleteSurroundingText. If the cursor moves as a result, this method
+     * will be called again, which may result in an infinite loop.
+     */
+    public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+            int newSelEnd, int candidatesStart, int candidatesEnd) {
+        // Intentionally empty
+    }
+
+    final void createImeSession(IInputSessionWithIdCallback callback) {
+        InputMethodSession session = onCreateInputMethodSessionInterface();
+        try {
+            IInputMethodSessionWrapper wrap =
+                    new IInputMethodSessionWrapper(mService, session, null);
+            callback.sessionCreated(wrap, mService.getConnectionId());
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    final void setImeSessionEnabled(@NonNull InputMethodSession session, boolean enabled) {
+        ((InputMethodSessionForAccessibility) session).setEnabled(enabled);
+    }
+
+    final void bindInput(@NonNull InputBinding binding) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.bindInput");
+        mInputBinding = binding;
+        mInputConnection = binding.getConnection();
+        Log.v(LOG_TAG, "bindInput(): binding=" + binding
+                + " ic=" + mInputConnection);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+    }
+
+    final void unbindInput() {
+        Log.v(LOG_TAG, "unbindInput(): binding=" + mInputBinding
+                + " ic=" + mInputConnection);
+        // Unbind input is per process per display.
+        mInputBinding = null;
+        mInputConnection = null;
+    }
+
+    final void startInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) {
+        Log.v(LOG_TAG, "startInput(): editor=" + attribute);
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput");
+        doStartInput(ic, attribute, false /* restarting */);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+    }
+
+    final void restartInput(@Nullable InputConnection ic, @NonNull EditorInfo attribute) {
+        Log.v(LOG_TAG, "restartInput(): editor=" + attribute);
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.restartInput");
+        doStartInput(ic, attribute, true /* restarting */);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+    }
+
+
+    final void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
+        if (!restarting && mInputStarted) {
+            doFinishInput();
+        }
+        mInputStarted = true;
+        mStartedInputConnection = ic;
+        mInputEditorInfo = attribute;
+        Log.v(LOG_TAG, "CALL: onStartInput");
+        onStartInput(attribute, restarting);
+    }
+
+    final void doFinishInput() {
+        Log.v(LOG_TAG, "CALL: doFinishInput");
+        if (mInputStarted) {
+            Log.v(LOG_TAG, "CALL: onFinishInput");
+            onFinishInput();
+        }
+        mInputStarted = false;
+        mStartedInputConnection = null;
+    }
+
+    private InputMethodSession onCreateInputMethodSessionInterface() {
+        return new InputMethodSessionForAccessibility();
+    }
+
+    /**
+     * This class provides the allowed list of {@link InputConnection} APIs for
+     * accessibility services.
+     */
+    public final class AccessibilityInputConnection {
+        private InputConnection mIc;
+        AccessibilityInputConnection(InputConnection ic) {
+            this.mIc = ic;
+        }
+
+        /**
+         * Commit text to the text box and set the new cursor position. This method is
+         * used to allow the IME to provide extra information while setting up text.
+         *
+         * <p>This method commits the contents of the currently composing text, and then
+         * moves the cursor according to {@code newCursorPosition}. If there
+         * is no composing text when this method is called, the new text is
+         * inserted at the cursor position, removing text inside the selection
+         * if any.
+         *
+         * <p>Calling this method will cause the editor to call
+         * {@link #onUpdateSelection(int, int, int, int,
+         * int, int)} on the current accessibility service after the batch input is over.
+         * <strong>Editor authors</strong>, for this to happen you need to
+         * make the changes known to the accessibility service by calling
+         * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+         * but be careful to wait until the batch edit is over if one is
+         * in progress.</p>
+         *
+         * @param text The text to commit. This may include styles.
+         * @param newCursorPosition The new cursor position around the text,
+         *        in Java characters. If > 0, this is relative to the end
+         *        of the text - 1; if <= 0, this is relative to the start
+         *        of the text. So a value of 1 will always advance the cursor
+         *        to the position after the full text being inserted. Note that
+         *        this means you can't position the cursor within the text,
+         *        because the editor can make modifications to the text
+         *        you are providing so it is not possible to correctly specify
+         *        locations there.
+         * @param textAttribute The extra information about the text.
+         */
+        public void commitText(@NonNull CharSequence text, int newCursorPosition,
+                @Nullable TextAttribute textAttribute) {
+            if (mIc != null) {
+                mIc.commitText(text, newCursorPosition, textAttribute);
+            }
+        }
+
+        /**
+         * Set the selection of the text editor. To set the cursor
+         * position, start and end should have the same value.
+         *
+         * <p>Since this moves the cursor, calling this method will cause
+         * the editor to call
+         * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int,
+         * int,int, int)} on the current IME after the batch input is over.
+         * <strong>Editor authors</strong>, for this to happen you need to
+         * make the changes known to the input method by calling
+         * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+         * but be careful to wait until the batch edit is over if one is
+         * in progress.</p>
+         *
+         * <p>This has no effect on the composing region which must stay
+         * unchanged. The order of start and end is not important. In
+         * effect, the region from start to end and the region from end to
+         * start is the same. Editor authors, be ready to accept a start
+         * that is greater than end.</p>
+         *
+         * @param start the character index where the selection should start.
+         * @param end the character index where the selection should end.
+         */
+        public void setSelection(int start, int end) {
+            if (mIc != null) {
+                mIc.setSelection(start, end);
+            }
+        }
+
+        /**
+         * Gets the surrounding text around the current cursor, with <var>beforeLength</var>
+         * characters of text before the cursor (start of the selection), <var>afterLength</var>
+         * characters of text after the cursor (end of the selection), and all of the selected
+         * text. The range are for java characters, not glyphs that can be multiple characters.
+         *
+         * <p>This method may fail either if the input connection has become invalid (such as its
+         * process crashing), or the client is taking too long to respond with the text (it is
+         * given a couple seconds to return), or the protocol is not supported. In any of these
+         * cases, null is returned.
+         *
+         * <p>This method does not affect the text in the editor in any way, nor does it affect the
+         * selection or composing spans.</p>
+         *
+         * <p>If {@link InputConnection#GET_TEXT_WITH_STYLES} is supplied as flags, the editor
+         * should return a {@link android.text.Spanned} with all the spans set on the text.</p>
+         *
+         * <p><strong>Accessibility service authors:</strong> please consider this will trigger an
+         * IPC round-trip that will take some time. Assume this method consumes a lot of time.
+         *
+         * @param beforeLength The expected length of the text before the cursor.
+         * @param afterLength The expected length of the text after the cursor.
+         * @param flags Supplies additional options controlling how the text is returned. May be
+         *              either {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+         * @return an {@link android.view.inputmethod.SurroundingText} object describing the
+         * surrounding text and state of selection, or null if the input connection is no longer
+         * valid, or the editor can't comply with the request for some reason, or the application
+         * does not implement this method. The length of the returned text might be less than the
+         * sum of <var>beforeLength</var> and <var>afterLength</var> .
+         * @throws IllegalArgumentException if {@code beforeLength} or {@code afterLength} is
+         * negative.
+         */
+        @Nullable
+        public SurroundingText getSurroundingText(
+                @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength,
+                @InputConnection.GetTextType int flags) {
+            if (mIc != null) {
+                return mIc.getSurroundingText(beforeLength, afterLength, flags);
+            }
+            return null;
+        }
+
+        /**
+         * Delete <var>beforeLength</var> characters of text before the
+         * current cursor position, and delete <var>afterLength</var>
+         * characters of text after the current cursor position, excluding
+         * the selection. Before and after refer to the order of the
+         * characters in the string, not to their visual representation:
+         * this means you don't have to figure out the direction of the
+         * text and can just use the indices as-is.
+         *
+         * <p>The lengths are supplied in Java chars, not in code points
+         * or in glyphs.</p>
+         *
+         * <p>Since this method only operates on text before and after the
+         * selection, it can't affect the contents of the selection. This
+         * may affect the composing span if the span includes characters
+         * that are to be deleted, but otherwise will not change it. If
+         * some characters in the composing span are deleted, the
+         * composing span will persist but get shortened by however many
+         * chars inside it have been removed.</p>
+         *
+         * <p><strong>Accessibility service authors:</strong> please be careful not to
+         * delete only half of a surrogate pair. Also take care not to
+         * delete more characters than are in the editor, as that may have
+         * ill effects on the application. Calling this method will cause
+         * the editor to call
+         * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+         * int, int)} on your service after the batch input is over.</p>
+         *
+         * <p><strong>Editor authors:</strong> please be careful of race
+         * conditions in implementing this call. An IME can make a change
+         * to the text or change the selection position and use this
+         * method right away; you need to make sure the effects are
+         * consistent with the results of the latest edits. Also, although
+         * the IME should not send lengths bigger than the contents of the
+         * string, you should check the values for overflows and trim the
+         * indices to the size of the contents to avoid crashes. Since
+         * this changes the contents of the editor, you need to make the
+         * changes known to the input method by calling
+         * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+         * but be careful to wait until the batch edit is over if one is
+         * in progress.</p>
+         *
+         * @param beforeLength The number of characters before the cursor to be deleted, in code
+         *        unit. If this is greater than the number of existing characters between the
+         *        beginning of the text and the cursor, then this method does not fail but deletes
+         *        all the characters in that range.
+         * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+         *        If this is greater than the number of existing characters between the cursor and
+         *        the end of the text, then this method does not fail but deletes all the characters
+         *        in that range.
+         */
+        public void deleteSurroundingText(int beforeLength, int afterLength) {
+            if (mIc != null) {
+                mIc.deleteSurroundingText(beforeLength, afterLength);
+            }
+        }
+
+        /**
+         * Send a key event to the process that is currently attached
+         * through this input connection. The event will be dispatched
+         * like a normal key event, to the currently focused view; this
+         * generally is the view that is providing this InputConnection,
+         * but due to the asynchronous nature of this protocol that can
+         * not be guaranteed and the focus may have changed by the time
+         * the event is received.
+         *
+         * <p>This method can be used to send key events to the
+         * application. For example, an on-screen keyboard may use this
+         * method to simulate a hardware keyboard. There are three types
+         * of standard keyboards, numeric (12-key), predictive (20-key)
+         * and ALPHA (QWERTY). You can specify the keyboard type by
+         * specify the device id of the key event.</p>
+         *
+         * <p>You will usually want to set the flag
+         * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+         * on all key event objects you give to this API; the flag will
+         * not be set for you.</p>
+         *
+         * <p>Note that it's discouraged to send such key events in normal
+         * operation; this is mainly for use with
+         * {@link android.text.InputType#TYPE_NULL} type text fields. Use
+         * the {@link #commitText} family of methods to send text to the
+         * application instead.</p>
+         *
+         * @param event The key event.
+         *
+         * @see KeyEvent
+         * @see KeyCharacterMap#NUMERIC
+         * @see KeyCharacterMap#PREDICTIVE
+         * @see KeyCharacterMap#ALPHA
+         */
+        public void sendKeyEvent(@NonNull KeyEvent event) {
+            if (mIc != null) {
+                mIc.sendKeyEvent(event);
+            }
+        }
+
+        /**
+         * Have the editor perform an action it has said it can do.
+         *
+         * @param editorAction This must be one of the action constants for
+         * {@link EditorInfo#imeOptions EditorInfo.imeOptions}, such as
+         * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}, or the value of
+         * {@link EditorInfo#actionId EditorInfo.actionId} if a custom action is available.
+         */
+        public void performEditorAction(int editorAction) {
+            if (mIc != null) {
+                mIc.performEditorAction(editorAction);
+            }
+        }
+
+        /**
+         * Perform a context menu action on the field. The given id may be one of:
+         * {@link android.R.id#selectAll},
+         * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+         * {@link android.R.id#cut}, {@link android.R.id#copy},
+         * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
+         * or {@link android.R.id#switchInputMethod}
+         */
+        public void performContextMenuAction(int id) {
+            if (mIc != null) {
+                mIc.performContextMenuAction(id);
+            }
+        }
+
+        /**
+         * Retrieve the current capitalization mode in effect at the
+         * current cursor position in the text. See
+         * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}
+         * for more information.
+         *
+         * <p>This method may fail either if the input connection has
+         * become invalid (such as its process crashing) or the client is
+         * taking too long to respond with the text (it is given a couple
+         * seconds to return). In either case, 0 is returned.</p>
+         *
+         * <p>This method does not affect the text in the editor in any
+         * way, nor does it affect the selection or composing spans.</p>
+         *
+         * <p><strong>Editor authors:</strong> please be careful of race
+         * conditions in implementing this call. An IME can change the
+         * cursor position and use this method right away; you need to make
+         * sure the returned value is consistent with the results of the
+         * latest edits and changes to the cursor position.</p>
+         *
+         * @param reqModes The desired modes to retrieve, as defined by
+         * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
+         * constants are defined so that you can simply pass the current
+         * {@link EditorInfo#inputType TextBoxAttribute.contentType} value
+         * directly in to here.
+         * @return the caps mode flags that are in effect at the current
+         * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
+         */
+        public int getCursorCapsMode(int reqModes) {
+            if (mIc != null) {
+                return mIc.getCursorCapsMode(reqModes);
+            }
+            return 0;
+        }
+
+        /**
+         * Clear the given meta key pressed states in the given input
+         * connection.
+         *
+         * <p>This can be used by the accessibility service to clear the meta key states set
+         * by a hardware keyboard with latched meta keys, if the editor
+         * keeps track of these.</p>
+         *
+         * @param states The states to be cleared, may be one or more bits as
+         * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
+         */
+        public void clearMetaKeyStates(int states) {
+            if (mIc != null) {
+                mIc.clearMetaKeyStates(states);
+            }
+        }
+    }
+
+    /**
+     * Concrete implementation of InputMethodSession that provides all of the standard behavior
+     * for an input method session.
+     */
+    private final class InputMethodSessionForAccessibility implements InputMethodSession {
+        boolean mEnabled = true;
+
+        public void setEnabled(boolean enabled) {
+            mEnabled = enabled;
+        }
+
+        @Override
+        public void finishInput() {
+            if (mEnabled) {
+                doFinishInput();
+            }
+        }
+
+        @Override
+        public void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+                int newSelEnd, int candidatesStart, int candidatesEnd) {
+            if (mEnabled) {
+                InputMethod.this.onUpdateSelection(oldSelEnd, oldSelEnd, newSelStart,
+                        newSelEnd, candidatesStart, candidatesEnd);
+            }
+        }
+
+        @Override
+        public void viewClicked(boolean focusChanged) {
+        }
+
+        @Override
+        public void updateCursor(@NonNull Rect newCursor) {
+        }
+
+        @Override
+        public void displayCompletions(
+                @SuppressLint("ArrayReturn") @NonNull CompletionInfo[] completions) {
+        }
+
+        @Override
+        public void updateExtractedText(int token, @NonNull ExtractedText text) {
+        }
+
+        public void dispatchKeyEvent(int seq, @NonNull KeyEvent event,
+                @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) {
+        }
+
+        @Override
+        public void dispatchKeyEvent(int seq, @NonNull KeyEvent event,
+                @NonNull EventCallback callback) {
+        }
+
+        public void dispatchTrackballEvent(int seq, @NonNull MotionEvent event,
+                @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) {
+        }
+
+        @Override
+        public void dispatchTrackballEvent(int seq, @NonNull MotionEvent event,
+                @NonNull EventCallback callback) {
+        }
+
+        public void dispatchGenericMotionEvent(int seq, @NonNull MotionEvent event,
+                @NonNull @CallbackExecutor Executor executor, @NonNull EventCallback callback) {
+        }
+
+        @Override
+        public void dispatchGenericMotionEvent(int seq, @NonNull MotionEvent event,
+                @NonNull EventCallback callback) {
+        }
+
+        @Override
+        public void appPrivateCommand(@NonNull String action, @NonNull Bundle data) {
+        }
+
+        @Override
+        public void toggleSoftInput(int showFlags, int hideFlags) {
+        }
+
+        @Override
+        public void updateCursorAnchorInfo(@NonNull CursorAnchorInfo cursorAnchorInfo) {
+        }
+
+        @Override
+        public void notifyImeHidden() {
+        }
+
+        @Override
+        public void removeImeSurface() {
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void invalidateInputInternal(EditorInfo editorInfo, IInputContext inputContext,
+                int sessionId) {
+            // TODO(b/217788708): Add automated test.
+            if (mStartedInputConnection instanceof RemoteInputConnection) {
+                final RemoteInputConnection ric =
+                        (RemoteInputConnection) mStartedInputConnection;
+                if (!ric.isSameConnection(inputContext)) {
+                    // This is not an error, and can be safely ignored.
+                    return;
+                }
+                editorInfo.makeCompatible(
+                        mService.getApplicationInfo().targetSdkVersion);
+                restartInput(new RemoteInputConnection(ric, sessionId), editorInfo);
+            }
+        }
+    }
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index e022700..a31aa28 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -17,6 +17,8 @@
 package android.app;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 import static android.os.Process.myUid;
@@ -44,6 +46,7 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ActivityNotFoundException;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -5435,26 +5438,115 @@
     }
 
     /**
+     * Launch an activity for which you would like a result when it finished.
+     * When this activity exits, your
+     * onActivityResult() method will be called with the given requestCode.
+     * Using a negative requestCode is the same as calling
+     * {@link #startActivity} (the activity is not launched as a sub-activity).
+     *
+     * <p>Note that this method should only be used with Intent protocols
+     * that are defined to return a result.  In other protocols (such as
+     * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+     * not get the result when you expect.  For example, if the activity you
+     * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+     * run in your task and thus you will immediately receive a cancel result.
+     *
+     * <p>As a special case, if you call startActivityForResult() with a requestCode
+     * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+     * activity, then your window will not be displayed until a result is
+     * returned back from the started activity.  This is to avoid visible
+     * flickering when redirecting to another activity.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param intent      The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param user        The user to start the intent as.
      * @hide Implement to provide correct calling token.
      */
-    @UnsupportedAppUsage
-    public void startActivityForResultAsUser(Intent intent, int requestCode, UserHandle user) {
+    @SystemApi
+    @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void startActivityForResultAsUser(@NonNull Intent intent, int requestCode,
+            @NonNull UserHandle user) {
         startActivityForResultAsUser(intent, requestCode, null, user);
     }
 
     /**
+     * Launch an activity for which you would like a result when it finished.
+     * When this activity exits, your
+     * onActivityResult() method will be called with the given requestCode.
+     * Using a negative requestCode is the same as calling
+     * {@link #startActivity} (the activity is not launched as a sub-activity).
+     *
+     * <p>Note that this method should only be used with Intent protocols
+     * that are defined to return a result.  In other protocols (such as
+     * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+     * not get the result when you expect.  For example, if the activity you
+     * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+     * run in your task and thus you will immediately receive a cancel result.
+     *
+     * <p>As a special case, if you call startActivityForResult() with a requestCode
+     * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+     * activity, then your window will not be displayed until a result is
+     * returned back from the started activity.  This is to avoid visible
+     * flickering when redirecting to another activity.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param intent      The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started. See {@link
+     *                    android.content.Context#startActivity(Intent, Bundle)} for more details.
+     * @param user        The user to start the intent as.
      * @hide Implement to provide correct calling token.
      */
-    public void startActivityForResultAsUser(Intent intent, int requestCode,
-            @Nullable Bundle options, UserHandle user) {
+    @SystemApi
+    @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void startActivityForResultAsUser(@NonNull Intent intent, int requestCode,
+            @Nullable Bundle options, @NonNull UserHandle user) {
         startActivityForResultAsUser(intent, mEmbeddedID, requestCode, options, user);
     }
 
     /**
+     * Launch an activity for which you would like a result when it finished.
+     * When this activity exits, your
+     * onActivityResult() method will be called with the given requestCode.
+     * Using a negative requestCode is the same as calling
+     * {@link #startActivity} (the activity is not launched as a sub-activity).
+     *
+     * <p>Note that this method should only be used with Intent protocols
+     * that are defined to return a result.  In other protocols (such as
+     * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+     * not get the result when you expect.  For example, if the activity you
+     * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+     * run in your task and thus you will immediately receive a cancel result.
+     *
+     * <p>As a special case, if you call startActivityForResult() with a requestCode
+     * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+     * activity, then your window will not be displayed until a result is
+     * returned back from the started activity.  This is to avoid visible
+     * flickering when redirecting to another activity.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param intent      The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options     Additional options for how the Activity should be started. See {@link
+     *                    android.content.Context#startActivity(Intent, Bundle)} for more details.
+     * @param user        The user to start the intent as.
      * @hide Implement to provide correct calling token.
      */
-    public void startActivityForResultAsUser(Intent intent, String resultWho, int requestCode,
-            @Nullable Bundle options, UserHandle user) {
+    @SystemApi
+    @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void startActivityForResultAsUser(@NonNull Intent intent, @NonNull String resultWho,
+            int requestCode,
+            @Nullable Bundle options, @NonNull UserHandle user) {
         if (mParent != null) {
             throw new RuntimeException("Can't be called from a child");
         }
@@ -5464,7 +5556,7 @@
                 options, user);
         if (ar != null) {
             mMainThread.sendActivityResult(
-                mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
+                    mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
         }
         if (requestCode >= 0) {
             // If this start is requesting a result, we can avoid making
@@ -5489,9 +5581,23 @@
     }
 
     /**
-     * @hide Implement to provide correct calling token.
+     * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the
+     * user the activity will be started for.  This is not available to applications
+     * that are not pre-installed on the system image.
+     * @param intent The description of the activity to start.
+     *
+     * @param user The UserHandle of the user to start this activity for.
+     * @param options Additional options for how the Activity should be started.
+     *          May be null if there are no options.  See {@link android.app.ActivityOptions}
+     *          for how to build the Bundle supplied here; there are no supported definitions
+     *          for building it manually.
+     * @throws ActivityNotFoundException &nbsp;
+     * @hide
      */
-    public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+    @SystemApi
+    @RequiresPermission(anyOf = {INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+    public void startActivityAsUser(@NonNull Intent intent,
+            @Nullable Bundle options, @NonNull UserHandle user) {
         if (mParent != null) {
             throw new RuntimeException("Can't be called from a child");
         }
@@ -8556,8 +8662,18 @@
     }
 
     /**
-     * If set to true, this indicates to the system that it should never take a
-     * screenshot of the activity to be used as a representation while it is not in a started state.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.S,
+            publicAlternatives = "Use {@link #setRecentsScreenshotEnabled(boolean)} instead.")
+    public void setDisablePreviewScreenshots(boolean disable) {
+        setRecentsScreenshotEnabled(!disable);
+    }
+
+    /**
+     * If set to false, this indicates to the system that it should never take a
+     * screenshot of the activity to be used as a representation in recents screen. By default, this
+     * value is {@code true}.
      * <p>
      * Note that the system may use the window background of the theme instead to represent
      * the window when it is not running.
@@ -8570,12 +8686,10 @@
      * {@link android.service.voice.VoiceInteractionService} requests a screenshot via
      * {@link android.service.voice.VoiceInteractionSession#SHOW_WITH_SCREENSHOT}.
      *
-     * @param disable {@code true} to disable preview screenshots; {@code false} otherwise.
-     * @hide
+     * @param enabled {@code true} to enable recents screenshots; {@code false} otherwise.
      */
-    @UnsupportedAppUsage
-    public void setDisablePreviewScreenshots(boolean disable) {
-        ActivityClient.getInstance().setDisablePreviewScreenshots(mToken, disable);
+    public void setRecentsScreenshotEnabled(boolean enabled) {
+        ActivityClient.getInstance().setRecentsScreenshotEnabled(mToken, enabled);
     }
 
     /**
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 605a1fa..4715e0f 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -438,9 +438,9 @@
         }
     }
 
-    void setDisablePreviewScreenshots(IBinder token, boolean disable) {
+    void setRecentsScreenshotEnabled(IBinder token, boolean enabled) {
         try {
-            getActivityClientController().setDisablePreviewScreenshots(token, disable);
+            getActivityClientController().setRecentsScreenshotEnabled(token, enabled);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index a58ceaa..cce7dd3 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -215,14 +215,6 @@
     public abstract boolean isSystemReady();
 
     /**
-     * Returns package name given pid.
-     *
-     * @param pid The pid we are searching package name for.
-     */
-    @Nullable
-    public abstract String getPackageNameByPid(int pid);
-
-    /**
      * Sets if the given pid has an overlay UI or not.
      *
      * @param pid The pid we are setting overlay UI for.
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 8181a74..ec2115c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1998,7 +1998,7 @@
     private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
             String instanceName, Handler handler, Executor executor, UserHandle user) {
         // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and
-        // ActivityManagerService.LocalService.startAndBindSupplementalProcessService
+        // ActivityManagerLocal.bindSupplementalProcessService
         IServiceConnection sd;
         if (conn == null) {
             throw new IllegalArgumentException("connection is null");
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 396e552..f9439cb 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -115,8 +115,8 @@
             int enterAnim, int exitAnim, int backgroundColor);
     int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
 
-    /** See {@link android.app.Activity#setDisablePreviewScreenshots}. */
-    oneway void setDisablePreviewScreenshots(in IBinder token, boolean disable);
+    /** See {@link android.app.Activity#setRecentsScreenshotEnabled}. */
+    oneway void setRecentsScreenshotEnabled(in IBinder token, boolean enabled);
 
     /**
      * It should only be called from home activity to remove its outdated snapshot. The home
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f5f2fe0..eeb4705 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -25,7 +25,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.IDevicePolicyManager;
 import android.app.ambientcontext.AmbientContextManager;
-import android.app.ambientcontext.IAmbientContextEventObserver;
+import android.app.ambientcontext.IAmbientContextManager;
 import android.app.appsearch.AppSearchManagerFrameworkInitializer;
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
 import android.app.cloudsearch.CloudSearchManager;
@@ -1542,8 +1542,8 @@
                             throws ServiceNotFoundException {
                         IBinder iBinder = ServiceManager.getServiceOrThrow(
                                 Context.AMBIENT_CONTEXT_SERVICE);
-                        IAmbientContextEventObserver manager =
-                                IAmbientContextEventObserver.Stub.asInterface(iBinder);
+                        IAmbientContextManager manager =
+                                IAmbientContextManager.Stub.asInterface(iBinder);
                         return new AmbientContextManager(ctx.getOuterContext(), manager);
                     }});
 
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index b41b5f00..2af8905 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -63,9 +63,14 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodSession;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.view.IInputSessionWithIdCallback;
 
 import libcore.io.IoUtils;
 
@@ -1566,6 +1571,29 @@
                 }
 
                 @Override
+                public void createImeSession(IInputSessionWithIdCallback callback) {
+                    /* do nothing */
+                }
+
+                @Override
+                public void setImeSessionEnabled(InputMethodSession session, boolean enabled) {
+                }
+
+                @Override
+                public void bindInput(InputBinding binding) {
+                }
+
+                @Override
+                public void unbindInput() {
+                }
+
+                @Override
+                public void startInput(@Nullable InputConnection inputConnection,
+                        @NonNull EditorInfo editorInfo, boolean restarting,
+                        @NonNull IBinder startInputToken) {
+                }
+
+                @Override
                 public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
                     /* do nothing */
                     return false;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e382d07..e52ae51 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -720,6 +720,125 @@
             "android.app.extra.PROVISIONING_ALLOW_OFFLINE";
 
     /**
+     * A String extra holding a url that specifies the download location of the device manager
+     * role holder package.
+     *
+     * <p>This is only meant to be used in cases when a specific variant of the role holder package
+     * is needed (such as a debug variant). If not provided, the default variant of the device
+     * manager role holder package is downloaded.
+     *
+     * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+     * provisioning via an NFC bump.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION =
+            "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION";
+
+    /**
+     * A String extra holding the URL-safe base64 encoded SHA-256 checksum of any signature of the
+     * android package archive at the download location specified in {@link
+     * #EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION}.
+     *
+     * <p>The signatures of an android package archive can be obtained using
+     * {@link android.content.pm.PackageManager#getPackageArchiveInfo} with flag
+     * {@link android.content.pm.PackageManager#GET_SIGNING_CERTIFICATES}.
+     *
+     * <p>If {@link #EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION} is provided, it must
+     * be accompanied by this extra. The provided checksum must match the checksum of any signature
+     * of the file at the download location. If the checksum does not match an error will be shown
+     * to the user and the user will be asked to factory reset the device.
+     *
+     * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+     * provisioning via an NFC bump.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM =
+            "android.app.extra.PROVISIONING_ROLE_HOLDER_SIGNATURE_CHECKSUM";
+
+    /**
+     * A String extra holding a http cookie header which should be used in the http request to the
+     * url specified in {@link #EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_LOCATION}.
+     *
+     * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+     * provisioning via an NFC bump.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER =
+            "android.app.extra.PROVISIONING_ROLE_HOLDER_PACKAGE_DOWNLOAD_COOKIE_HEADER";
+
+    /**
+     * An extra of type {@link android.os.PersistableBundle} that allows the provisioning initiator
+     * to pass data to the device manager role holder.
+     *
+     * <p>The device manager role holder will receive this extra via the {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent.
+     *
+     * <p>The contents of this extra are up to the contract between the provisioning initiator
+     * and the device manager role holder.
+     *
+     * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+     * provisioning via an NFC bump.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE =
+            "android.app.extra.PROVISIONING_ROLE_HOLDER_EXTRAS_BUNDLE";
+
+    /**
+     * A String extra containing the package name of the provisioning initiator.
+     *
+     * <p>Use in an intent with action {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE =
+            "android.app.extra.ROLE_HOLDER_PROVISIONING_INITIATOR_PACKAGE";
+
+    /**
+     * An {@link Intent} result extra specifying the {@link Intent} to be launched after
+     * provisioning is finalized.
+     *
+     * <p>If {@link #EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT} is set to {@code false},
+     * this result will be supplied as part of the result {@link Intent} for provisioning actions
+     * such as {@link #ACTION_PROVISION_MANAGED_PROFILE}. This result will also be supplied as
+     * part of the result {@link Intent} for the device manager role holder provisioning actions.
+     */
+    public static final String EXTRA_RESULT_LAUNCH_INTENT =
+            "android.app.extra.RESULT_LAUNCH_INTENT";
+
+    /**
+     * A boolean extra that determines whether the provisioning flow should launch the resulting
+     * launch intent, if one is supplied by the device manager role holder via {@link
+     * #EXTRA_RESULT_LAUNCH_INTENT}. Default value is {@code false}.
+     *
+     * <p>If {@code true}, the resulting intent will be launched by the provisioning flow, if one
+     * is supplied by the device manager role holder.
+     *
+     * <p>If {@code false}, the resulting intent will be returned as {@link
+     * #EXTRA_RESULT_LAUNCH_INTENT} to the provisioning initiator, if one is supplied by the device
+     * manager role holder. It will be the responsibility of the provisioning initiator to launch
+     * this {@link Intent} after provisioning completes.
+     *
+     * <p>This extra is respected when provided via the provisioning intent actions such as {@link
+     * #ACTION_PROVISION_MANAGED_PROFILE}.
+     */
+    public static final String EXTRA_PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT =
+            "android.app.extra.PROVISIONING_SHOULD_LAUNCH_RESULT_INTENT";
+
+    /**
      * Action: Bugreport sharing with device owner has been accepted by the user.
      *
      * @hide
@@ -15518,4 +15637,40 @@
             }
         }
     }
+
+    /**
+     * Returns the package name of the device manager role holder.
+     *
+     * <p>If the device manager role holder is not configured for this device, returns {@code null}.
+     */
+    @Nullable
+    public String getDeviceManagerRoleHolderPackageName() {
+        String deviceManagerConfig =
+                mContext.getString(com.android.internal.R.string.config_deviceManager);
+        return extractPackageNameFromDeviceManagerConfig(deviceManagerConfig);
+    }
+
+    /**
+     * Retrieves the package name for a given {@code deviceManagerConfig}.
+     *
+     * <p>Valid configs look like:
+     * <ul>
+     *     <li>{@code com.package.name}</li>
+     *     <li>{@code com.package.name:<SHA256 checksum>}</li>
+     * </ul>
+     *
+     * <p>If the supplied {@code deviceManagerConfig} is {@code null} or empty, returns
+     * {@code null}.
+     */
+    @Nullable
+    private String extractPackageNameFromDeviceManagerConfig(
+            @Nullable String deviceManagerConfig) {
+        if (TextUtils.isEmpty(deviceManagerConfig)) {
+            return null;
+        }
+        if (deviceManagerConfig.contains(":")) {
+            return deviceManagerConfig.split(":")[0];
+        }
+        return deviceManagerConfig;
+    }
 }
diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java
index 82b16a2..0557acb 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java
@@ -23,6 +23,9 @@
 import android.os.PersistableBundle;
 import android.util.ArraySet;
 
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
 import java.util.HashSet;
 import java.util.Set;
 
@@ -36,15 +39,17 @@
     @NonNull private final Set<Integer> mEventTypes;
     @NonNull private final PersistableBundle mOptions;
 
-    AmbientContextEventRequest(
+    private AmbientContextEventRequest(
             @NonNull Set<Integer> eventTypes,
             @NonNull PersistableBundle options) {
         this.mEventTypes = eventTypes;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mEventTypes);
+        AnnotationValidations.validate(NonNull.class, null, mEventTypes);
+        Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty");
+        for (int eventType : eventTypes) {
+            AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType);
+        }
         this.mOptions = options;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mOptions);
+        AnnotationValidations.validate(NonNull.class, null, mOptions);
     }
 
     /**
@@ -80,16 +85,20 @@
 
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
-    AmbientContextEventRequest(@NonNull Parcel in) {
+    private AmbientContextEventRequest(@NonNull Parcel in) {
         Set<Integer> eventTypes = (Set<Integer>) in.readArraySet(Integer.class.getClassLoader());
         PersistableBundle options = (PersistableBundle) in.readTypedObject(
                 PersistableBundle.CREATOR);
 
         this.mEventTypes = eventTypes;
-        com.android.internal.util.AnnotationValidations.validate(
+        AnnotationValidations.validate(
                 NonNull.class, null, mEventTypes);
+        Preconditions.checkArgument(!eventTypes.isEmpty(), "eventTypes cannot be empty");
+        for (int eventType : eventTypes) {
+            AnnotationValidations.validate(AmbientContextEvent.EventCode.class, null, eventType);
+        }
         this.mOptions = options;
-        com.android.internal.util.AnnotationValidations.validate(
+        AnnotationValidations.validate(
                 NonNull.class, null, mOptions);
     }
 
diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java
deleted file mode 100644
index 472a78b..0000000
--- a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2022 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.ambientcontext;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.app.PendingIntent;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Represents a response from the {@code AmbientContextEvent} service.
- *
- * @hide
- */
-@SystemApi
-public final class AmbientContextEventResponse implements Parcelable {
-    /**
-     * An unknown status.
-     */
-    public static final int STATUS_UNKNOWN = 0;
-    /**
-     * The value of the status code that indicates success.
-     */
-    public static final int STATUS_SUCCESS = 1;
-    /**
-     * The value of the status code that indicates one or more of the
-     * requested events are not supported.
-     */
-    public static final int STATUS_NOT_SUPPORTED = 2;
-    /**
-     * The value of the status code that indicates service not available.
-     */
-    public static final int STATUS_SERVICE_UNAVAILABLE = 3;
-    /**
-     * The value of the status code that microphone is disabled.
-     */
-    public static final int STATUS_MICROPHONE_DISABLED = 4;
-    /**
-     * The value of the status code that the app is not granted access.
-     */
-    public static final int STATUS_ACCESS_DENIED = 5;
-
-    /** @hide */
-    @IntDef(prefix = { "STATUS_" }, value = {
-            STATUS_UNKNOWN,
-            STATUS_SUCCESS,
-            STATUS_NOT_SUPPORTED,
-            STATUS_SERVICE_UNAVAILABLE,
-            STATUS_MICROPHONE_DISABLED,
-            STATUS_ACCESS_DENIED
-    }) public @interface StatusCode {}
-
-    @StatusCode private final int mStatusCode;
-    @NonNull private final List<AmbientContextEvent> mEvents;
-    @NonNull private final String mPackageName;
-    @Nullable private final PendingIntent mActionPendingIntent;
-
-    /** @hide */
-    public static String statusToString(@StatusCode int value) {
-        switch (value) {
-            case STATUS_UNKNOWN:
-                return "STATUS_UNKNOWN";
-            case STATUS_SUCCESS:
-                return "STATUS_SUCCESS";
-            case STATUS_NOT_SUPPORTED:
-                return "STATUS_NOT_SUPPORTED";
-            case STATUS_SERVICE_UNAVAILABLE:
-                return "STATUS_SERVICE_UNAVAILABLE";
-            case STATUS_MICROPHONE_DISABLED:
-                return "STATUS_MICROPHONE_DISABLED";
-            case STATUS_ACCESS_DENIED:
-                return "STATUS_ACCESS_DENIED";
-            default: return Integer.toHexString(value);
-        }
-    }
-
-    AmbientContextEventResponse(
-            @StatusCode int statusCode,
-            @NonNull List<AmbientContextEvent> events,
-            @NonNull String packageName,
-            @Nullable PendingIntent actionPendingIntent) {
-        this.mStatusCode = statusCode;
-        AnnotationValidations.validate(StatusCode.class, null, mStatusCode);
-        this.mEvents = events;
-        AnnotationValidations.validate(NonNull.class, null, mEvents);
-        this.mPackageName = packageName;
-        AnnotationValidations.validate(NonNull.class, null, mPackageName);
-        this.mActionPendingIntent = actionPendingIntent;
-    }
-
-    /**
-     * The status of the response.
-     */
-    public @StatusCode int getStatusCode() {
-        return mStatusCode;
-    }
-
-    /**
-     * The detected event.
-     */
-    public @NonNull List<AmbientContextEvent> getEvents() {
-        return mEvents;
-    }
-
-    /**
-     * The package to deliver the response to.
-     */
-    public @NonNull String getPackageName() {
-        return mPackageName;
-    }
-
-    /**
-     * A {@link PendingIntent} that the client should call to allow further actions by user.
-     * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to the
-     * grant access activity.
-     */
-    public @Nullable PendingIntent getActionPendingIntent() {
-        return mActionPendingIntent;
-    }
-
-    @Override
-    public String toString() {
-        return "AmbientContextEventResponse { " + "statusCode = " + mStatusCode + ", "
-                + "events = " + mEvents + ", " + "packageName = " + mPackageName + ", "
-                + "callbackPendingIntent = " + mActionPendingIntent + " }";
-    }
-
-    @Override
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        byte flg = 0;
-        if (mActionPendingIntent != null) flg |= 0x8;
-        dest.writeByte(flg);
-        dest.writeInt(mStatusCode);
-        dest.writeParcelableList(mEvents, flags);
-        dest.writeString(mPackageName);
-        if (mActionPendingIntent != null) dest.writeTypedObject(mActionPendingIntent, flags);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    AmbientContextEventResponse(@NonNull android.os.Parcel in) {
-        byte flg = in.readByte();
-        int statusCode = in.readInt();
-        List<AmbientContextEvent> events = new ArrayList<>();
-        in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(),
-                AmbientContextEvent.class);
-        String packageName = in.readString();
-        PendingIntent callbackPendingIntent = (flg & 0x8) == 0 ? null
-                : (PendingIntent) in.readTypedObject(PendingIntent.CREATOR);
-
-        this.mStatusCode = statusCode;
-        AnnotationValidations.validate(
-                StatusCode.class, null, mStatusCode);
-        this.mEvents = events;
-        AnnotationValidations.validate(
-                NonNull.class, null, mEvents);
-        this.mPackageName = packageName;
-        AnnotationValidations.validate(
-                NonNull.class, null, mPackageName);
-        this.mActionPendingIntent = callbackPendingIntent;
-    }
-
-    public static final @NonNull Parcelable.Creator<AmbientContextEventResponse> CREATOR =
-            new Parcelable.Creator<AmbientContextEventResponse>() {
-        @Override
-        public AmbientContextEventResponse[] newArray(int size) {
-            return new AmbientContextEventResponse[size];
-        }
-
-        @Override
-        public AmbientContextEventResponse createFromParcel(@NonNull android.os.Parcel in) {
-            return new AmbientContextEventResponse(in);
-        }
-    };
-
-    /**
-     * A builder for {@link AmbientContextEventResponse}
-     */
-    @SuppressWarnings("WeakerAccess")
-    public static final class Builder {
-        private @StatusCode int mStatusCode;
-        private @NonNull List<AmbientContextEvent> mEvents;
-        private @NonNull String mPackageName;
-        private @Nullable PendingIntent mCallbackPendingIntent;
-        private long mBuilderFieldsSet = 0L;
-
-        public Builder() {
-        }
-
-        /**
-         * The status of the response.
-         */
-        public @NonNull Builder setStatusCode(@StatusCode int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mStatusCode = value;
-            return this;
-        }
-
-        /**
-         * Adds an event to the builder.
-         */
-        public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) {
-            checkNotUsed();
-            if (mEvents == null) {
-                mBuilderFieldsSet |= 0x2;
-                mEvents = new ArrayList<>();
-            }
-            mEvents.add(value);
-            return this;
-        }
-
-        /**
-         * The package to deliver the response to.
-         */
-        public @NonNull Builder setPackageName(@NonNull String value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
-            mPackageName = value;
-            return this;
-        }
-
-        /**
-         * A {@link PendingIntent} that the client should call to allow further actions by user.
-         * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to
-         * the grant access activity.
-         */
-        public @NonNull Builder setActionPendingIntent(@NonNull PendingIntent value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
-            mCallbackPendingIntent = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull AmbientContextEventResponse build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x10; // Mark builder used
-
-            if ((mBuilderFieldsSet & 0x1) == 0) {
-                mStatusCode = STATUS_UNKNOWN;
-            }
-            if ((mBuilderFieldsSet & 0x2) == 0) {
-                mEvents = new ArrayList<>();
-            }
-            if ((mBuilderFieldsSet & 0x4) == 0) {
-                mPackageName = "";
-            }
-            if ((mBuilderFieldsSet & 0x8) == 0) {
-                mCallbackPendingIntent = null;
-            }
-            AmbientContextEventResponse o = new AmbientContextEventResponse(
-                    mStatusCode,
-                    mEvents,
-                    mPackageName,
-                    mCallbackPendingIntent);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x10) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-}
diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java
index 6841d1b..7f913e7 100644
--- a/core/java/android/app/ambientcontext/AmbientContextManager.java
+++ b/core/java/android/app/ambientcontext/AmbientContextManager.java
@@ -17,116 +17,280 @@
 package android.app.ambientcontext;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Binder;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
 /**
- * Allows granted apps to register for particular pre-defined {@link AmbientContextEvent}s.
- * After successful registration, the app receives a callback on the provided {@link PendingIntent}
- * when the requested event is detected.
- * <p />
- *
- * Example:
- *
- * <pre><code>
- *     // Create request
- *     AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
- *         .addEventType(AmbientContextEvent.EVENT_COUGH)
- *         .addEventTYpe(AmbientContextEvent.EVENT_SNORE)
- *         .build();
- *     // Create PendingIntent
- *     Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class)
- *         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- *     PendingIntent pendingIntent = PendingIntents.getBroadcastMutable(context, 0, intent, 0);
- *     // Register for events
- *     AmbientContextManager ambientContextManager =
- *         context.getSystemService(AmbientContextManager.class);
- *    ambientContextManager.registerObserver(request, pendingIntent);
- *
- *    // Handle the callback intent in your receiver
- *    {@literal @}Override
- *    protected void onReceive(Context context, Intent intent) {
- *      AmbientContextEventResponse response =
- *          AmbientContextManager.getResponseFromIntent(intent);
- *      if (response != null) {
- *        if (response.getStatusCode() == AmbientContextEventResponse.STATUS_SUCCESS) {
- *          // Do something useful with response.getEvent()
- *        } else if (response.getStatusCode() == AmbientContextEventResponse.STATUS_ACCESS_DENIED) {
- *          // Redirect users to grant access
- *          PendingIntent callbackPendingIntent = response.getCallbackPendingIntent();
- *          if (callbackPendingIntent != null) {
- *            callbackPendingIntent.send();
- *          }
- *        } else ...
- *      }
- *    }
- * </code></pre>
+ * Allows granted apps to register for event types defined in {@link AmbientContextEvent}.
+ * After registration, the app receives a Consumer callback of the service status.
+ * If it is {@link STATUS_SUCCESSFUL}, when the requested events are detected, the provided
+ * {@link PendingIntent} callback will receive the list of detected {@link AmbientContextEvent}s.
+ * If it is {@link STATUS_ACCESS_DENIED}, the app can call {@link #startConsentActivity}
+ * to load the consent screen.
  *
  * @hide
  */
 @SystemApi
 @SystemService(Context.AMBIENT_CONTEXT_SERVICE)
 public final class AmbientContextManager {
-
     /**
-     * The key of an Intent extra indicating the response.
+     * The bundle key for the service status query result, used in
+     * {@code RemoteCallback#sendResult}.
+     *
+     * @hide
      */
-    public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE =
-            "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE";
+    public static final String STATUS_RESPONSE_BUNDLE_KEY =
+            "android.app.ambientcontext.AmbientContextStatusBundleKey";
 
     /**
-     * Allows clients to retrieve the response from the intent.
+     * The key of an intent extra indicating a list of detected {@link AmbientContextEvent}s.
+     * The intent is sent to the app in the app's registered {@link PendingIntent}.
+     */
+    public static final String EXTRA_AMBIENT_CONTEXT_EVENTS =
+            "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENTS";
+
+    /**
+     * An unknown status.
+     */
+    public static final int STATUS_UNKNOWN = 0;
+
+    /**
+     * The value of the status code that indicates success.
+     */
+    public static final int STATUS_SUCCESS = 1;
+
+    /**
+     * The value of the status code that indicates one or more of the
+     * requested events are not supported.
+     */
+    public static final int STATUS_NOT_SUPPORTED = 2;
+
+    /**
+     * The value of the status code that indicates service not available.
+     */
+    public static final int STATUS_SERVICE_UNAVAILABLE = 3;
+
+    /**
+     * The value of the status code that microphone is disabled.
+     */
+    public static final int STATUS_MICROPHONE_DISABLED = 4;
+
+    /**
+     * The value of the status code that the app is not granted access.
+     */
+    public static final int STATUS_ACCESS_DENIED = 5;
+
+    /** @hide */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_UNKNOWN,
+            STATUS_SUCCESS,
+            STATUS_NOT_SUPPORTED,
+            STATUS_SERVICE_UNAVAILABLE,
+            STATUS_MICROPHONE_DISABLED,
+            STATUS_ACCESS_DENIED
+    }) public @interface StatusCode {}
+
+    /**
+     * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent.
+     *
      * @param intent received from the PendingIntent callback
      *
-     * @return the AmbientContextEventResponse, or null if not present
+     * @return the list of events, or an empty list if the intent doesn't have such events.
      */
-    @Nullable
-    public static AmbientContextEventResponse getResponseFromIntent(
-            @NonNull Intent intent) {
-        if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE)) {
-            return intent.getParcelableExtra(EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE);
+    @NonNull public static List<AmbientContextEvent> getEventsFromIntent(@NonNull Intent intent) {
+        if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS)) {
+            return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS);
         } else {
-            return null;
+            return new ArrayList<>();
         }
     }
 
     private final Context mContext;
-    private final IAmbientContextEventObserver mService;
+    private final IAmbientContextManager mService;
 
     /**
      * {@hide}
      */
-    public AmbientContextManager(Context context, IAmbientContextEventObserver service) {
+    public AmbientContextManager(Context context, IAmbientContextManager service) {
         mContext = context;
         mService = service;
     }
 
     /**
+     * Queries the {@link AmbientContextEvent} service status for the calling package, and
+     * sends the result to the {@link Consumer} right after the call. This is used by foreground
+     * apps to check whether the requested events are enabled for detection on the device.
+     * If all events are enabled for detection, the response has
+     * {@link AmbientContextManager#STATUS_SUCCESS}.
+     * If any of the events are not consented by user, the response has
+     * {@link AmbientContextManager#STATUS_ACCESS_DENIED}, and the app can
+     * call {@link #startConsentActivity} to redirect the user to the consent screen.
+     * <p />
+     *
+     * Example:
+     *
+     * <pre><code>
+     *   Set<Integer> eventTypes = new HashSet<>();
+     *   eventTypes.add(AmbientContextEvent.EVENT_COUGH);
+     *   eventTypes.add(AmbientContextEvent.EVENT_SNORE);
+     *
+     *   // Create Consumer
+     *   Consumer<Integer> statusConsumer = response -> {
+     *     int status = status.getStatusCode();
+     *     if (status == AmbientContextManager.STATUS_SUCCESS) {
+     *       // Show user it's enabled
+     *     } else if (status == AmbientContextManager.STATUS_ACCESS_DENIED) {
+     *       // Send user to grant access
+     *       startConsentActivity(eventTypes);
+     *     }
+     *   };
+     *
+     *   // Query status
+     *   AmbientContextManager ambientContextManager =
+     *       context.getSystemService(AmbientContextManager.class);
+     *   ambientContextManager.queryAmbientContextStatus(eventTypes, executor, statusConsumer);
+     * </code></pre>
+     *
+     * @param eventTypes The set of event codes to check status on.
+     * @param executor Executor on which to run the consumer callback.
+     * @param consumer The consumer that handles the status code.
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
+    public void queryAmbientContextServiceStatus(
+            @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> consumer) {
+        try {
+            RemoteCallback callback = new RemoteCallback(result -> {
+                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> consumer.accept(status));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            });
+            mService.queryServiceStatus(integerSetToIntArray(eventTypes),
+                    mContext.getOpPackageName(), callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests the consent data host to open an activity that allows users to modify consent.
+     *
+     * @param eventTypes The set of event codes to be consented.
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
+    public void startConsentActivity(
+            @NonNull @AmbientContextEvent.EventCode Set<Integer> eventTypes) {
+        try {
+            mService.startConsentActivity(
+                    integerSetToIntArray(eventTypes), mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @NonNull
+    private static int[] integerSetToIntArray(@NonNull Set<Integer> integerSet) {
+        int[] intArray = new int[integerSet.size()];
+        int i = 0;
+        for (Integer type : integerSet) {
+            intArray[i++] = type;
+        }
+        return intArray;
+    }
+
+    /**
      * Allows app to register as a {@link AmbientContextEvent} observer. The
      * observer receives a callback on the provided {@link PendingIntent} when the requested
      * event is detected. Registering another observer from the same package that has already been
      * registered will override the previous observer.
+     * <p />
+     *
+     * Example:
+     *
+     * <pre><code>
+     *   // Create request
+     *   AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
+     *       .addEventType(AmbientContextEvent.EVENT_COUGH)
+     *       .addEventType(AmbientContextEvent.EVENT_SNORE)
+     *       .build();
+     *
+     *   // Create PendingIntent for delivering detection results to my receiver
+     *   Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class)
+     *       .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+     *   PendingIntent pendingIntent =
+     *       PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+     *
+     *   // Create Consumer of service status
+     *   Consumer<Integer> statusConsumer = status -> {
+     *       if (status == AmbientContextManager.STATUS_ACCESS_DENIED) {
+     *         // User did not consent event detection. See #queryAmbientContextServiceStatus and
+     *         // #startConsentActivity
+     *       }
+     *   };
+     *
+     *   // Register as observer
+     *   AmbientContextManager ambientContextManager =
+     *       context.getSystemService(AmbientContextManager.class);
+     *   ambientContextManager.registerObserver(request, pendingIntent, executor, statusConsumer);
+     *
+     *   // Handle the list of {@link AmbientContextEvent}s in your receiver
+     *   {@literal @}Override
+     *   protected void onReceive(Context context, Intent intent) {
+     *     List<AmbientContextEvent> events = AmbientContextManager.getEventsFromIntent(intent);
+     *     if (!events.isEmpty()) {
+     *       // Do something useful with the events.
+     *     }
+     *   }
+     * </code></pre>
      *
      * @param request The request with events to observe.
-     * @param pendingIntent A mutable {@link PendingIntent} that will be dispatched when any
-     *                     requested event is detected.
+     * @param resultPendingIntent A mutable {@link PendingIntent} that will be dispatched after the
+     *                            requested events are detected.
+     * @param executor Executor on which to run the consumer callback.
+     * @param statusConsumer A consumer that handles the status code, which is returned
+     *                      right after the call.
      */
     @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT)
     public void registerObserver(
             @NonNull AmbientContextEventRequest request,
-            @NonNull PendingIntent pendingIntent) {
-        Preconditions.checkArgument(!pendingIntent.isImmutable());
+            @NonNull PendingIntent resultPendingIntent,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+        Preconditions.checkArgument(!resultPendingIntent.isImmutable());
         try {
-            mService.registerObserver(request, pendingIntent);
+            RemoteCallback callback = new RemoteCallback(result -> {
+                int statusCode =  result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> statusConsumer.accept(statusCode));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            });
+            mService.registerObserver(request, resultPendingIntent, callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
similarity index 66%
rename from core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl
rename to core/java/android/app/ambientcontext/IAmbientContextManager.aidl
index 9032fe1..3b15bcb 100644
--- a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl
+++ b/core/java/android/app/ambientcontext/IAmbientContextManager.aidl
@@ -18,13 +18,19 @@
 
 import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEventRequest;
+import android.os.RemoteCallback;
 
 /**
- * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents.
+ * Interface for an AmbientContextManager that provides access to AmbientContextEvents.
  *
  * @hide
  */
-oneway interface IAmbientContextEventObserver {
-    void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent);
+oneway interface IAmbientContextManager {
+    void registerObserver(in AmbientContextEventRequest request,
+        in PendingIntent resultPendingIntent,
+        in RemoteCallback statusCallback);
     void unregisterObserver(in String callingPackage);
+    void queryServiceStatus(in int[] eventTypes, in String callingPackage,
+        in RemoteCallback statusCallback);
+    void startConsentActivity(in int[] eventTypes, in String callingPackage);
 }
\ No newline at end of file
diff --git a/core/java/android/app/assist/ActivityId.java b/core/java/android/app/assist/ActivityId.java
index fb0d056..1cc4b02 100644
--- a/core/java/android/app/assist/ActivityId.java
+++ b/core/java/android/app/assist/ActivityId.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.service.contentcapture.ContentCaptureService;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.translation.UiTranslationManager;
@@ -38,7 +39,8 @@
  */
 @Immutable
 @SystemApi
-public class ActivityId {
+@TestApi
+public final class ActivityId implements Parcelable {
 
     /**
      * The identifier of the task this activity is in.
@@ -53,6 +55,7 @@
     /**
      * @hide
      */
+    @TestApi
     public ActivityId(int taskId, @Nullable IBinder activityId) {
         mTaskId = taskId;
         mActivityId = activityId;
@@ -87,13 +90,39 @@
     }
 
     /**
-     * @hide
+     * {@inheritDoc}
      */
-    public void writeToParcel(@NonNull Parcel dest, int parcelableFlags) {
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mTaskId);
         dest.writeStrongBinder(mActivityId);
     }
 
+    /**
+     * Creates {@link ActivityId} instances from parcels.
+     */
+    @NonNull
+    public static final Parcelable.Creator<ActivityId> CREATOR =
+            new Parcelable.Creator<ActivityId>() {
+                @Override
+                public ActivityId createFromParcel(Parcel parcel) {
+                    return new ActivityId(parcel);
+                }
+
+                @Override
+                public ActivityId[] newArray(int size) {
+                    return new ActivityId[size];
+                }
+            };
+
     @Override
     public String toString() {
         return "ActivityId { taskId = " + mTaskId + ", activityId = " + mActivityId + " }";
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index bd8ba9e..257530b 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -260,9 +260,9 @@
     }
 
     /**
-     * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit
-     * confirmation from the user before creating an association, even if such confirmation is not
-     * required.
+     * Indicates whether the application requires the {@link CompanionDeviceManager} service to
+     * collect an explicit confirmation from the user before creating an association, even if
+     * such confirmation is not required from the service's perspective.
      *
      * @see Builder#setForceConfirmation(boolean)
      */
@@ -391,9 +391,9 @@
         }
 
         /**
-         * Indicates whether the application would prefer the CompanionDeviceManager to collect an
-         * explicit confirmation from the user before creating an association, even if such
-         * confirmation is not required.
+         * Indicates whether the application requires the {@link CompanionDeviceManager} service to
+         * collect an explicit confirmation from the user before creating an association, even if
+         * such confirmation is not required from the service's perspective.
          */
         @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED)
         @NonNull
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 69033a6..1dbe04c 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -249,6 +249,7 @@
          * Closes the virtual device, stopping and tearing down any virtual displays,
          * audio policies, and event injection that's currently in progress.
          */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         public void close() {
             try {
                 mVirtualDevice.close();
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 1d0f7c0..45d0ad5 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -48,15 +48,16 @@
 
     /** @hide */
     @IntDef(prefix = "LOCK_STATE_",
-            value = {LOCK_STATE_ALWAYS_LOCKED, LOCK_STATE_ALWAYS_UNLOCKED})
+            value = {LOCK_STATE_DEFAULT, LOCK_STATE_ALWAYS_UNLOCKED})
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
     public @interface LockState {}
 
     /**
-     * Indicates that the lock state of the virtual device should be always locked.
+     * Indicates that the lock state of the virtual device will be the same as the default physical
+     * display.
      */
-    public static final int LOCK_STATE_ALWAYS_LOCKED = 0;
+    public static final int LOCK_STATE_DEFAULT = 0;
 
     /**
      * Indicates that the lock state of the virtual device should be always unlocked.
@@ -199,7 +200,7 @@
      */
     public static final class Builder {
 
-        private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED;
+        private @LockState int mLockState = LOCK_STATE_DEFAULT;
         private Set<UserHandle> mUsersWithMatchingAccounts;
         @Nullable private Set<ComponentName> mBlockedActivities;
         @Nullable private Set<ComponentName> mAllowedActivities;
@@ -207,9 +208,9 @@
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
          * is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
-         * The default is {@link #LOCK_STATE_ALWAYS_LOCKED}.
+         * The default is {@link #LOCK_STATE_DEFAULT}.
          *
-         * @param lockState The lock state, either {@link #LOCK_STATE_ALWAYS_LOCKED} or
+         * @param lockState The lock state, either {@link #LOCK_STATE_DEFAULT} or
          *   {@link #LOCK_STATE_ALWAYS_UNLOCKED}.
          */
         @RequiresPermission(value = ADD_ALWAYS_UNLOCKED_DISPLAY, conditional = true)
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index a0d348f..e35c2f4 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -753,7 +753,7 @@
      * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}.
      *
      * @param component The ComponentName of the activity to launch
-     * @param startActivityOptions Options to pass to startActivity
+     * @param startActivityOptions This parameter is no longer supported
      * @param user The UserHandle of the profile
      * @hide
      */
@@ -765,7 +765,8 @@
             Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user);
         }
         try {
-            return mService.getActivityLaunchIntent(component, startActivityOptions, user);
+            // due to b/209607104, startActivityOptions will be ignored
+            return mService.getActivityLaunchIntent(component, null /* opts */, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -860,7 +861,7 @@
      *
      * @param packageName The packageName of the shortcut
      * @param shortcutId The id of the shortcut
-     * @param opts Options to pass to the PendingIntent
+     * @param opts This parameter is no longer supported
      * @param user The UserHandle of the profile
      */
     @Nullable
@@ -872,8 +873,9 @@
             Log.i(TAG, "GetShortcutIntent " + packageName + "/" + shortcutId + " " + user);
         }
         try {
+            // due to b/209607104, opts will be ignored
             return mService.getShortcutIntent(
-                    mContext.getPackageName(), packageName, shortcutId, opts, user);
+                    mContext.getPackageName(), packageName, shortcutId, null /* opts */, user);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index feef9b2..ce549c3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3186,6 +3186,13 @@
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_SENSOR_HINGE_ANGLE = "android.hardware.sensor.hinge_angle";
 
+     /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device includes a heading sensor.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_SENSOR_HEADING = "android.hardware.sensor.heading";
+
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports exposing head tracker sensors from peripheral
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index d8295c9..f67a5b4 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -37,7 +37,6 @@
 import android.media.ImageReader;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -152,6 +151,100 @@
      */
     public static final int SURFACE_GROUP_ID_NONE = -1;
 
+    /**
+     * Default timestamp base.
+     *
+     * <p>The camera device decides the timestamp based on the properties of the
+     * output surface.</p>
+     *
+     * <li> For a SurfaceView output surface, the timestamp base is {@link
+     * #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}. The timestamp is overridden with choreographer
+     * pulses from the display subsystem for smoother display of camera frames. The timestamp
+     * is roughly in the same time base as {@link android.os.SystemClock#uptimeMillis}.</li>
+     * <li> For an output surface of MediaRecorder, MediaCodec, or ImageReader with {@link
+     * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usge flag, the timestamp base is
+     * {@link #TIMESTAMP_BASE_MONOTONIC}, which is roughly the same time base as
+     * {@link android.os.SystemClock#uptimeMillis}.</li>
+     * <li> For all other cases, the timestamp base is {@link #TIMESTAMP_BASE_SENSOR}, the same
+     * as what's specified by {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE}.</li>
+     *
+     * @see #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED
+     * @see #TIMESTAMP_BASE_MONOTONIC
+     * @see #TIMESTAMP_BASE_SENSOR
+     */
+    public static final int TIMESTAMP_BASE_DEFAULT = 0;
+
+    /**
+     * Timestamp base of {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE}.
+     *
+     * <p>The timestamps of the output images are in the time base as specified by {@link
+     * CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE}. The application can look up the
+     * corresponding result metadata for a particular output image using this timestamp.</p>
+     */
+    public static final int TIMESTAMP_BASE_SENSOR = 1;
+
+    /**
+     * Timestamp base roughly the same as {@link android.os.SystemClock#uptimeMillis}.
+     *
+     * <p>The timestamps of the output images are monotonically increasing, and are roughly in the
+     * same time base as {@link android.os.SystemClock#uptimeMillis}. The timestamps with this
+     * time base can be directly used for audio-video sync in video recording.</p>
+     *
+     * <p>If the camera device's {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is
+     * REALTIME, timestamps with this time base cannot directly match the timestamps in
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted} or the sensor timestamps in
+     * {@link android.hardware.camera2.CaptureResult}.</p>
+     */
+    public static final int TIMESTAMP_BASE_MONOTONIC = 2;
+
+    /**
+     * Timestamp base roughly the same as {@link android.os.SystemClock#elapsedRealtime}.
+     *
+     * <p>The timestamps of the output images are roughly in the
+     * same time base as {@link android.os.SystemClock#elapsedRealtime}. The timestamps with this
+     * time base cannot be directly used for audio-video sync in video recording.</p>
+     *
+     * <p>If the camera device's {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is
+     * UNKNOWN, timestamps with this time base cannot directly match the timestamps in
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted} or the sensor timestamps in
+     * {@link android.hardware.camera2.CaptureResult}.</p>
+     *
+     * <p>If using a REALTIME timestamp base on a device that supports only
+     * TIMESTAMP_SOURCE_UNKNOWN, the accuracy of timestamps is only what is guaranteed in the
+     * documentation for UNKNOWN. In particular, they have no guarantees about being accurate
+     * enough to use in fusing image data with the output of inertial sensors, for features such as
+     * image stabilization or augmented reality.</p>
+     */
+    public static final int TIMESTAMP_BASE_REALTIME = 3;
+
+    /**
+     * Timestamp is synchronized to choreographer.
+     *
+     * <p>The timestamp of the output images are overridden with choreographer pulses from the
+     * display subsystem for smoother display of camera frames. An output target of SurfaceView
+     * uses this time base by default.</p>
+     *
+     * <p>The choreographer synchronized timestamps are also reasonable to use when drawing to a
+     * TextureView. So this timestamp base can be used for a SurfaceTexture as part of a
+     * TextureView, in addition to SurfaceView.</p>
+     *
+     * <p>Timestamps with this time base cannot directly match the timestamps in
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted} or the sensor timestamps in
+     * {@link android.hardware.camera2.CaptureResult}. This timestamp base shouldn't be used if the
+     * timestamp needs to be used for audio-video synchronization.</p>
+     */
+    public static final int TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED = 4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"TIMESTAMP_BASE_"}, value =
+        {TIMESTAMP_BASE_DEFAULT,
+         TIMESTAMP_BASE_SENSOR,
+         TIMESTAMP_BASE_MONOTONIC,
+         TIMESTAMP_BASE_REALTIME,
+         TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED})
+    public @interface TimestampBase {};
+
     /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = {"SENSOR_PIXEL_MODE_"}, value =
@@ -367,6 +460,7 @@
         mSensorPixelModesUsed = new ArrayList<Integer>();
         mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
         mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
+        mTimestampBase = TIMESTAMP_BASE_DEFAULT;
     }
 
     /**
@@ -810,6 +904,47 @@
     }
 
     /**
+     * Set timestamp base for this output target
+     *
+     * <p>Timestamp base describes the time domain of images from this
+     * camera output and its relationship with {@link
+     * CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE}.</p>
+     *
+     * <p>If this function is not called, the timestamp base for this output
+     * is {@link #TIMESTAMP_BASE_DEFAULT}, with which the camera device adjusts
+     * timestamps based on the output target.</p>
+     *
+     * <p>See {@link #TIMESTAMP_BASE_DEFAULT}, {@link #TIMESTAMP_BASE_SENSOR},
+     * and {@link #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED} for details of each timestamp base.</p>
+     *
+     * @param timestampBase The timestamp base to be set.
+     *
+     * @throws IllegalArgumentException If the timestamp base isn't within the range of valid
+     *                                  values.
+     */
+    public void setTimestampBase(@TimestampBase int timestampBase) {
+        // Verify that the value is in range
+        if (timestampBase < TIMESTAMP_BASE_DEFAULT ||
+                timestampBase > TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED) {
+            throw new IllegalArgumentException("Not a valid timestamp base value " +
+                    timestampBase);
+        }
+        mTimestampBase = timestampBase;
+    }
+
+    /**
+     * Get the current timestamp base
+     *
+     * <p>If no {@link #setTimestampBase} is called first, this function returns
+     * {@link #TIMESTAMP_BASE_DEFAULT}.</p>
+     *
+     * @return The currently set timestamp base
+     */
+    public @TimestampBase int getTimestampBase() {
+        return mTimestampBase;
+    }
+
+    /**
      * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
      * instance.
      *
@@ -837,6 +972,7 @@
         this.mSensorPixelModesUsed = other.mSensorPixelModesUsed;
         this.mDynamicRangeProfile = other.mDynamicRangeProfile;
         this.mStreamUseCase = other.mStreamUseCase;
+        this.mTimestampBase = other.mTimestampBase;
     }
 
     /**
@@ -861,6 +997,7 @@
         int dynamicRangeProfile = source.readInt();
         DynamicRangeProfiles.checkProfileValue(dynamicRangeProfile);
 
+        int timestampBase = source.readInt();
         mSurfaceGroupId = surfaceSetId;
         mRotation = rotation;
         mSurfaces = surfaces;
@@ -885,6 +1022,7 @@
         mSensorPixelModesUsed = convertIntArrayToIntegerList(sensorPixelModesUsed);
         mDynamicRangeProfile = dynamicRangeProfile;
         mStreamUseCase = streamUseCase;
+        mTimestampBase = timestampBase;
     }
 
     /**
@@ -1002,6 +1140,7 @@
         dest.writeIntArray(convertIntegerToIntList(mSensorPixelModesUsed));
         dest.writeInt(mDynamicRangeProfile);
         dest.writeInt(mStreamUseCase);
+        dest.writeInt(mTimestampBase);
     }
 
     /**
@@ -1033,7 +1172,8 @@
                     mConfiguredGenerationId != other.mConfiguredGenerationId ||
                     !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
                     mIsMultiResolution != other.mIsMultiResolution ||
-                    mStreamUseCase != other.mStreamUseCase)
+                    mStreamUseCase != other.mStreamUseCase ||
+                    mTimestampBase != other.mTimestampBase)
                 return false;
             if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
                 return false;
@@ -1071,7 +1211,7 @@
                     mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0,
                     mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                     mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
-                    mDynamicRangeProfile, mStreamUseCase);
+                    mDynamicRangeProfile, mStreamUseCase, mTimestampBase);
         }
 
         return HashCodeHelpers.hashCode(
@@ -1080,7 +1220,7 @@
                 mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0,
                 mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                 mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
-                mDynamicRangeProfile, mStreamUseCase);
+                mDynamicRangeProfile, mStreamUseCase, mTimestampBase);
     }
 
     private static final String TAG = "OutputConfiguration";
@@ -1116,4 +1256,6 @@
     private int mDynamicRangeProfile;
     // Stream use case
     private int mStreamUseCase;
+    // Timestamp base
+    private int mTimestampBase;
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index eccbb40..75356d1 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -41,7 +41,9 @@
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodSession;
 
-class IInputMethodSessionWrapper extends IInputMethodSession.Stub
+/** @hide */
+// TODO(b/215636776): move IInputMethodSessionWrapper to proper package
+public class IInputMethodSessionWrapper extends IInputMethodSession.Stub
         implements HandlerCaller.Callback {
     private static final String TAG = "InputMethodWrapper";
 
diff --git a/core/java/android/inputmethodservice/InputMethodServiceInternal.java b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
index 7cd4ff6..09dbb2735 100644
--- a/core/java/android/inputmethodservice/InputMethodServiceInternal.java
+++ b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.inputmethod.InputConnection;
@@ -31,8 +32,11 @@
  * framework classes for internal use.
  *
  * <p>CAVEATS: {@link AbstractInputMethodService} does not support all the methods here.</p>
+ *
+ * @hide
  */
-interface InputMethodServiceInternal {
+// TODO(b/215636776): move InputMethodServiceInternal to proper package
+public interface InputMethodServiceInternal {
     /**
      * @return {@link Context} associated with the service.
      */
@@ -70,7 +74,8 @@
      * closed for you after you return.
      * @param args additional arguments to the dump request.
      */
-    default void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+    default void dump(@SuppressLint("UseParcelFileDescriptor") @NonNull FileDescriptor fd,
+            @NonNull PrintWriter fout, @NonNull String[] args) {
     }
 
     /**
@@ -81,6 +86,6 @@
      * @param where {@code where} parameter to be passed.
      * @param icProto {@code icProto} parameter to be passed.
      */
-    default void triggerServiceDump(String where, @Nullable byte[] icProto) {
+    default void triggerServiceDump(@NonNull String where, @Nullable byte[] icProto) {
     }
 }
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 9ef2579..6b7815d 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -53,8 +53,11 @@
  *
  * <p>See also {@link IInputContext} for the actual {@link android.os.Binder} IPC protocols under
  * the hood.</p>
+ *
+ * @hide
  */
-final class RemoteInputConnection implements InputConnection {
+// TODO(b/215636776): move RemoteInputConnection to proper package
+public final class RemoteInputConnection implements InputConnection {
     private static final String TAG = "RemoteInputConnection";
 
     private static final int MAX_WAIT_TIME_MILLIS = 2000;
@@ -95,7 +98,7 @@
     @NonNull
     private final CancellationGroup mCancellationGroup;
 
-    RemoteInputConnection(
+    public RemoteInputConnection(
             @NonNull WeakReference<InputMethodServiceInternal> inputMethodService,
             IInputContext inputContext, @NonNull CancellationGroup cancellationGroup) {
         mImsInternal = new InputMethodServiceInternalHolder(inputMethodService);
@@ -108,7 +111,7 @@
         return mInvoker.isSameConnection(inputContext);
     }
 
-    RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) {
+    public RemoteInputConnection(@NonNull RemoteInputConnection original, int sessionId) {
         mImsInternal = original.mImsInternal;
         mInvoker = original.mInvoker.cloneWithSessionId(sessionId);
         mCancellationGroup = original.mCancellationGroup;
diff --git a/core/java/android/net/IVpnManager.aidl b/core/java/android/net/IVpnManager.aidl
index 271efe4..070efa3 100644
--- a/core/java/android/net/IVpnManager.aidl
+++ b/core/java/android/net/IVpnManager.aidl
@@ -38,7 +38,7 @@
     /** VpnManager APIs */
     boolean provisionVpnProfile(in VpnProfile profile, String packageName);
     void deleteVpnProfile(String packageName);
-    void startVpnProfile(String packageName);
+    String startVpnProfile(String packageName);
     void stopVpnProfile(String packageName);
 
     /** Always-on VPN APIs */
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 036607b..ec752fd 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -159,8 +159,9 @@
             boolean isMetered,
             int maxMtu,
             boolean restrictToTestNetworks,
-            boolean excludeLocalRoutes) {
-        super(type, excludeLocalRoutes);
+            boolean excludeLocalRoutes,
+            boolean requiresInternetValidation) {
+        super(type, excludeLocalRoutes, requiresInternetValidation);
 
         checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address");
         checkNotNull(userIdentity, MISSING_PARAM_MSG_TMPL, "User Identity");
@@ -181,7 +182,7 @@
         mAllowedAlgorithms = Collections.unmodifiableList(new ArrayList<>(allowedAlgorithms));
         if (excludeLocalRoutes && !isBypassable) {
             throw new IllegalArgumentException(
-                    "Vpn should be byassable if excludeLocalRoutes is set");
+                    "Vpn must be bypassable if excludeLocalRoutes is set");
         }
 
         mIsBypassable = isBypassable;
@@ -238,7 +239,7 @@
      * that provides Authentication, and one that provides Encryption. Authenticated Encryption with
      * Associated Data (AEAD) algorithms are counted as providing Authentication and Encryption.
      *
-     * @param allowedAlgorithms The list to be validated
+     * @param algorithmNames The list to be validated
      */
     private static void validateAllowedAlgorithms(@NonNull List<String> algorithmNames) {
         // First, make sure no insecure algorithms were proposed.
@@ -400,7 +401,9 @@
                 mIsBypassable,
                 mIsMetered,
                 mMaxMtu,
-                mIsRestrictedToTestNetworks);
+                mIsRestrictedToTestNetworks,
+                mExcludeLocalRoutes,
+                mRequiresInternetValidation);
     }
 
     @Override
@@ -425,7 +428,8 @@
                 && mIsMetered == other.mIsMetered
                 && mMaxMtu == other.mMaxMtu
                 && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks
-                && mExcludeLocalRoutes == other.mExcludeLocalRoutes;
+                && mExcludeLocalRoutes == other.mExcludeLocalRoutes
+                && mRequiresInternetValidation == other.mRequiresInternetValidation;
     }
 
     /**
@@ -439,7 +443,7 @@
     @NonNull
     public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException {
         final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */,
-                mIsRestrictedToTestNetworks, mExcludeLocalRoutes);
+                mIsRestrictedToTestNetworks, mExcludeLocalRoutes, mRequiresInternetValidation);
         profile.type = mType;
         profile.server = mServerAddr;
         profile.ipsecIdentifier = mUserIdentity;
@@ -544,6 +548,7 @@
             Log.w(TAG, "ExcludeLocalRoutes should only be set in the bypassable VPN");
         }
         builder.setExcludeLocalRoutes(profile.excludeLocalRoutes && profile.isBypassable);
+        builder.setRequiresInternetValidation(profile.requiresInternetValidation);
 
         return builder.build();
     }
@@ -776,6 +781,7 @@
 
         @Nullable private ProxyInfo mProxyInfo;
         @NonNull private List<String> mAllowedAlgorithms = DEFAULT_ALGORITHMS;
+        private boolean mRequiresInternetValidation = false;
         private boolean mIsBypassable = false;
         private boolean mIsMetered = true;
         private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT;
@@ -988,6 +994,30 @@
         }
 
         /**
+         * Request that this VPN undergoes Internet validation.
+         *
+         * If this is true, the platform will perform basic validation checks for Internet
+         * connectivity over this VPN. If and when they succeed, the VPN network capabilities will
+         * reflect this by gaining the {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}
+         * capability.
+         *
+         * If this is false, the platform assumes the VPN either is always capable of reaching the
+         * Internet or intends not to. In this case, the VPN network capabilities will
+         * always gain the {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} capability
+         * immediately after it connects, whether it can reach public Internet destinations or not.
+         *
+         * @param requiresInternetValidation {@code true} if the framework should attempt to
+         *                                   validate this VPN for Internet connectivity. Defaults
+         *                                   to {@code false}.
+         */
+        @NonNull
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+        public Builder setRequiresInternetValidation(boolean requiresInternetValidation) {
+            mRequiresInternetValidation = requiresInternetValidation;
+            return this;
+        }
+
+        /**
          * Marks the VPN network as metered.
          *
          * <p>A VPN network is classified as metered when the user is sensitive to heavy data usage
@@ -1103,7 +1133,8 @@
                     mIsMetered,
                     mMaxMtu,
                     mIsRestrictedToTestNetworks,
-                    mExcludeLocalRoutes);
+                    mExcludeLocalRoutes,
+                    mRequiresInternetValidation);
         }
     }
 }
diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java
index 3c45799..8bd1c8d 100644
--- a/core/java/android/net/PlatformVpnProfile.java
+++ b/core/java/android/net/PlatformVpnProfile.java
@@ -16,10 +16,6 @@
 
 package android.net;
 
-import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK;
-import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA;
-import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 
@@ -67,11 +63,15 @@
 
     /** @hide */
     protected final boolean mExcludeLocalRoutes;
+    /** @hide */
+    protected final boolean mRequiresInternetValidation;
 
     /** @hide */
-    PlatformVpnProfile(@PlatformVpnType int type, boolean excludeLocalRoutes) {
+    PlatformVpnProfile(@PlatformVpnType int type, boolean excludeLocalRoutes,
+            boolean requiresValidation) {
         mType = type;
         mExcludeLocalRoutes = excludeLocalRoutes;
+        mRequiresInternetValidation = requiresValidation;
     }
 
     /** Returns the profile integer type. */
@@ -80,14 +80,30 @@
         return mType;
     }
 
-
     /**
-     * Returns if the local traffic is exempted from the VPN.
+     * Returns whether the local traffic is exempted from the VPN.
      */
     public final boolean getExcludeLocalRoutes() {
         return mExcludeLocalRoutes;
     }
 
+    /**
+     * Returns whether this VPN should undergo Internet validation.
+     *
+     * If this is true, the platform will perform basic validation checks for Internet
+     * connectivity over this VPN. If and when they succeed, the VPN network capabilities will
+     * reflect this by gaining the {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}
+     * capability.
+     *
+     * If this is false, the platform assumes the VPN either is always capable of reaching the
+     * Internet or intends not to. In this case, the VPN network capabilities will
+     * always gain the {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} capability
+     * immediately after it connects, whether it can reach public Internet destinations or not.
+     */
+    public final boolean getRequiresInternetValidation() {
+        return mRequiresInternetValidation;
+    }
+
     /** Returns a type string describing the VPN profile type */
     @NonNull
     public final String getTypeString() {
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 3193826..5aad997 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -211,25 +211,18 @@
     public static final int ERROR_CODE_NETWORK_TIMEOUT = 1;
 
     /**
-     * An error code to indicate that the connection is refused.
-     *
-     * @hide
-     */
-    public static final int ERROR_CODE_NETWORK_CONNECT = 2;
-
-    /**
      * An error code to indicate the connection was reset. (e.g. SocketException)
      *
      * @hide
      */
-    public static final int ERROR_CODE_NETWORK_CONNECTION_RESET = 3;
+    public static final int ERROR_CODE_NETWORK_RESET = 2;
 
     /**
      * An error code to indicate that there is an IOException.
      *
      * @hide
      */
-    public static final int ERROR_CODE_NETWORK_IO = 4;
+    public static final int ERROR_CODE_NETWORK_IO = 3;
 
     /** @hide */
     @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY,
@@ -317,17 +310,32 @@
     /**
      * Request the startup of a previously provisioned VPN.
      *
+     * @return A unique key corresponding to this session.
      * @throws SecurityException exception if user or device settings prevent this VPN from being
-     *     setup, or if user consent has not been granted
+     *         setup, or if user consent has not been granted
      */
-    public void startProvisionedVpnProfile() {
+    @NonNull
+    public String startProvisionedVpnProfileSession() {
         try {
-            mService.startVpnProfile(mContext.getOpPackageName());
+            return mService.startVpnProfile(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    /**
+     * Request the startup of a previously provisioned VPN.
+     *
+     * @throws SecurityException exception if user or device settings prevent this VPN from being
+     *         setup, or if user consent has not been granted
+     * @deprecated This method is replaced by startProvisionedVpnProfileSession which returns a
+     *             session key for the caller to diagnose the errors.
+     */
+    @Deprecated
+    public void startProvisionedVpnProfile() {
+        startProvisionedVpnProfileSession();
+    }
+
     /** Tear down the VPN provided by the calling app (if any) */
     public void stopProvisionedVpnProfile() {
         try {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 6dfa995..de1dc80 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -27,6 +27,8 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.location.GnssSignalQuality;
 import android.os.BatteryStatsManager.WifiState;
 import android.os.BatteryStatsManager.WifiSupplState;
@@ -43,16 +45,16 @@
 import android.util.Pair;
 import android.util.Printer;
 import android.util.SparseArray;
+import android.util.SparseDoubleArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BatterySipper;
-import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BatteryUsageStatsProvider;
-import com.android.internal.os.PowerCalculator;
+
+import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -65,7 +67,9 @@
 import java.util.Comparator;
 import java.util.Formatter;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 /**
@@ -3535,6 +3539,44 @@
         }
     }
 
+    /**
+     * Converts charge in mAh to string.
+     */
+    public static String formatCharge(double power) {
+        return formatValue(power);
+    }
+
+    /**
+     * Converts double to string, limiting small values to 3 significant figures.
+     */
+    private static String formatValue(double value) {
+        if (value == 0) return "0";
+
+        final String format;
+        if (value < .00001) {
+            format = "%.8f";
+        } else if (value < .0001) {
+            format = "%.7f";
+        } else if (value < .001) {
+            format = "%.6f";
+        } else if (value < .01) {
+            format = "%.5f";
+        } else if (value < .1) {
+            format = "%.4f";
+        } else if (value < 1) {
+            format = "%.3f";
+        } else if (value < 10) {
+            format = "%.2f";
+        } else if (value < 100) {
+            format = "%.1f";
+        } else {
+            format = "%.0f";
+        }
+
+        // Use English locale because this is never used in UI (only in checkin and dump).
+        return String.format(Locale.ENGLISH, format, value);
+    }
+
     private static long roundUsToMs(long timeUs) {
         return (timeUs + 500) / 1000;
     }
@@ -4021,7 +4063,7 @@
             sb.append("     ");
             sb.append(controllerName);
             sb.append(" Battery drain: ").append(
-                    PowerCalculator.formatCharge(powerDrainMaMs / MILLISECONDS_IN_HOUR));
+                    formatCharge(powerDrainMaMs / MILLISECONDS_IN_HOUR));
             sb.append("mAh");
             pw.println(sb.toString());
         }
@@ -4126,7 +4168,28 @@
      * Temporary for settings.
      */
     public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid) {
-        dumpCheckinLocked(context, pw, which, reqUid, BatteryStatsHelper.checkWifiOnly(context));
+        dumpCheckinLocked(context, pw, which, reqUid, checkWifiOnly(context));
+    }
+
+    private static final String[] CHECKIN_POWER_COMPONENT_LABELS =
+            new String[BatteryConsumer.POWER_COMPONENT_COUNT];
+    static {
+        // Assign individually to avoid future mismatch of indices
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_SCREEN] = "scrn";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_CPU] = "cpu";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_BLUETOOTH] = "blue";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_CAMERA] = "camera";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_AUDIO] = "audio";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_VIDEO] = "video";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_FLASHLIGHT] = "flashlight";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO] = "cell";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_SENSORS] = "sensors";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_GNSS] = "gnss";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_WIFI] = "wifi";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_MEMORY] = "memory";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_PHONE] = "phone";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY] = "ambi";
+        CHECKIN_POWER_COMPONENT_LABELS[BatteryConsumer.POWER_COMPONENT_IDLE] = "idle";
     }
 
     /**
@@ -4397,74 +4460,36 @@
             }
         }
 
-        final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
-        helper.create(this);
-        helper.refreshStats(which, UserHandle.USER_ALL);
-        final List<BatterySipper> sippers = helper.getUsageList();
-        if (sippers != null && sippers.size() > 0) {
-            dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA,
-                    PowerCalculator.formatCharge(helper.getPowerProfile().getBatteryCapacity()),
-                    PowerCalculator.formatCharge(helper.getComputedPower()),
-                    PowerCalculator.formatCharge(helper.getMinDrainedPower()),
-                    PowerCalculator.formatCharge(helper.getMaxDrainedPower()));
-            int uid = 0;
-            for (int i=0; i<sippers.size(); i++) {
-                final BatterySipper bs = sippers.get(i);
-                String label;
-                switch (bs.drainType) {
-                    case AMBIENT_DISPLAY:
-                        label = "ambi";
-                        break;
-                    case IDLE:
-                        label="idle";
-                        break;
-                    case CELL:
-                        label="cell";
-                        break;
-                    case PHONE:
-                        label="phone";
-                        break;
-                    case WIFI:
-                        label="wifi";
-                        break;
-                    case BLUETOOTH:
-                        label="blue";
-                        break;
-                    case SCREEN:
-                        label="scrn";
-                        break;
-                    case FLASHLIGHT:
-                        label="flashlight";
-                        break;
-                    case APP:
-                        uid = bs.uidObj.getUid();
-                        label = "uid";
-                        break;
-                    case USER:
-                        uid = UserHandle.getUid(bs.userId, 0);
-                        label = "user";
-                        break;
-                    case UNACCOUNTED:
-                        label = "unacc";
-                        break;
-                    case OVERCOUNTED:
-                        label = "over";
-                        break;
-                    case CAMERA:
-                        label = "camera";
-                        break;
-                    case MEMORY:
-                        label = "memory";
-                        break;
-                    default:
-                        label = "???";
-                }
-                dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label,
-                        PowerCalculator.formatCharge(bs.totalPowerMah),
-                        bs.shouldHide ? 1 : 0,
-                        PowerCalculator.formatCharge(bs.screenPowerMah),
-                        PowerCalculator.formatCharge(bs.proportionalSmearMah));
+        final BatteryUsageStats stats = getBatteryUsageStats(context);
+        dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA,
+                formatCharge(stats.getBatteryCapacity()),
+                formatCharge(stats.getConsumedPower()),
+                formatCharge(stats.getDischargedPowerRange().getLower()),
+                formatCharge(stats.getDischargedPowerRange().getUpper()));
+        final BatteryConsumer deviceConsumer = stats.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+        for (@BatteryConsumer.PowerComponent int powerComponent = 0;
+                powerComponent < BatteryConsumer.POWER_COMPONENT_COUNT; powerComponent++) {
+            String label = CHECKIN_POWER_COMPONENT_LABELS[powerComponent];
+            if (label == null) {
+                label = "???";
             }
+            dumpLine(pw, 0 /* uid */, category, POWER_USE_ITEM_DATA, label,
+                    formatCharge(deviceConsumer.getConsumedPower(powerComponent)),
+                    shouldHidePowerComponent(powerComponent) ? 1 : 0, "0", "0");
+        }
+
+        final ProportionalAttributionCalculator proportionalAttributionCalculator =
+                new ProportionalAttributionCalculator(context, stats);
+        final List<UidBatteryConsumer> uidBatteryConsumers = stats.getUidBatteryConsumers();
+        for (int i = 0; i < uidBatteryConsumers.size(); i++) {
+            UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+            dumpLine(pw, consumer.getUid(), category, POWER_USE_ITEM_DATA, "uid",
+                    formatCharge(consumer.getConsumedPower()),
+                    proportionalAttributionCalculator.isSystemBatteryConsumer(consumer) ? 1 : 0,
+                    formatCharge(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)),
+                    formatCharge(
+                            proportionalAttributionCalculator.getProportionalPowerMah(consumer)));
         }
 
         final long[] cpuFreqs = getCpuFreqs();
@@ -4897,11 +4922,11 @@
     }
 
     private void printmAh(PrintWriter printer, double power) {
-        printer.print(PowerCalculator.formatCharge(power));
+        printer.print(formatCharge(power));
     }
 
     private void printmAh(StringBuilder sb, double power) {
-        sb.append(PowerCalculator.formatCharge(power));
+        sb.append(formatCharge(power));
     }
 
     /**
@@ -4909,7 +4934,7 @@
      */
     public final void dumpLocked(Context context, PrintWriter pw, String prefix, int which,
             int reqUid) {
-        dumpLocked(context, pw, prefix, which, reqUid, BatteryStatsHelper.checkWifiOnly(context));
+        dumpLocked(context, pw, prefix, which, reqUid, checkWifiOnly(context));
     }
 
     @SuppressWarnings("unused")
@@ -4948,7 +4973,7 @@
             sb.setLength(0);
             sb.append(prefix);
                 sb.append("  Estimated battery capacity: ");
-            sb.append(PowerCalculator.formatCharge(estimatedBatteryCapacity));
+            sb.append(formatCharge(estimatedBatteryCapacity));
                 sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -4958,7 +4983,7 @@
             sb.setLength(0);
             sb.append(prefix);
             sb.append("  Last learned battery capacity: ");
-            sb.append(PowerCalculator.formatCharge(lastLearnedBatteryCapacity / 1000));
+            sb.append(formatCharge(lastLearnedBatteryCapacity / 1000));
             sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -4967,7 +4992,7 @@
             sb.setLength(0);
             sb.append(prefix);
             sb.append("  Min learned battery capacity: ");
-            sb.append(PowerCalculator.formatCharge(minLearnedBatteryCapacity / 1000));
+            sb.append(formatCharge(minLearnedBatteryCapacity / 1000));
             sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -4976,7 +5001,7 @@
             sb.setLength(0);
             sb.append(prefix);
             sb.append("  Max learned battery capacity: ");
-            sb.append(PowerCalculator.formatCharge(maxLearnedBatteryCapacity / 1000));
+            sb.append(formatCharge(maxLearnedBatteryCapacity / 1000));
             sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -5040,7 +5065,7 @@
             sb.setLength(0);
             sb.append(prefix);
                 sb.append("  Discharge: ");
-            sb.append(PowerCalculator.formatCharge(dischargeCount / 1000.0));
+            sb.append(formatCharge(dischargeCount / 1000.0));
                 sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -5050,7 +5075,7 @@
             sb.setLength(0);
             sb.append(prefix);
                 sb.append("  Screen off discharge: ");
-            sb.append(PowerCalculator.formatCharge(dischargeScreenOffCount / 1000.0));
+            sb.append(formatCharge(dischargeScreenOffCount / 1000.0));
                 sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -5060,7 +5085,7 @@
             sb.setLength(0);
             sb.append(prefix);
             sb.append("  Screen doze discharge: ");
-            sb.append(PowerCalculator.formatCharge(dischargeScreenDozeCount / 1000.0));
+            sb.append(formatCharge(dischargeScreenDozeCount / 1000.0));
             sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -5070,7 +5095,7 @@
             sb.setLength(0);
             sb.append(prefix);
                 sb.append("  Screen on discharge: ");
-            sb.append(PowerCalculator.formatCharge(dischargeScreenOnCount / 1000.0));
+            sb.append(formatCharge(dischargeScreenOnCount / 1000.0));
                 sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -5080,7 +5105,7 @@
             sb.setLength(0);
             sb.append(prefix);
             sb.append("  Device light doze discharge: ");
-            sb.append(PowerCalculator.formatCharge(dischargeLightDozeCount / 1000.0));
+            sb.append(formatCharge(dischargeLightDozeCount / 1000.0));
             sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -5090,7 +5115,7 @@
             sb.setLength(0);
             sb.append(prefix);
             sb.append("  Device deep doze discharge: ");
-            sb.append(PowerCalculator.formatCharge(dischargeDeepDozeCount / 1000.0));
+            sb.append(formatCharge(dischargeDeepDozeCount / 1000.0));
             sb.append(" mAh");
             pw.println(sb.toString());
         }
@@ -5224,7 +5249,7 @@
         for (int iu = 0; iu < NU; iu++) {
             final Uid u = uidStats.valueAt(iu);
 
-            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
+            final ArrayMap<String, ? extends Uid.Wakelock> wakelocks
                     = u.getWakelockStats();
             for (int iw=wakelocks.size()-1; iw>=0; iw--) {
                 final Uid.Wakelock wl = wakelocks.valueAt(iw);
@@ -5633,34 +5658,38 @@
                         .build());
         stats.dump(pw, prefix);
 
-        final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, wifiOnly);
-        helper.create(this);
-        helper.refreshStats(which, UserHandle.USER_ALL);
-
-        final List<BatterySipper> sippers = helper.getMobilemsppList();
-        if (sippers != null && sippers.size() > 0) {
-            pw.print(prefix); pw.println("  Per-app mobile ms per packet:");
+        List<UidMobileRadioStats> uidMobileRadioStats =
+                getUidMobileRadioStats(stats.getUidBatteryConsumers());
+        if (uidMobileRadioStats.size() > 0) {
+            pw.print(prefix);
+            pw.println("  Per-app mobile ms per packet:");
             long totalTime = 0;
-            for (int i=0; i<sippers.size(); i++) {
-                final BatterySipper bs = sippers.get(i);
+            for (int i = 0; i < uidMobileRadioStats.size(); i++) {
+                final UidMobileRadioStats mrs = uidMobileRadioStats.get(i);
                 sb.setLength(0);
-                sb.append(prefix); sb.append("    Uid ");
-                UserHandle.formatUid(sb, bs.uidObj.getUid());
+                sb.append(prefix);
+                sb.append("    Uid ");
+                UserHandle.formatUid(sb, mrs.uid);
                 sb.append(": ");
-                sb.append(PowerCalculator.formatCharge(bs.mobilemspp));
-                sb.append(" ("); sb.append(bs.mobileRxPackets+bs.mobileTxPackets);
-                sb.append(" packets over "); formatTimeMsNoSpace(sb, bs.mobileActive);
-                sb.append(") "); sb.append(bs.mobileActiveCount); sb.append("x");
-                pw.println(sb.toString());
-                totalTime += bs.mobileActive;
+                sb.append(formatValue(mrs.millisecondsPerPacket));
+                sb.append(" (");
+                sb.append(mrs.rxPackets + mrs.txPackets);
+                sb.append(" packets over ");
+                formatTimeMsNoSpace(sb, mrs.radioActiveMs);
+                sb.append(") ");
+                sb.append(mrs.radioActiveCount);
+                sb.append("x");
+                pw.println(sb);
+                totalTime += mrs.radioActiveMs;
             }
             sb.setLength(0);
             sb.append(prefix);
             sb.append("    TOTAL TIME: ");
             formatTimeMs(sb, totalTime);
-            sb.append("("); sb.append(formatRatioLocked(totalTime, whichBatteryRealtime));
+            sb.append("(");
+            sb.append(formatRatioLocked(totalTime, whichBatteryRealtime));
             sb.append(")");
-            pw.println(sb.toString());
+            pw.println(sb);
             pw.println();
         }
 
@@ -5680,13 +5709,13 @@
         };
 
         if (reqUid < 0) {
-            final Map<String, ? extends BatteryStats.Timer> kernelWakelocks
+            final Map<String, ? extends Timer> kernelWakelocks
                     = getKernelWakelockStats();
             if (kernelWakelocks.size() > 0) {
                 final ArrayList<TimerEntry> ktimers = new ArrayList<>();
-                for (Map.Entry<String, ? extends BatteryStats.Timer> ent
+                for (Map.Entry<String, ? extends Timer> ent
                         : kernelWakelocks.entrySet()) {
-                    final BatteryStats.Timer timer = ent.getValue();
+                    final Timer timer = ent.getValue();
                     final long totalTimeMillis = computeWakeLock(timer, rawRealtime, which);
                     if (totalTimeMillis > 0) {
                         ktimers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis));
@@ -5871,8 +5900,7 @@
                     packets = 1;
                 }
                 sb.append(" @ ");
-                sb.append(PowerCalculator.formatCharge(
-                        uidMobileActiveTime / 1000 / (double) packets));
+                sb.append(formatCharge(uidMobileActiveTime / 1000 / (double) packets));
                 sb.append(" mspp");
                 pw.println(sb.toString());
             }
@@ -6080,7 +6108,7 @@
                 }
             }
 
-            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks
+            final ArrayMap<String, ? extends Uid.Wakelock> wakelocks
                     = u.getWakelockStats();
             long totalFullWakelock = 0, totalPartialWakelock = 0, totalWindowWakelock = 0;
             long totalDrawWakelock = 0;
@@ -6308,7 +6336,7 @@
             uidActivity |= printTimer(pw, sb, u.getAudioTurnedOnTimer(), rawRealtime, which,
                     prefix, "Audio");
 
-            final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            final SparseArray<? extends Uid.Sensor> sensors = u.getSensorStats();
             final int NSE = sensors.size();
             for (int ise=0; ise<NSE; ise++) {
                 final Uid.Sensor se = sensors.valueAt(ise);
@@ -6451,7 +6479,7 @@
                 }
             }
 
-            final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
+            final ArrayMap<String, ? extends Uid.Proc> processStats
                     = u.getProcessStats();
             for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
                 final Uid.Proc ps = processStats.valueAt(ipr);
@@ -6525,7 +6553,7 @@
                 }
             }
 
-            final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats
+            final ArrayMap<String, ? extends Uid.Pkg> packageStats
                     = u.getPackageStats();
             for (int ipkg=packageStats.size()-1; ipkg>=0; ipkg--) {
                 pw.print(prefix); pw.print("    Apk "); pw.print(packageStats.keyAt(ipkg));
@@ -6542,7 +6570,7 @@
                 }
                 final ArrayMap<String, ? extends  Uid.Pkg.Serv> serviceStats = ps.getServiceStats();
                 for (int isvc=serviceStats.size()-1; isvc>=0; isvc--) {
-                    final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+                    final Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
                     final long startTime = ss.getStartTime(batteryUptime, which);
                     final int starts = ss.getStarts(which);
                     final int launches = ss.getLaunches(which);
@@ -7625,21 +7653,20 @@
         proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion());
 
         if ((flags & DUMP_DAILY_ONLY) == 0) {
-            final BatteryStatsHelper helper = new BatteryStatsHelper(context, false,
-                    (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
-            helper.create(this);
-            helper.refreshStats(STATS_SINCE_CHARGED, UserHandle.USER_ALL);
-
-            dumpProtoAppsLocked(proto, helper, apps);
-            dumpProtoSystemLocked(proto, helper);
+            final BatteryUsageStats stats = getBatteryUsageStats(context);
+            ProportionalAttributionCalculator proportionalAttributionCalculator =
+                    new ProportionalAttributionCalculator(context, stats);
+            dumpProtoAppsLocked(proto, stats, apps, proportionalAttributionCalculator);
+            dumpProtoSystemLocked(proto, stats);
         }
 
         proto.end(bToken);
         proto.flush();
     }
 
-    private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryStatsHelper helper,
-            List<ApplicationInfo> apps) {
+    private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryUsageStats stats,
+            List<ApplicationInfo> apps,
+            ProportionalAttributionCalculator proportionalAttributionCalculator) {
         final int which = STATS_SINCE_CHARGED;
         final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
         final long rawRealtimeMs = SystemClock.elapsedRealtime();
@@ -7660,17 +7687,11 @@
             }
         }
 
-        SparseArray<BatterySipper> uidToSipper = new SparseArray<>();
-        final List<BatterySipper> sippers = helper.getUsageList();
-        if (sippers != null) {
-            for (int i = 0; i < sippers.size(); ++i) {
-                final BatterySipper bs = sippers.get(i);
-                if (bs.drainType != BatterySipper.DrainType.APP) {
-                    // Others are handled by dumpProtoSystemLocked()
-                    continue;
-                }
-                uidToSipper.put(bs.uidObj.getUid(), bs);
-            }
+        SparseArray<UidBatteryConsumer> uidToConsumer = new SparseArray<>();
+        final List<UidBatteryConsumer> consumers = stats.getUidBatteryConsumers();
+        for (int i = consumers.size() - 1; i >= 0; --i) {
+            final UidBatteryConsumer bs = consumers.get(i);
+            uidToConsumer.put(bs.getUid(), bs);
         }
 
         SparseArray<? extends Uid> uidStats = getUidStats();
@@ -7936,14 +7957,16 @@
             proto.end(nToken);
 
             // Power use item (POWER_USE_ITEM_DATA)
-            BatterySipper bs = uidToSipper.get(uid);
-            if (bs != null) {
+            UidBatteryConsumer consumer = uidToConsumer.get(uid);
+            if (consumer != null) {
                 final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
-                proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
-                proto.write(UidProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
-                proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, consumer.getConsumedPower());
+                proto.write(UidProto.PowerUseItem.SHOULD_HIDE,
+                        proportionalAttributionCalculator.isSystemBatteryConsumer(consumer));
+                proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH,
+                        consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN));
                 proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
-                        bs.proportionalSmearMah);
+                        proportionalAttributionCalculator.getProportionalPowerMah(consumer));
                 proto.end(bsToken);
             }
 
@@ -8189,7 +8212,7 @@
         }
     }
 
-    private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) {
+    private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryUsageStats stats) {
         final long sToken = proto.start(BatteryStatsProto.SYSTEM);
         final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
         final long rawRealtimeMs = SystemClock.elapsedRealtime();
@@ -8433,77 +8456,65 @@
                 multicastWakeLockCountTotal);
         proto.end(wmctToken);
 
-        // Power use item (POWER_USE_ITEM_DATA)
-        final List<BatterySipper> sippers = helper.getUsageList();
-        if (sippers != null) {
-            for (int i = 0; i < sippers.size(); ++i) {
-                final BatterySipper bs = sippers.get(i);
-                int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
-                int uid = 0;
-                switch (bs.drainType) {
-                    case AMBIENT_DISPLAY:
-                        n = SystemProto.PowerUseItem.AMBIENT_DISPLAY;
-                        break;
-                    case IDLE:
-                        n = SystemProto.PowerUseItem.IDLE;
-                        break;
-                    case CELL:
-                        n = SystemProto.PowerUseItem.CELL;
-                        break;
-                    case PHONE:
-                        n = SystemProto.PowerUseItem.PHONE;
-                        break;
-                    case WIFI:
-                        n = SystemProto.PowerUseItem.WIFI;
-                        break;
-                    case BLUETOOTH:
-                        n = SystemProto.PowerUseItem.BLUETOOTH;
-                        break;
-                    case SCREEN:
-                        n = SystemProto.PowerUseItem.SCREEN;
-                        break;
-                    case FLASHLIGHT:
-                        n = SystemProto.PowerUseItem.FLASHLIGHT;
-                        break;
-                    case APP:
-                        // dumpProtoAppsLocked will handle this.
-                        continue;
-                    case USER:
-                        n = SystemProto.PowerUseItem.USER;
-                        uid = UserHandle.getUid(bs.userId, 0);
-                        break;
-                    case UNACCOUNTED:
-                        n = SystemProto.PowerUseItem.UNACCOUNTED;
-                        break;
-                    case OVERCOUNTED:
-                        n = SystemProto.PowerUseItem.OVERCOUNTED;
-                        break;
-                    case CAMERA:
-                        n = SystemProto.PowerUseItem.CAMERA;
-                        break;
-                    case MEMORY:
-                        n = SystemProto.PowerUseItem.MEMORY;
-                        break;
-                }
-                final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
-                proto.write(SystemProto.PowerUseItem.NAME, n);
-                proto.write(SystemProto.PowerUseItem.UID, uid);
-                proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
-                proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
-                proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
-                proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
-                        bs.proportionalSmearMah);
-                proto.end(puiToken);
+        final BatteryConsumer deviceConsumer = stats.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+        for (int powerComponent = 0; powerComponent < BatteryConsumer.POWER_COMPONENT_COUNT;
+                powerComponent++) {
+            int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
+            switch (powerComponent) {
+                case BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY:
+                    n = SystemProto.PowerUseItem.AMBIENT_DISPLAY;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_IDLE:
+                    n = SystemProto.PowerUseItem.IDLE;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO:
+                    n = SystemProto.PowerUseItem.CELL;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_PHONE:
+                    n = SystemProto.PowerUseItem.PHONE;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_WIFI:
+                    n = SystemProto.PowerUseItem.WIFI;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_BLUETOOTH:
+                    n = SystemProto.PowerUseItem.BLUETOOTH;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_SCREEN:
+                    n = SystemProto.PowerUseItem.SCREEN;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_FLASHLIGHT:
+                    n = SystemProto.PowerUseItem.FLASHLIGHT;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_CAMERA:
+                    n = SystemProto.PowerUseItem.CAMERA;
+                    break;
+                case BatteryConsumer.POWER_COMPONENT_MEMORY:
+                    n = SystemProto.PowerUseItem.MEMORY;
+                    break;
             }
+            final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
+            proto.write(SystemProto.PowerUseItem.NAME, n);
+            proto.write(SystemProto.PowerUseItem.UID, 0);
+            proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH,
+                    deviceConsumer.getConsumedPower(powerComponent));
+            proto.write(SystemProto.PowerUseItem.SHOULD_HIDE,
+                    shouldHidePowerComponent(powerComponent));
+            proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, 0);
+            proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH, 0);
+            proto.end(puiToken);
         }
 
         // Power use summary (POWER_USE_SUMMARY_DATA)
         final long pusToken = proto.start(SystemProto.POWER_USE_SUMMARY);
         proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
-                helper.getPowerProfile().getBatteryCapacity());
-        proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
-        proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
-        proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
+                stats.getBatteryCapacity());
+        proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, stats.getConsumedPower());
+        proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH,
+                stats.getDischargedPowerRange().getLower());
+        proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH,
+                stats.getDischargedPowerRange().getUpper());
         proto.end(pusToken);
 
         // RPM stats (RESOURCE_POWER_MANAGER_DATA)
@@ -8579,4 +8590,162 @@
 
         proto.end(sToken);
     }
+
+    /**
+     * Returns true if the device does not have data-capable telephony.
+     */
+    public static boolean checkWifiOnly(Context context) {
+        final TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        if (tm == null) {
+            return false;
+        }
+        return !tm.isDataCapable();
+    }
+
+    private BatteryUsageStats getBatteryUsageStats(Context context) {
+        final BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, this);
+        final BatteryUsageStatsQuery query =
+                new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(0).build();
+        return provider.getBatteryUsageStats(query);
+    }
+
+    private boolean shouldHidePowerComponent(int powerComponent) {
+        return powerComponent == BatteryConsumer.POWER_COMPONENT_IDLE
+                || powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO
+                || powerComponent == BatteryConsumer.POWER_COMPONENT_SCREEN
+                || powerComponent == BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY;
+    }
+
+    private static class ProportionalAttributionCalculator {
+        private static final double SYSTEM_BATTERY_CONSUMER = -1;
+        private final PackageManager mPackageManager;
+        private final HashSet<String> mSystemAndServicePackages;
+        private final SparseDoubleArray mProportionalPowerMah;
+
+        ProportionalAttributionCalculator(Context context, BatteryUsageStats stats) {
+            mPackageManager = context.getPackageManager();
+            final Resources resources = context.getResources();
+            final String[] systemPackageArray = resources.getStringArray(
+                    com.android.internal.R.array.config_batteryPackageTypeSystem);
+            final String[] servicePackageArray = resources.getStringArray(
+                    com.android.internal.R.array.config_batteryPackageTypeService);
+            mSystemAndServicePackages =
+                    new HashSet<>(systemPackageArray.length + servicePackageArray.length);
+            for (String packageName : systemPackageArray) {
+                mSystemAndServicePackages.add(packageName);
+            }
+            for (String packageName : servicePackageArray) {
+                mSystemAndServicePackages.add(packageName);
+            }
+
+            final List<UidBatteryConsumer> uidBatteryConsumers = stats.getUidBatteryConsumers();
+            mProportionalPowerMah =  new SparseDoubleArray(uidBatteryConsumers.size());
+            double systemPowerMah = 0;
+            for (int i = uidBatteryConsumers.size() - 1; i >= 0; i--) {
+                UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+                final int uid = consumer.getUid();
+                if (isSystemUid(uid)) {
+                    mProportionalPowerMah.put(uid, SYSTEM_BATTERY_CONSUMER);
+                    systemPowerMah += consumer.getConsumedPower();
+                }
+            }
+
+            final double totalRemainingPower = stats.getConsumedPower() - systemPowerMah;
+            if (Math.abs(totalRemainingPower) > 1e-3) {
+                for (int i = uidBatteryConsumers.size() - 1; i >= 0; i--) {
+                    UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+                    final int uid = consumer.getUid();
+                    if (mProportionalPowerMah.get(uid) != SYSTEM_BATTERY_CONSUMER) {
+                        final double power = consumer.getConsumedPower();
+                        mProportionalPowerMah.put(uid,
+                                power + systemPowerMah * power / totalRemainingPower);
+                    }
+                }
+            }
+        }
+
+        boolean isSystemBatteryConsumer(UidBatteryConsumer consumer) {
+            return mProportionalPowerMah.get(consumer.getUid()) < 0;
+        }
+
+        double getProportionalPowerMah(UidBatteryConsumer consumer) {
+            final double powerMah = mProportionalPowerMah.get(consumer.getUid());
+            return powerMah >= 0 ? powerMah : 0;
+        }
+
+        /**
+         * Check whether the UID is one of the system UIDs or a service UID
+         */
+        private boolean isSystemUid(int uid) {
+            if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
+                return true;
+            }
+
+            final String[] packages = mPackageManager.getPackagesForUid(uid);
+            if (packages == null) {
+                return false;
+            }
+
+            for (String packageName : packages) {
+                if (mSystemAndServicePackages.contains(packageName)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    private static class UidMobileRadioStats {
+        public final int uid;
+        public final long rxPackets;
+        public final long txPackets;
+        public final long radioActiveMs;
+        public final int radioActiveCount;
+        public final double millisecondsPerPacket;
+
+        private UidMobileRadioStats(int uid, long rxPackets, long txPackets, long radioActiveMs,
+                int radioActiveCount, double millisecondsPerPacket) {
+            this.uid = uid;
+            this.txPackets = txPackets;
+            this.rxPackets = rxPackets;
+            this.radioActiveMs = radioActiveMs;
+            this.radioActiveCount = radioActiveCount;
+            this.millisecondsPerPacket = millisecondsPerPacket;
+        }
+    }
+
+    private List<UidMobileRadioStats> getUidMobileRadioStats(
+            List<UidBatteryConsumer> uidBatteryConsumers) {
+        final SparseArray<? extends Uid> uidStats = getUidStats();
+        List<UidMobileRadioStats> uidMobileRadioStats = Lists.newArrayList();
+        for (int i = 0; i < uidBatteryConsumers.size(); i++) {
+            final UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
+            if (consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) == 0) {
+                continue;
+            }
+
+            final int uid = consumer.getUid();
+            final Uid u = uidStats.get(uid);
+            final long rxPackets = u.getNetworkActivityPackets(
+                    BatteryStats.NETWORK_MOBILE_RX_DATA, STATS_SINCE_CHARGED);
+            final long txPackets = u.getNetworkActivityPackets(
+                    BatteryStats.NETWORK_MOBILE_TX_DATA, STATS_SINCE_CHARGED);
+            if (rxPackets == 0 && txPackets == 0) {
+                continue;
+            }
+            final long radioActiveMs = u.getMobileRadioActiveTime(STATS_SINCE_CHARGED) / 1000;
+            final int radioActiveCount = u.getMobileRadioActiveCount(STATS_SINCE_CHARGED);
+            final double msPerPacket = (double) radioActiveMs / (rxPackets + txPackets);
+            if (msPerPacket == 0) {
+                continue;
+            }
+            uidMobileRadioStats.add(
+                    new UidMobileRadioStats(uid, rxPackets, txPackets, radioActiveMs,
+                            radioActiveCount, msPerPacket));
+        }
+        uidMobileRadioStats.sort(
+                (lhs, rhs) -> Double.compare(rhs.millisecondsPerPacket, lhs.millisecondsPerPacket));
+        return uidMobileRadioStats;
+    }
 }
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index d41a5fe..58f9336 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -597,20 +597,21 @@
 
         dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers());
         dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers());
+        pw.println();
     }
 
     private void printPowerComponent(PrintWriter pw, String prefix, String label,
             double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
         StringBuilder sb = new StringBuilder();
         sb.append(prefix).append("      ").append(label).append(": ")
-                .append(PowerCalculator.formatCharge(devicePowerMah));
+                .append(BatteryStats.formatCharge(devicePowerMah));
         if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
                 && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
             sb.append(" [");
             sb.append(BatteryConsumer.powerModelToString(powerModel));
             sb.append("]");
         }
-        sb.append(" apps: ").append(PowerCalculator.formatCharge(appsPowerMah));
+        sb.append(" apps: ").append(BatteryStats.formatCharge(appsPowerMah));
         if (durationMs != 0) {
             sb.append(" duration: ");
             BatteryStats.formatTimeMs(sb, durationMs);
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 48e1116..6051712 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -26,8 +26,6 @@
 import android.util.TypedXmlSerializer;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.os.PowerCalculator;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -170,7 +168,7 @@
                 separator = " ";
                 sb.append(key.toShortString());
                 sb.append("=");
-                sb.append(PowerCalculator.formatCharge(componentPower));
+                sb.append(BatteryStats.formatCharge(componentPower));
 
                 if (durationMs != 0) {
                     sb.append(" (");
@@ -194,7 +192,7 @@
             separator = " ";
             sb.append(getCustomPowerComponentName(customComponentId));
             sb.append("=");
-            sb.append(PowerCalculator.formatCharge(customComponentPower));
+            sb.append(BatteryStats.formatCharge(customComponentPower));
         }
 
         pw.print(sb);
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 1a082d1..a1ff923 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -136,7 +136,7 @@
         }
 
         sb.append(" ").append(processStateToString(processState)).append(": ")
-                .append(PowerCalculator.formatCharge(power));
+                .append(BatteryStats.formatCharge(power));
     }
 
     static UidBatteryConsumer create(BatteryConsumerData data) {
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index a5a40ad..4e69952 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -26,6 +26,7 @@
      * @param dsuSlot Name used to identify this installation
      * @return true if the call succeeds
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean startInstallation(@utf8InCpp String dsuSlot);
 
     /**
@@ -36,6 +37,7 @@
      * @param readOnly True if this partition is readOnly
      * @return true if the call succeeds
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean createPartition(@utf8InCpp String name, long size, boolean readOnly);
 
     /**
@@ -43,12 +45,14 @@
      *
      * @return true if the partition installation completes without error.
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean closePartition();
 
     /**
      * Finish a previously started installation. Installations without
      * a cooresponding finishInstallation() will be cleaned up during device boot.
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean finishInstallation();
 
     /**
@@ -57,6 +61,7 @@
      *
      * @return GsiProgress
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     GsiProgress getInstallationProgress();
 
     /**
@@ -66,21 +71,25 @@
      *
      * @return true if the call succeeds
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean abort();
 
     /**
      * @return true if the device is running an DynamicAnroid image
      */
+    @RequiresNoPermission
     boolean isInUse();
 
     /**
      * @return true if the device has an DynamicSystem image installed
      */
+    @RequiresNoPermission
     boolean isInstalled();
 
     /**
      * @return true if the device has an DynamicSystem image enabled
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean isEnabled();
 
     /**
@@ -88,6 +97,7 @@
      *
      * @return true if the call succeeds
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean remove();
 
     /**
@@ -97,6 +107,7 @@
      *
      * @return true if the call succeeds
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean setEnable(boolean enable, boolean oneShot);
 
     /**
@@ -106,6 +117,7 @@
      * @param fd            fd that points to a ashmem
      * @param size          size of the ashmem file
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean setAshmem(in ParcelFileDescriptor fd, long size);
 
     /**
@@ -115,6 +127,7 @@
      * @param bytes         number of bytes that can be read from stream.
      * @return              true on success, false otherwise.
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean submitFromAshmem(long bytes);
 
     /**
@@ -124,10 +137,12 @@
      * @return              true on success, false if partition doesn't have a
      *                      valid VBMeta block to retrieve the AVB key from.
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     boolean getAvbPublicKey(out AvbPublicKey dst);
 
     /**
      * Returns the suggested scratch partition size for overlayFS.
      */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     long suggestScratchSize();
 }
diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl
index 02db274..68b5679 100644
--- a/core/java/android/os/logcat/ILogcatManagerService.aidl
+++ b/core/java/android/os/logcat/ILogcatManagerService.aidl
@@ -22,7 +22,5 @@
 interface ILogcatManagerService {
     void startThread(in int uid, in int gid, in int pid, in int fd);
     void finishThread(in int uid, in int gid, in int pid, in int fd);
-    void approve(in int uid, in int gid, in int pid, in int fd);
-    void decline(in int uid, in int gid, in int pid, in int fd);
 }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bf94ab5..464567b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4501,6 +4501,13 @@
         public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
 
         /**
+         * The amount of time in milliseconds before the device goes to sleep or begins to dream
+         * after a period of inactivity while it is docked.
+         * @hide
+         */
+        public static final String SCREEN_OFF_TIMEOUT_DOCKED = "screen_off_timeout_docked";
+
+        /**
          * The screen backlight brightness between 0 and 255.
          */
         @Readable
@@ -10314,6 +10321,34 @@
                 "nearby_fast_pair_settings_devices_component";
 
         /**
+         * Current provider of the component for requesting ambient context consent.
+         * Default value in @string/config_defaultAmbientContextConsentComponent.
+         * No VALIDATOR as this setting will not be backed up.
+         * @hide
+         */
+        public static final String AMBIENT_CONTEXT_CONSENT_COMPONENT =
+                "ambient_context_consent_component";
+
+        /**
+         * Current provider of the intent extra key for the caller's package name while
+         * requesting ambient context consent.
+         * No VALIDATOR as this setting will not be backed up.
+         * @hide
+         */
+        public static final String AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY =
+                "ambient_context_package_name_key";
+
+        /**
+         * Current provider of the intent extra key for the event code int array while
+         * requesting ambient context consent.
+         * Default value in @string/config_ambientContextEventArrayExtraKey.
+         * No VALIDATOR as this setting will not be backed up.
+         * @hide
+         */
+        public static final String AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY =
+                "ambient_context_event_array_key";
+
+        /**
          * Controls whether aware is enabled.
          * @hide
          */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 3ff0161..5ff9263 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -2216,651 +2216,6 @@
     }
 
     /**
-     * Columns for the "rcs_*" tables used by {@link android.telephony.ims.RcsMessageStore} classes.
-     *
-     * @hide - not meant for public use
-     */
-    public interface RcsColumns {
-        // TODO(sahinc): Turn this to true once the schema finalizes, so that people can update
-        //  their messaging databases. NOTE: move the switch/case update in MmsSmsDatabaseHelper to
-        //  the latest version of the database before turning this flag to true.
-        boolean IS_RCS_TABLE_SCHEMA_CODE_COMPLETE = false;
-
-        /**
-         * The authority for the content provider
-         */
-        String AUTHORITY = "rcs";
-
-        /**
-         * The URI to start building upon to use {@link com.android.providers.telephony.RcsProvider}
-         */
-        Uri CONTENT_AND_AUTHORITY = Uri.parse("content://" + AUTHORITY);
-
-        /**
-         * The value to be used whenever a transaction that expects an integer to be returned
-         * failed.
-         */
-        int TRANSACTION_FAILED = Integer.MIN_VALUE;
-
-        /**
-         * The value that denotes a timestamp was not set before (e.g. a message that is not
-         * delivered yet will not have a DELIVERED_TIMESTAMP)
-         */
-        long TIMESTAMP_NOT_SET = 0;
-
-        /**
-         * The table that {@link android.telephony.ims.RcsThread} gets persisted to
-         */
-        interface RcsThreadColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsThread}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String RCS_THREAD_URI_PART = "thread";
-
-            /**
-             * The URI to query or modify {@link android.telephony.ims.RcsThread} via the content
-             * provider.
-             */
-            Uri RCS_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, RCS_THREAD_URI_PART);
-
-            /**
-             * The unique identifier of an {@link android.telephony.ims.RcsThread}
-             */
-            String RCS_THREAD_ID_COLUMN = "rcs_thread_id";
-        }
-
-        /**
-         * The table that {@link android.telephony.ims.Rcs1To1Thread} gets persisted to
-         */
-        interface Rcs1To1ThreadColumns extends RcsThreadColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.Rcs1To1Thread}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String RCS_1_TO_1_THREAD_URI_PART = "p2p_thread";
-
-            /**
-             * The URI to query or modify {@link android.telephony.ims.Rcs1To1Thread}s via the
-             * content provider. Can also insert to this URI to create a new 1-to-1 thread. When
-             * performing an insert, ensure that the provided content values contain the other
-             * participant's ID under the key
-             * {@link RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN}
-             */
-            Uri RCS_1_TO_1_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    RCS_1_TO_1_THREAD_URI_PART);
-
-            /**
-             * The SMS/MMS thread to fallback to in case of an RCS outage
-             */
-            String FALLBACK_THREAD_ID_COLUMN = "rcs_fallback_thread_id";
-        }
-
-        /**
-         * The table that {@link android.telephony.ims.RcsGroupThread} gets persisted to
-         */
-        interface RcsGroupThreadColumns extends RcsThreadColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsGroupThread}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String RCS_GROUP_THREAD_URI_PART = "group_thread";
-
-            /**
-             * The URI to query or modify {@link android.telephony.ims.RcsGroupThread}s via the
-             * content provider
-             */
-            Uri RCS_GROUP_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    RCS_GROUP_THREAD_URI_PART);
-
-            /**
-             * The owner/admin of the {@link android.telephony.ims.RcsGroupThread}
-             */
-            String OWNER_PARTICIPANT_COLUMN = "owner_participant";
-
-            /**
-             * The user visible name of the group
-             */
-            String GROUP_NAME_COLUMN = "group_name";
-
-            /**
-             * The user visible icon of the group
-             */
-            String GROUP_ICON_COLUMN = "group_icon";
-
-            /**
-             * The RCS conference URI for this group
-             */
-            String CONFERENCE_URI_COLUMN = "conference_uri";
-        }
-
-        /**
-         * The view that enables polling from all types of RCS threads at once
-         */
-        interface RcsUnifiedThreadColumns extends RcsThreadColumns, Rcs1To1ThreadColumns,
-                RcsGroupThreadColumns {
-            /**
-             * The type of this {@link android.telephony.ims.RcsThread}
-             */
-            String THREAD_TYPE_COLUMN = "thread_type";
-
-            /**
-             * Integer returned as a result from a database query that denotes the thread is 1 to 1
-             */
-            int THREAD_TYPE_1_TO_1 = 0;
-
-            /**
-             * Integer returned as a result from a database query that denotes the thread is 1 to 1
-             */
-            int THREAD_TYPE_GROUP = 1;
-        }
-
-        /**
-         * The table that {@link android.telephony.ims.RcsParticipant} gets persisted to
-         */
-        interface RcsParticipantColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsParticipant}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String RCS_PARTICIPANT_URI_PART = "participant";
-
-            /**
-             * The URI to query or modify {@link android.telephony.ims.RcsParticipant}s via the
-             * content provider
-             */
-            Uri RCS_PARTICIPANT_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    RCS_PARTICIPANT_URI_PART);
-
-            /**
-             * The unique identifier of the entry in the database
-             */
-            String RCS_PARTICIPANT_ID_COLUMN = "rcs_participant_id";
-
-            /**
-             * A foreign key on canonical_address table, also used by SMS/MMS
-             */
-            String CANONICAL_ADDRESS_ID_COLUMN = "canonical_address_id";
-
-            /**
-             * The user visible RCS alias for this participant.
-             */
-            String RCS_ALIAS_COLUMN = "rcs_alias";
-        }
-
-        /**
-         * Additional constants to enable access to {@link android.telephony.ims.RcsParticipant}
-         * related data
-         */
-        interface RcsParticipantHelpers extends RcsParticipantColumns {
-            /**
-             * The view that unifies "rcs_participant" and "canonical_addresses" tables for easy
-             * access to participant address.
-             */
-            String RCS_PARTICIPANT_WITH_ADDRESS_VIEW = "rcs_participant_with_address_view";
-
-            /**
-             * The view that unifies "rcs_participant", "canonical_addresses" and
-             * "rcs_thread_participant" junction table to get full information on participants that
-             * contribute to threads.
-             */
-            String RCS_PARTICIPANT_WITH_THREAD_VIEW = "rcs_participant_with_thread_view";
-        }
-
-        /**
-         * The table that {@link android.telephony.ims.RcsMessage} gets persisted to
-         */
-        interface RcsMessageColumns {
-            /**
-             * Denotes the type of this message (i.e.
-             * {@link android.telephony.ims.RcsIncomingMessage} or
-             * {@link android.telephony.ims.RcsOutgoingMessage}
-             */
-            String MESSAGE_TYPE_COLUMN = "rcs_message_type";
-
-            /**
-             * The unique identifier for the message in the database - i.e. the primary key.
-             */
-            String MESSAGE_ID_COLUMN = "rcs_message_row_id";
-
-            /**
-             * The globally unique RCS identifier for the message. Please see 4.4.5.2 - GSMA
-             * RCC.53 (RCS Device API 1.6 Specification)
-             */
-            String GLOBAL_ID_COLUMN = "rcs_message_global_id";
-
-            /**
-             * The subscription where this message was sent from/to.
-             */
-            String SUB_ID_COLUMN = "sub_id";
-
-            /**
-             * The sending status of the message.
-             * @see android.telephony.ims.RcsMessage.RcsMessageStatus
-             */
-            String STATUS_COLUMN = "status";
-
-            /**
-             * The creation timestamp of the message.
-             */
-            String ORIGINATION_TIMESTAMP_COLUMN = "origination_timestamp";
-
-            /**
-             * The text content of the message.
-             */
-            String MESSAGE_TEXT_COLUMN = "rcs_text";
-
-            /**
-             * The latitude content of the message, if it contains a location.
-             */
-            String LATITUDE_COLUMN = "latitude";
-
-            /**
-             * The longitude content of the message, if it contains a location.
-             */
-            String LONGITUDE_COLUMN = "longitude";
-        }
-
-        /**
-         * The table that additional information of {@link android.telephony.ims.RcsIncomingMessage}
-         * gets persisted to.
-         */
-        interface RcsIncomingMessageColumns extends RcsMessageColumns {
-            /**
-             The path that should be used for referring to
-             * {@link android.telephony.ims.RcsIncomingMessage}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String INCOMING_MESSAGE_URI_PART = "incoming_message";
-
-            /**
-             * The URI to query incoming messages through
-             * {@link com.android.providers.telephony.RcsProvider}
-             */
-            Uri INCOMING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    INCOMING_MESSAGE_URI_PART);
-
-            /**
-             * The ID of the {@link android.telephony.ims.RcsParticipant} that sent this message
-             */
-            String SENDER_PARTICIPANT_ID_COLUMN = "sender_participant";
-
-            /**
-             * The timestamp of arrival for this message.
-             */
-            String ARRIVAL_TIMESTAMP_COLUMN = "arrival_timestamp";
-
-            /**
-             * The time when the recipient has read this message.
-             */
-            String SEEN_TIMESTAMP_COLUMN = "seen_timestamp";
-        }
-
-        /**
-         * The table that additional information of {@link android.telephony.ims.RcsOutgoingMessage}
-         * gets persisted to.
-         */
-        interface RcsOutgoingMessageColumns extends RcsMessageColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsOutgoingMessage}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String OUTGOING_MESSAGE_URI_PART = "outgoing_message";
-
-            /**
-             * The URI to query or modify {@link android.telephony.ims.RcsOutgoingMessage}s via the
-             * content provider
-             */
-            Uri OUTGOING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    OUTGOING_MESSAGE_URI_PART);
-        }
-
-        /**
-         * The delivery information of an {@link android.telephony.ims.RcsOutgoingMessage}
-         */
-        interface RcsMessageDeliveryColumns extends RcsOutgoingMessageColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsOutgoingMessageDelivery}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String DELIVERY_URI_PART = "delivery";
-
-            /**
-             * The timestamp of delivery of this message.
-             */
-            String DELIVERED_TIMESTAMP_COLUMN = "delivered_timestamp";
-
-            /**
-             * The time when the recipient has read this message.
-             */
-            String SEEN_TIMESTAMP_COLUMN = "seen_timestamp";
-        }
-
-        /**
-         * The views that allow querying {@link android.telephony.ims.RcsIncomingMessage} and
-         * {@link android.telephony.ims.RcsOutgoingMessage} at the same time.
-         */
-        interface RcsUnifiedMessageColumns extends RcsIncomingMessageColumns,
-                RcsOutgoingMessageColumns {
-            /**
-             * The path that is used to query all {@link android.telephony.ims.RcsMessage} in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String UNIFIED_MESSAGE_URI_PART = "message";
-
-            /**
-             * The URI to query all types of {@link android.telephony.ims.RcsMessage}s
-             */
-            Uri UNIFIED_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    UNIFIED_MESSAGE_URI_PART);
-
-            /**
-             * The name of the view that unites rcs_message and rcs_incoming_message tables.
-             */
-            String UNIFIED_INCOMING_MESSAGE_VIEW = "unified_incoming_message_view";
-
-            /**
-             * The name of the view that unites rcs_message and rcs_outgoing_message tables.
-             */
-            String UNIFIED_OUTGOING_MESSAGE_VIEW = "unified_outgoing_message_view";
-
-            /**
-             * The column that shows from which table the message entry came from.
-             */
-            String MESSAGE_TYPE_COLUMN = "message_type";
-
-            /**
-             * Integer returned as a result from a database query that denotes that the message is
-             * an incoming message
-             */
-            int MESSAGE_TYPE_INCOMING = 1;
-
-            /**
-             * Integer returned as a result from a database query that denotes that the message is
-             * an outgoing message
-             */
-            int MESSAGE_TYPE_OUTGOING = 0;
-        }
-
-        /**
-         * The table that {@link android.telephony.ims.RcsFileTransferPart} gets persisted to.
-         */
-        interface RcsFileTransferColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsFileTransferPart}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String FILE_TRANSFER_URI_PART = "file_transfer";
-
-            /**
-             * The URI to query or modify {@link android.telephony.ims.RcsFileTransferPart}s via the
-             * content provider
-             */
-            Uri FILE_TRANSFER_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    FILE_TRANSFER_URI_PART);
-
-            /**
-             * The globally unique file transfer ID for this RCS file transfer.
-             */
-            String FILE_TRANSFER_ID_COLUMN = "rcs_file_transfer_id";
-
-            /**
-             * The RCS session ID for this file transfer. The ID is implementation dependent but
-             * should be unique.
-             */
-            String SESSION_ID_COLUMN = "session_id";
-
-            /**
-             * The URI that points to the content of this file transfer
-             */
-            String CONTENT_URI_COLUMN = "content_uri";
-
-            /**
-             * The file type of this file transfer in bytes. The validity of types is not enforced
-             * in {@link android.telephony.ims.RcsMessageStore} APIs.
-             */
-            String CONTENT_TYPE_COLUMN = "content_type";
-
-            /**
-             * The size of the file transfer in bytes.
-             */
-            String FILE_SIZE_COLUMN = "file_size";
-
-            /**
-             * Number of bytes that was successfully transmitted for this file transfer
-             */
-            String SUCCESSFULLY_TRANSFERRED_BYTES = "transfer_offset";
-
-            /**
-             * The status of this file transfer
-             * @see android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus
-             */
-            String TRANSFER_STATUS_COLUMN = "transfer_status";
-
-            /**
-             * The on-screen width of the file transfer, if it contains multi-media
-             */
-            String WIDTH_COLUMN = "width";
-
-            /**
-             * The on-screen height of the file transfer, if it contains multi-media
-             */
-            String HEIGHT_COLUMN = "height";
-
-            /**
-             * The duration of the content in milliseconds if this file transfer contains
-             * multi-media
-             */
-            String DURATION_MILLIS_COLUMN = "duration";
-
-            /**
-             * The URI to the preview of the content of this file transfer
-             */
-            String PREVIEW_URI_COLUMN = "preview_uri";
-
-            /**
-             * The type of the preview of the content of this file transfer. The validity of types
-             * is not enforced in {@link android.telephony.ims.RcsMessageStore} APIs.
-             */
-            String PREVIEW_TYPE_COLUMN = "preview_type";
-        }
-
-        /**
-         * The table that holds the information for
-         * {@link android.telephony.ims.RcsGroupThreadEvent} and its subclasses.
-         */
-        interface RcsThreadEventColumns {
-            /**
-             * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
-             * refer to participant joined events (example URI:
-             * {@code content://rcs/group_thread/3/participant_joined_event})
-             */
-            String PARTICIPANT_JOINED_URI_PART = "participant_joined_event";
-
-            /**
-             * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
-             * refer to participant left events. (example URI:
-             * {@code content://rcs/group_thread/3/participant_left_event/4})
-             */
-            String PARTICIPANT_LEFT_URI_PART = "participant_left_event";
-
-            /**
-             * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
-             * refer to name changed events. (example URI:
-             * {@code content://rcs/group_thread/3/name_changed_event})
-             */
-            String NAME_CHANGED_URI_PART = "name_changed_event";
-
-            /**
-             * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
-             * refer to icon changed events. (example URI:
-             * {@code content://rcs/group_thread/3/icon_changed_event})
-             */
-            String ICON_CHANGED_URI_PART = "icon_changed_event";
-
-            /**
-             * The unique ID of this event in the database, i.e. the primary key
-             */
-            String EVENT_ID_COLUMN = "event_id";
-
-            /**
-             * The type of this event
-             *
-             * @see RcsEventTypes
-             */
-            String EVENT_TYPE_COLUMN = "event_type";
-
-            /**
-             * The timestamp in milliseconds of when this event happened
-             */
-            String TIMESTAMP_COLUMN = "origination_timestamp";
-
-            /**
-             * The participant that generated this event
-             */
-            String SOURCE_PARTICIPANT_ID_COLUMN = "source_participant";
-
-            /**
-             * The receiving participant of this event if this was an
-             * {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} or
-             * {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent}
-             */
-            String DESTINATION_PARTICIPANT_ID_COLUMN = "destination_participant";
-
-            /**
-             * The URI for the new icon of the group thread if this was an
-             * {@link android.telephony.ims.RcsGroupThreadIconChangedEvent}
-             */
-            String NEW_ICON_URI_COLUMN = "new_icon_uri";
-
-            /**
-             * The URI for the new name of the group thread if this was an
-             * {@link android.telephony.ims.RcsGroupThreadNameChangedEvent}
-             */
-            String NEW_NAME_COLUMN = "new_name";
-        }
-
-        /**
-         * The table that {@link android.telephony.ims.RcsParticipantAliasChangedEvent} gets
-         * persisted to
-         */
-        interface RcsParticipantEventColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsParticipantAliasChangedEvent}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String ALIAS_CHANGE_EVENT_URI_PART = "alias_change_event";
-
-            /**
-             * The new alias of the participant
-             */
-            String NEW_ALIAS_COLUMN = "new_alias";
-        }
-
-        /**
-         * These values are used in {@link com.android.providers.telephony.RcsProvider} to determine
-         * what kind of event is present in the storage.
-         */
-        interface RcsEventTypes {
-            /**
-             * Integer constant that is stored in the
-             * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
-             * is of type {@link android.telephony.ims.RcsParticipantAliasChangedEvent}
-             */
-            int PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE = 1;
-
-            /**
-             * Integer constant that is stored in the
-             * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
-             * is of type {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent}
-             */
-            int PARTICIPANT_JOINED_EVENT_TYPE = 2;
-
-            /**
-             * Integer constant that is stored in the
-             * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
-             * is of type {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent}
-             */
-            int PARTICIPANT_LEFT_EVENT_TYPE = 4;
-
-            /**
-             * Integer constant that is stored in the
-             * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
-             * is of type {@link android.telephony.ims.RcsGroupThreadIconChangedEvent}
-             */
-            int ICON_CHANGED_EVENT_TYPE = 8;
-
-            /**
-             * Integer constant that is stored in the
-             * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
-             * is of type {@link android.telephony.ims.RcsGroupThreadNameChangedEvent}
-             */
-            int NAME_CHANGED_EVENT_TYPE = 16;
-        }
-
-        /**
-         * The view that allows unified querying across all events
-         */
-        interface RcsUnifiedEventHelper extends RcsParticipantEventColumns, RcsThreadEventColumns {
-            /**
-             * The path that should be used for referring to
-             * {@link android.telephony.ims.RcsEvent}s in
-             * {@link com.android.providers.telephony.RcsProvider} URIs.
-             */
-            String RCS_EVENT_QUERY_URI_PATH = "event";
-
-            /**
-             * The URI to query {@link android.telephony.ims.RcsEvent}s via the content provider.
-             */
-            Uri RCS_EVENT_QUERY_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
-                    RCS_EVENT_QUERY_URI_PATH);
-        }
-
-        /**
-         * Allows RCS specific canonical address handling.
-         */
-        interface RcsCanonicalAddressHelper {
-            /**
-             * Returns the canonical address ID for a canonical address, if now row exists, this
-             * will add a row and return its ID. This helper works against the same table used by
-             * the SMS and MMS threads, but is accessible only by the phone process for use by RCS
-             * message storage.
-             *
-             * @throws IllegalArgumentException if unable to retrieve or create the canonical
-             *                                  address entry.
-             */
-            static long getOrCreateCanonicalAddressId(
-                    ContentResolver contentResolver, String canonicalAddress) {
-
-                Uri.Builder uriBuilder = CONTENT_AND_AUTHORITY.buildUpon();
-                uriBuilder.appendPath("canonical-address");
-                uriBuilder.appendQueryParameter("address", canonicalAddress);
-                Uri uri = uriBuilder.build();
-
-                try (Cursor cursor = contentResolver.query(uri, null, null, null)) {
-                    if (cursor != null && cursor.moveToFirst()) {
-                        return cursor.getLong(cursor.getColumnIndex(CanonicalAddressesColumns._ID));
-                    } else {
-                        Rlog.e(TAG, "getOrCreateCanonicalAddressId returned no rows");
-                    }
-                }
-
-                Rlog.e(TAG, "getOrCreateCanonicalAddressId failed");
-                throw new IllegalArgumentException(
-                        "Unable to find or allocate a canonical address ID");
-            }
-        }
-    }
-
-    /**
      * Contains all MMS messages.
      */
     public static final class Mms implements BaseMmsColumns {
diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl
similarity index 80%
rename from core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl
rename to core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl
index 4dc6466..4bb29b2 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl
+++ b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.ambientcontext;
+package android.service.ambientcontext;
 
-parcelable AmbientContextEventResponse;
\ No newline at end of file
+parcelable AmbientContextDetectionResult;
\ No newline at end of file
diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java
new file mode 100644
index 0000000..227194e
--- /dev/null
+++ b/core/java/android/service/ambientcontext/AmbientContextDetectionResult.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 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.service.ambientcontext;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a {@code AmbientContextEvent} detection result reported by the detection service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AmbientContextDetectionResult implements Parcelable {
+
+    /**
+     * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
+     *
+     * @hide
+     */
+    public static final String RESULT_RESPONSE_BUNDLE_KEY =
+            "android.app.ambientcontext.AmbientContextDetectionResultBundleKey";
+    @NonNull private final List<AmbientContextEvent> mEvents;
+    @NonNull private final String mPackageName;
+
+    AmbientContextDetectionResult(
+            @NonNull List<AmbientContextEvent> events,
+            @NonNull String packageName) {
+        this.mEvents = events;
+        AnnotationValidations.validate(NonNull.class, null, mEvents);
+        this.mPackageName = packageName;
+        AnnotationValidations.validate(NonNull.class, null, mPackageName);
+    }
+
+    /**
+     * A list of detected event.
+     */
+    @SuppressLint("ConcreteCollection")
+    public @NonNull List<AmbientContextEvent> getEvents() {
+        return mEvents;
+    }
+
+    /**
+     * The package to deliver the response to.
+     */
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    @Override
+    public String toString() {
+        return "AmbientContextEventResponse { "
+                + "events = " + mEvents + ", " + "packageName = " + mPackageName + " }";
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        byte flg = 0;
+        dest.writeByte(flg);
+        dest.writeParcelableList(mEvents, flags);
+        dest.writeString(mPackageName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    AmbientContextDetectionResult(@NonNull android.os.Parcel in) {
+        byte flg = in.readByte();
+        ArrayList<AmbientContextEvent> events = new ArrayList<>();
+        in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(),
+                AmbientContextEvent.class);
+        String packageName = in.readString();
+
+        this.mEvents = events;
+        AnnotationValidations.validate(
+                NonNull.class, null, mEvents);
+        this.mPackageName = packageName;
+        AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+    }
+
+    public static final @NonNull Creator<AmbientContextDetectionResult> CREATOR =
+            new Creator<AmbientContextDetectionResult>() {
+        @Override
+        public AmbientContextDetectionResult[] newArray(int size) {
+            return new AmbientContextDetectionResult[size];
+        }
+
+        @Override
+        public AmbientContextDetectionResult createFromParcel(@NonNull android.os.Parcel in) {
+            return new AmbientContextDetectionResult(in);
+        }
+    };
+
+    /**
+     * A builder for {@link AmbientContextDetectionResult}
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static final class Builder {
+        private @NonNull ArrayList<AmbientContextEvent> mEvents;
+        private @NonNull String mPackageName;
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Adds an event to the builder.
+         */
+        public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) {
+            checkNotUsed();
+            if (mEvents == null) {
+                mBuilderFieldsSet |= 0x1;
+                mEvents = new ArrayList<>();
+            }
+            mEvents.add(value);
+            return this;
+        }
+
+        /**
+         * The package to deliver the response to.
+         */
+        public @NonNull Builder setPackageName(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mPackageName = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull AmbientContextDetectionResult build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mEvents = new ArrayList<>();
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mPackageName = "";
+            }
+            AmbientContextDetectionResult o = new AmbientContextDetectionResult(
+                    mEvents,
+                    mPackageName);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java
index dccfe36..6224aa1 100644
--- a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java
+++ b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java
@@ -16,13 +16,13 @@
 
 package android.service.ambientcontext;
 
+import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
-import android.app.ambientcontext.AmbientContextEventResponse;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -64,15 +64,6 @@
     public static final String SERVICE_INTERFACE =
             "android.service.ambientcontext.AmbientContextDetectionService";
 
-    /**
-     * The key for the bundle the parameter of {@code RemoteCallback#sendResult}. Implementation
-     * should set bundle result with this key.
-     *
-     * @hide
-     */
-    public static final String RESPONSE_BUNDLE_KEY =
-            "android.service.ambientcontext.EventResponseKey";
-
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
@@ -82,19 +73,30 @@
                 @Override
                 public void startDetection(
                         @NonNull AmbientContextEventRequest request, String packageName,
-                        RemoteCallback callback) {
+                        RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
                     Objects.requireNonNull(request);
-                    Objects.requireNonNull(callback);
-                    Consumer<AmbientContextEventResponse> consumer =
-                            response -> {
+                    Objects.requireNonNull(packageName);
+                    Objects.requireNonNull(detectionResultCallback);
+                    Objects.requireNonNull(statusCallback);
+                    Consumer<AmbientContextDetectionResult> detectionResultConsumer =
+                            result -> {
                                 Bundle bundle = new Bundle();
                                 bundle.putParcelable(
-                                        AmbientContextDetectionService.RESPONSE_BUNDLE_KEY,
-                                        response);
-                                callback.sendResult(bundle);
+                                        AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY,
+                                        result);
+                                detectionResultCallback.sendResult(bundle);
+                            };
+                    Consumer<AmbientContextDetectionServiceStatus> statusConsumer =
+                            status -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionServiceStatus
+                                                .STATUS_RESPONSE_BUNDLE_KEY,
+                                        status);
+                                statusCallback.sendResult(bundle);
                             };
                     AmbientContextDetectionService.this.onStartDetection(
-                            request, packageName, consumer);
+                            request, packageName, detectionResultConsumer, statusConsumer);
                     Slog.d(TAG, "startDetection " + request);
                 }
 
@@ -104,29 +106,52 @@
                     Objects.requireNonNull(packageName);
                     AmbientContextDetectionService.this.onStopDetection(packageName);
                 }
+
+                /** {@inheritDoc} */
+                @Override
+                public void queryServiceStatus(
+                        @AmbientContextEvent.EventCode int[] eventTypes,
+                        String packageName,
+                        RemoteCallback callback) {
+                    Objects.requireNonNull(eventTypes);
+                    Objects.requireNonNull(packageName);
+                    Objects.requireNonNull(callback);
+                    Consumer<AmbientContextDetectionServiceStatus> consumer =
+                            response -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionServiceStatus
+                                                .STATUS_RESPONSE_BUNDLE_KEY,
+                                        response);
+                                callback.sendResult(bundle);
+                            };
+                    AmbientContextDetectionService.this.onQueryServiceStatus(
+                            eventTypes, packageName, consumer);
+                }
             };
         }
         return null;
     }
 
     /**
-     * Starts detection and provides detected events to the consumer. The ongoing detection will
-     * keep running, until onStopDetection is called. If there were previously requested
+     * Starts detection and provides detected events to the statusConsumer. The ongoing detection
+     * will keep running, until onStopDetection is called. If there were previously requested
      * detection from the same package, the previous request will be replaced with the new request.
      * The implementation should keep track of whether the user consented each requested
-     * AmbientContextEvent for the app. If not consented, the response should set status
-     * STATUS_ACCESS_DENIED and include an action PendingIntent for the app to redirect the user
-     * to the consent screen.
+     * AmbientContextEvent for the app. If not consented, the statusConsumer should get a response
+     * with STATUS_ACCESS_DENIED.
      *
-     * @param request The request with events to detect, optional detection window and other
-     *                options.
+     * @param request The request with events to detect.
      * @param packageName the requesting app's package name
-     * @param consumer the consumer for the detected event
+     * @param detectionResultConsumer the consumer for the detected event
+     * @param statusConsumer the consumer for the service status.
      */
+    @BinderThread
     public abstract void onStartDetection(
             @NonNull AmbientContextEventRequest request,
             @NonNull String packageName,
-            @NonNull Consumer<AmbientContextEventResponse> consumer);
+            @NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer,
+            @NonNull Consumer<AmbientContextDetectionServiceStatus> statusConsumer);
 
     /**
      * Stops detection of the events. Events that are not being detected will be ignored.
@@ -134,4 +159,19 @@
      * @param packageName stops detection for the given package.
      */
     public abstract void onStopDetection(@NonNull String packageName);
+
+    /**
+     * Called when a query for the detection status occurs. The implementation should check
+     * the detection status of the requested events for the package, and provide results in a
+     * {@link AmbientContextDetectionServiceStatus} for the consumer.
+     *
+     * @param eventTypes The events to check for status.
+     * @param packageName the requesting app's package name
+     * @param consumer the consumer for the query results
+     */
+    @BinderThread
+    public abstract void onQueryServiceStatus(
+            @NonNull int[] eventTypes,
+            @NonNull String packageName,
+            @NonNull Consumer<AmbientContextDetectionServiceStatus> consumer);
 }
diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl
similarity index 79%
copy from core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl
copy to core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl
index 4dc6466..979cf7b 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl
+++ b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.app.ambientcontext;
+package android.service.ambientcontext;
 
-parcelable AmbientContextEventResponse;
\ No newline at end of file
+parcelable AmbientContextDetectionServiceStatus;
\ No newline at end of file
diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java
new file mode 100644
index 0000000..3e92f39
--- /dev/null
+++ b/core/java/android/service/ambientcontext/AmbientContextDetectionServiceStatus.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 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.service.ambientcontext;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.ambientcontext.AmbientContextManager;
+import android.app.ambientcontext.AmbientContextManager.StatusCode;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+/**
+ * Represents a status for the {@code AmbientContextDetectionService}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AmbientContextDetectionServiceStatus implements Parcelable {
+    /**
+     * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
+     *
+     * @hide
+     */
+    public static final String STATUS_RESPONSE_BUNDLE_KEY =
+            "android.app.ambientcontext.AmbientContextServiceStatusBundleKey";
+
+    @StatusCode private final int mStatusCode;
+    @NonNull private final String mPackageName;
+
+    AmbientContextDetectionServiceStatus(
+            @StatusCode int statusCode,
+            @NonNull String packageName) {
+        this.mStatusCode = statusCode;
+        AnnotationValidations.validate(StatusCode.class, null, mStatusCode);
+        this.mPackageName = packageName;
+    }
+
+    /**
+     * The status of the service.
+     */
+    public @StatusCode int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /**
+     * The package to deliver the response to.
+     */
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    @Override
+    public String toString() {
+        return "AmbientContextDetectionServiceStatus { " + "statusCode = " + mStatusCode + ", "
+                + "packageName = " + mPackageName + " }";
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        byte flg = 0;
+        dest.writeByte(flg);
+        dest.writeInt(mStatusCode);
+        dest.writeString(mPackageName);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    AmbientContextDetectionServiceStatus(@NonNull android.os.Parcel in) {
+        byte flg = in.readByte();
+        int statusCode = in.readInt();
+        String packageName = in.readString();
+
+        this.mStatusCode = statusCode;
+        AnnotationValidations.validate(
+                StatusCode.class, null, mStatusCode);
+        this.mPackageName = packageName;
+        AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+    }
+
+    public static final @NonNull Creator<AmbientContextDetectionServiceStatus> CREATOR =
+            new Creator<AmbientContextDetectionServiceStatus>() {
+        @Override
+        public AmbientContextDetectionServiceStatus[] newArray(int size) {
+            return new AmbientContextDetectionServiceStatus[size];
+        }
+
+        @Override
+        public AmbientContextDetectionServiceStatus createFromParcel(
+                @NonNull android.os.Parcel in) {
+            return new AmbientContextDetectionServiceStatus(in);
+        }
+    };
+
+    /**
+     * A builder for {@link AmbientContextDetectionServiceStatus}
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static final class Builder {
+        private @StatusCode int mStatusCode;
+        private @NonNull String mPackageName;
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        /**
+         * Sets the status of the service.
+         */
+        public @NonNull Builder setStatusCode(@StatusCode int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mStatusCode = value;
+            return this;
+        }
+
+        /**
+         * The package to deliver the response to.
+         */
+        public @NonNull Builder setPackageName(@NonNull String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mPackageName = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull AmbientContextDetectionServiceStatus build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mStatusCode = AmbientContextManager.STATUS_UNKNOWN;
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mPackageName = "";
+            }
+            AmbientContextDetectionServiceStatus o = new AmbientContextDetectionServiceStatus(
+                    mStatusCode,
+                    mPackageName);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl
index 1c6e25e..50c89c0 100644
--- a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl
+++ b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl
@@ -26,6 +26,8 @@
  */
 oneway interface IAmbientContextDetectionService {
     void startDetection(in AmbientContextEventRequest request, in String packageName,
-        in RemoteCallback callback);
+        in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback);
     void stopDetection(in String packageName);
+    void queryServiceStatus(in int[] eventTypes, in String packageName,
+        in RemoteCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index ae39d3d..b9e60a1 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -256,6 +256,8 @@
     public static final int REASON_CHANNEL_REMOVED = 20;
     /** Notification was canceled due to the app's storage being cleared */
     public static final int REASON_CLEAR_DATA = 21;
+    /** Notification was canceled due to an assistant adjustment update. */
+    public static final int REASON_ASSISTANT_CANCEL = 22;
 
     /**
      * @hide
@@ -279,7 +281,10 @@
             REASON_UNAUTOBUNDLED,
             REASON_CHANNEL_BANNED,
             REASON_SNOOZED,
-            REASON_TIMEOUT
+            REASON_TIMEOUT,
+            REASON_CHANNEL_REMOVED,
+            REASON_CLEAR_DATA,
+            REASON_ASSISTANT_CANCEL,
     })
     public @interface NotificationCancelReason{};
 
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 7c79b1a..986a41c 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -78,6 +78,24 @@
     void onPartialResults(in Bundle results);
 
     /**
+     * Called for each ready segment of a recognition request. To request segmented speech results
+     * use {@link RecognizerIntent#EXTRA_SEGMENT_SESSION}. The callback might be called
+     * any number of times between {@link #onBeginningOfSpeech()} and
+     * {@link #onEndOfSegmentedSession()}.
+     *
+     * @param segmentResults the returned results. To retrieve the results in
+     *        ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
+     *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
+    */
+    void onSegmentResults(in Bundle results);
+
+    /**
+     * Called at the end of a segmented recognition request. To request segmented speech results
+     * use {@link RecognizerIntent#EXTRA_SEGMENT_SESSION}.
+     */
+    void onEndOfSegmentedSession();
+
+    /**
      * Reserved for adding future events.
      *
      * @param eventType the type of the occurred event
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
index c94b60f..64fd09f 100644
--- a/core/java/android/speech/RecognitionListener.java
+++ b/core/java/android/speech/RecognitionListener.java
@@ -15,6 +15,7 @@
  */
 package android.speech;
 
+import android.annotation.NonNull;
 import android.content.Intent;
 import android.os.Bundle;
 
@@ -69,7 +70,13 @@
 
     /**
      * Called when recognition results are ready.
-     * 
+     *
+     * <p>
+     *     Called with the results for the full speech since {@link #onReadyForSpeech(Bundle)}.
+     *     To get recognition results in segments rather than for the full session see
+     *     {@link RecognizerIntent#EXTRA_SEGMENT_SESSION}.
+     * </p>
+     *
      * @param results the recognition results. To retrieve the results in {@code
      *        ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
      *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter. A float array of
@@ -92,6 +99,24 @@
     void onPartialResults(Bundle partialResults);
 
     /**
+     * Called for each ready segment of a recognition request. To request segmented speech results
+     * use {@link RecognizerIntent#EXTRA_SEGMENT_SESSION}. The callback might be called
+     * any number of times between {@link #onReadyForSpeech(Bundle)} and
+     * {@link #onEndOfSegmentedSession()}.
+     *
+     * @param segmentResults the returned results. To retrieve the results in
+     *        ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
+     *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
+     */
+    default void onSegmentResults(@NonNull Bundle segmentResults) {}
+
+    /**
+     * Called at the end of a segmented recognition request. To request segmented speech results
+     * use {@link RecognizerIntent#EXTRA_SEGMENT_SESSION}.
+     */
+    default void onEndOfSegmentedSession() {}
+
+    /**
      * Reserved for adding future events.
      * 
      * @param eventType the type of the occurred event
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 5dbbc04..22cffc1 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -427,6 +427,27 @@
         }
 
         /**
+         * The service should call this method for each ready segment of a long recognition session.
+         *
+         * @param results the recognition results. To retrieve the results in {@code
+         *        ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
+         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
+         */
+        @SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
+        public void segmentResults(@NonNull Bundle results) throws RemoteException {
+            mListener.onSegmentResults(results);
+        }
+
+        /**
+         * The service should call this method to end a segmented session.
+         */
+        @SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
+        public void endOfSegmentedSession() throws RemoteException {
+            Message.obtain(mHandler, MSG_RESET).sendToTarget();
+            mListener.onEndOfSegmentedSession();
+        }
+
+        /**
          * Return the Linux uid assigned to the process that sent you the current transaction that
          * is being processed. This is obtained from {@link Binder#getCallingUid()}.
          */
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 3183f15..271e307 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -426,4 +426,16 @@
      *
      */
     public static final String EXTRA_PREFER_OFFLINE = "android.speech.extra.PREFER_OFFLINE";
+
+    /**
+     * Optional boolean, when true and supported by the recognizer implementation it will split
+     * the recognition results in segments, returned via
+     * {@link RecognitionListener#onSegmentResults(Bundle)} and terminate the session with
+     * {@link RecognitionListener#onEndOfSegmentedSession()}. There will be no call to
+     * {@link RecognitionListener#onResults(Bundle)}. Callers can use
+     * {@link #EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS} and
+     * {@link #EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS} to tune how long the segments
+     * will be. Defaults to false.
+     */
+    public static final String EXTRA_SEGMENT_SESSION = "android.speech.extra.SEGMENT_SESSION";
 }
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 71c1e88..502558e 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -768,6 +768,8 @@
         private static final int MSG_PARTIAL_RESULTS = 7;
         private static final int MSG_RMS_CHANGED = 8;
         private static final int MSG_ON_EVENT = 9;
+        private static final int MSG_SEGMENT_RESULTS = 10;
+        private static final int MSG_SEGMENT_END_SESSION = 11;
 
         private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) {
             @Override
@@ -803,6 +805,12 @@
                     case MSG_ON_EVENT:
                         mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
                         break;
+                    case MSG_SEGMENT_RESULTS:
+                        mInternalListener.onSegmentResults((Bundle) msg.obj);
+                        break;
+                    case MSG_SEGMENT_END_SESSION:
+                        mInternalListener.onEndOfSegmentedSession();
+                        break;
                 }
             }
         };
@@ -839,6 +847,14 @@
             Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
         }
 
+        public void onSegmentResults(final Bundle bundle) {
+            Message.obtain(mInternalHandler, MSG_SEGMENT_RESULTS, bundle).sendToTarget();
+        }
+
+        public void onEndOfSegmentedSession() {
+            Message.obtain(mInternalHandler, MSG_SEGMENT_END_SESSION).sendToTarget();
+        }
+
         public void onEvent(final int eventType, final Bundle params) {
             Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
                     .sendToTarget();
diff --git a/core/java/android/util/DumpableContainer.java b/core/java/android/util/DumpableContainer.java
index 04d19dc..e7b2acd 100644
--- a/core/java/android/util/DumpableContainer.java
+++ b/core/java/android/util/DumpableContainer.java
@@ -37,4 +37,15 @@
      * @return {@code true} if the dumpable was added, {@code false} if the call was ignored.
      */
     boolean addDumpable(@NonNull Dumpable dumpable);
+
+    /**
+     * Removes the given {@link Dumpable dumpable} from the container.
+     *
+     * @param dumpable dumpable to be removed.
+     *
+     * @return {@code true} if the dumpable was removed, {@code false} if it was not previously
+     * {@link #addDumpable(Dumpable) added} with the same identify (object reference) and
+     * {@link Dumpable#getDumpableName() name}.
+     */
+    boolean removeDumpable(@NonNull Dumpable dumpable);
 }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 96a1fc6..fa61de5 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -60,6 +60,13 @@
     public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
             "settings_enable_monitor_phantom_procs";
 
+    /**
+     * Support dark theme activation at Bedtime.
+     * @hide
+     */
+    public static final String SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME =
+            "settings_app_allow_dark_theme_activation_at_bedtime";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -84,6 +91,7 @@
         DEFAULT_FLAGS.put("settings_search_always_expand", "false");
         DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
+        DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -92,6 +100,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
+        PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
     }
 
     /**
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index c199b96..28c078e 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,3 +1,4 @@
+per-file Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
 per-file FeatureFlagUtils.java = sbasi@google.com
 per-file FeatureFlagUtils.java = tmfang@google.com
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c5ccc18..36baa04 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -941,4 +941,14 @@
      * @hide
      */
     void unregisterTaskFpsCallback(in IOnFpsCallbackListener listener);
+
+    /**
+     * Take a snapshot using the same path that's used for Recents. This is used for Testing only.
+     *
+     * @param taskId to take the snapshot of
+     *
+     * Returns a bitmap of the screenshot or {@code null} if it was unable to screenshot.
+     * @hide
+     */
+    Bitmap snapshotTaskForRecents(int taskId);
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 7f115fa..d3061e2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2779,9 +2779,13 @@
          * is allowed as a convenience.
          */
         public Transaction() {
-            mNativeObject = nativeCreateTransaction();
-            mFreeNativeResources
-                = sRegistry.registerNativeAllocation(this, mNativeObject);
+            this(nativeCreateTransaction());
+        }
+
+        private Transaction(long nativeObject) {
+            mNativeObject = nativeObject;
+            mFreeNativeResources =
+                    sRegistry.registerNativeAllocation(this, mNativeObject);
         }
 
         private Transaction(Parcel in) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 83a59e7..a2fcf80 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -527,20 +527,12 @@
         mRequestedVisible = false;
 
         updateSurface();
-        tryReleaseSurfaces();
 
         // We don't release this as part of releaseSurfaces as
         // that is also called on transient visibility changes. We can't
         // recreate this Surface, so only release it when we are fully
         // detached.
-        if (mSurfacePackage != null) {
-            final SurfaceControl sc = mSurfacePackage.getSurfaceControl();
-            if (sc != null && sc.isValid()) {
-                mTmpTransaction.reparent(sc, null).apply();
-            }
-            mSurfacePackage.release();
-            mSurfacePackage = null;
-        }
+        tryReleaseSurfaces(true /* releaseSurfacePackage*/);
 
         mHaveFrame = false;
         super.onDetachedFromWindow();
@@ -871,7 +863,7 @@
         return t;
     }
 
-    private void tryReleaseSurfaces() {
+    private void tryReleaseSurfaces(boolean releaseSurfacePackage) {
         mSurfaceAlpha = 1f;
 
         synchronized (mSurfaceControlLock) {
@@ -881,9 +873,26 @@
                 mBlastBufferQueue = null;
             }
 
-            ViewRootImpl viewRoot = getViewRootImpl();
             Transaction transaction = new Transaction();
-            releaseSurfaces(transaction);
+            if (mSurfaceControl != null) {
+                transaction.remove(mSurfaceControl);
+                mSurfaceControl = null;
+            }
+            if (mBackgroundControl != null) {
+                transaction.remove(mBackgroundControl);
+                mBackgroundControl = null;
+            }
+            if (mBlastSurfaceControl != null) {
+                transaction.remove(mBlastSurfaceControl);
+                mBlastSurfaceControl = null;
+            }
+
+            if (releaseSurfacePackage && mSurfacePackage != null) {
+                mSurfacePackage.release();
+                mSurfacePackage = null;
+            }
+
+            ViewRootImpl viewRoot = getViewRootImpl();
             if (viewRoot != null) {
                 viewRoot.applyTransactionOnDraw(transaction);
             } else {
@@ -892,22 +901,6 @@
         }
     }
 
-    private void releaseSurfaces(Transaction transaction) {
-        if (mSurfaceControl != null) {
-            transaction.remove(mSurfaceControl);
-            mSurfaceControl = null;
-        }
-        if (mBackgroundControl != null) {
-            transaction.remove(mBackgroundControl);
-            mBackgroundControl = null;
-        }
-        if (mBlastSurfaceControl != null) {
-            transaction.remove(mBlastSurfaceControl);
-            mBlastSurfaceControl = null;
-        }
-    }
-
-
     // The position update listener is used to safely share the surface size between render thread
     // workers and the UI thread. Both threads need to know the surface size to determine the scale.
     // The parent layer scales the surface size to view size. The child (BBQ) layer scales
@@ -1048,7 +1041,7 @@
 
         if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
             notifySurfaceDestroyed();
-            tryReleaseSurfaces();
+            tryReleaseSurfaces(false /* releaseSurfacePackage*/);
             return;
         }
 
@@ -1189,7 +1182,7 @@
                 } finally {
                     mIsCreating = false;
                     if (mSurfaceControl != null && !mSurfaceCreated) {
-                        tryReleaseSurfaces();
+                        tryReleaseSurfaces(false /* releaseSurfacePackage*/);
                     }
                 }
             } catch (Exception ex) {
@@ -1835,43 +1828,24 @@
      * @param p The SurfacePackage to embed.
      */
     public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) {
-        setChildSurfacePackage(p, false /* applyTransactionOnDraw */);
-    }
-
-    /**
-     * Similar to setChildSurfacePackage, but using the BLAST queue so the transaction can be
-     * synchronized with the ViewRootImpl frame.
-     * @hide
-     */
-    public void setChildSurfacePackageOnDraw(
-            @NonNull SurfaceControlViewHost.SurfacePackage p) {
-        setChildSurfacePackage(p, true /* applyTransactionOnDraw */);
-    }
-
-    /**
-     * @param applyTransactionOnDraw Whether to apply transaction at onDraw or immediately.
-     */
-    private void setChildSurfacePackage(
-            @NonNull SurfaceControlViewHost.SurfacePackage p, boolean applyTransactionOnDraw) {
         final SurfaceControl lastSc = mSurfacePackage != null ?
                 mSurfacePackage.getSurfaceControl() : null;
+        final SurfaceControl.Transaction transaction = new Transaction();
         if (mSurfaceControl != null) {
             if (lastSc != null) {
-                mTmpTransaction.reparent(lastSc, null);
+                transaction.reparent(lastSc, null);
                 mSurfacePackage.release();
             }
-            reparentSurfacePackage(mTmpTransaction, p);
-            applyTransaction(applyTransactionOnDraw);
+            reparentSurfacePackage(transaction, p);
+            final ViewRootImpl viewRoot = getViewRootImpl();
+            if (viewRoot != null) {
+                viewRoot.applyTransactionOnDraw(transaction);
+            } else {
+                transaction.apply();
+            }
         }
         mSurfacePackage = p;
-    }
-
-    private void applyTransaction(boolean applyTransactionOnDraw) {
-        if (applyTransactionOnDraw) {
-            getViewRootImpl().applyTransactionOnDraw(mTmpTransaction);
-        } else {
-            mTmpTransaction.apply();
-        }
+        invalidate();
     }
 
     private void reparentSurfacePackage(SurfaceControl.Transaction t,
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 777e89d..39fc2c0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4169,19 +4169,22 @@
                         + " didProduceBuffer=" + didProduceBuffer);
             }
 
+            Transaction tmpTransaction = new Transaction();
+            tmpTransaction.merge(mRtBLASTSyncTransaction);
+
             // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
             // draw attempt. The next transaction and transaction complete callback were only set
             // for the current draw attempt.
             if (!didProduceBuffer) {
                 mBlastBufferQueue.setSyncTransaction(null);
-                // Apply the transactions that were sent to mergeWithNextTransaction since the
+                // Get the transactions that were sent to mergeWithNextTransaction since the
                 // frame didn't draw on this vsync. It's possible the frame will draw later, but
                 // it's better to not be sync than to block on a frame that may never come.
-                mBlastBufferQueue.applyPendingTransactions(mRtLastAttemptedDrawFrameNum);
+                Transaction pendingTransactions = mBlastBufferQueue.gatherPendingTransactions(
+                        mRtLastAttemptedDrawFrameNum);
+                tmpTransaction.merge(pendingTransactions);
             }
 
-            Transaction tmpTransaction = new Transaction();
-            tmpTransaction.merge(mRtBLASTSyncTransaction);
             // Post at front of queue so the buffer can be processed immediately and allow RT
             // to continue processing new buffers. If RT tries to process buffers before the sync
             // buffer is applied, the new buffers will not get acquired and could result in a
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index ca7f900..771d40b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -97,6 +97,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -4886,4 +4887,21 @@
      */
     @SystemApi
     default void unregisterTaskFpsCallback(@NonNull TaskFpsCallback callback) {}
+
+    /**
+     * Take a snapshot using the same path that's used for Recents. This is used for Testing only.
+     *
+     * @param taskId to take the snapshot of
+     *
+     * @return a bitmap of the screenshot or {@code null} if it was unable to screenshot. The
+     * screenshot can fail if the taskId is invalid or if there's no SurfaceControl associated with
+     * that task.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    default Bitmap snapshotTaskForRecents(@IntRange(from = 0) int taskId) {
+        return null;
+    }
 }
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index c16703ef..f4353eb 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -32,6 +32,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Bundle;
@@ -439,4 +440,14 @@
         } catch (RemoteException e) {
         }
     }
+
+    @Override
+    public Bitmap snapshotTaskForRecents(int taskId) {
+        try {
+            return WindowManagerGlobal.getWindowManagerService().snapshotTaskForRecents(taskId);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        return null;
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e54ed18..c6cd2e8 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1773,8 +1773,7 @@
      * @param userId The user Id.
      * @hide
      */
-    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
-    public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) {
+    public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1783,7 +1782,7 @@
             }
         }
         try {
-            service.setSystemAudioCaptioningRequested(isEnabled, userId);
+            service.setSystemAudioCaptioningEnabled(isEnabled, userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -1796,7 +1795,7 @@
      * @return the system audio caption UI enabled state.
      * @hide
      */
-    public boolean isSystemAudioCaptioningUiRequested(int userId) {
+    public boolean isSystemAudioCaptioningUiEnabled(int userId) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1805,7 +1804,7 @@
             }
         }
         try {
-            return service.isSystemAudioCaptioningUiRequested(userId);
+            return service.isSystemAudioCaptioningUiEnabled(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -1818,8 +1817,7 @@
      * @param userId The user Id.
      * @hide
      */
-    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
-    public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) {
+    public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1828,7 +1826,7 @@
             }
         }
         try {
-            service.setSystemAudioCaptioningUiRequested(isEnabled, userId);
+            service.setSystemAudioCaptioningUiEnabled(isEnabled, userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index 4f9781b..e960bec 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -152,7 +152,7 @@
     /**
      * @return the system audio caption enabled state.
      */
-    public final boolean isSystemAudioCaptioningRequested() {
+    public final boolean isSystemAudioCaptioningEnabled() {
         return Secure.getIntForUser(mContentResolver, Secure.ODI_CAPTIONS_ENABLED,
                 SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED ? 1 : 0, mContext.getUserId()) == 1;
     }
@@ -169,9 +169,9 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
-    public final void setSystemAudioCaptioningRequested(boolean isEnabled) {
+    public final void setSystemAudioCaptioningEnabled(boolean isEnabled) {
         if (mAccessibilityManager != null) {
-            mAccessibilityManager.setSystemAudioCaptioningRequested(isEnabled,
+            mAccessibilityManager.setSystemAudioCaptioningEnabled(isEnabled,
                     mContext.getUserId());
         }
     }
@@ -179,9 +179,9 @@
     /**
      * @return the system audio caption UI enabled state.
      */
-    public final boolean isSystemAudioCaptioningUiRequested() {
+    public final boolean isSystemAudioCaptioningUiEnabled() {
         return mAccessibilityManager != null
-                && mAccessibilityManager.isSystemAudioCaptioningUiRequested(mContext.getUserId());
+                && mAccessibilityManager.isSystemAudioCaptioningUiEnabled(mContext.getUserId());
     }
 
     /**
@@ -196,9 +196,9 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
-    public final void setSystemAudioCaptioningUiRequested(boolean isEnabled) {
+    public final void setSystemAudioCaptioningUiEnabled(boolean isEnabled) {
         if (mAccessibilityManager != null) {
-            mAccessibilityManager.setSystemAudioCaptioningUiRequested(isEnabled,
+            mAccessibilityManager.setSystemAudioCaptioningUiEnabled(isEnabled,
                     mContext.getUserId());
         }
     }
@@ -300,7 +300,7 @@
     }
 
     private void notifySystemAudioCaptionChanged() {
-        final boolean enabled = isSystemAudioCaptioningRequested();
+        final boolean enabled = isSystemAudioCaptioningEnabled();
         synchronized (mListeners) {
             for (CaptioningChangeListener listener : mListeners) {
                 listener.onSystemAudioCaptioningChanged(enabled);
@@ -309,7 +309,7 @@
     }
 
     private void notifySystemAudioCaptionUiChanged() {
-        final boolean enabled = isSystemAudioCaptioningUiRequested();
+        final boolean enabled = isSystemAudioCaptioningUiEnabled();
         synchronized (mListeners) {
             for (CaptioningChangeListener listener : mListeners) {
                 listener.onSystemAudioCaptioningUiChanged(enabled);
@@ -686,7 +686,7 @@
          * @param isEnabled The system audio captioning enabled state.
          * @param userId The user Id.
          */
-        void setSystemAudioCaptioningRequested(boolean isEnabled, int userId);
+        void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId);
 
         /**
          * Gets the system audio caption UI enabled state.
@@ -694,7 +694,7 @@
          * @param userId The user Id.
          * @return the system audio caption UI enabled state.
          */
-        boolean isSystemAudioCaptioningUiRequested(int userId);
+        boolean isSystemAudioCaptioningUiEnabled(int userId);
 
         /**
          * Sets the system audio caption UI enabled state.
@@ -702,6 +702,6 @@
          * @param isEnabled The system audio captioning UI enabled state.
          * @param userId The user Id.
          */
-        void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId);
+        void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId);
     }
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 645ddf5..418132a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -101,13 +101,11 @@
 
     boolean isAudioDescriptionByDefaultEnabled();
 
-    // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION
-    // System process only
-    void setSystemAudioCaptioningRequested(boolean isEnabled, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)")
+    void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId);
 
-    boolean isSystemAudioCaptioningUiRequested(int userId);
+    boolean isSystemAudioCaptioningUiEnabled(int userId);
 
-    // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION
-    // System process only
-    void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)")
+    void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f480b24..c713a54 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -115,6 +115,7 @@
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * Central system API to the overall input method framework (IMF) architecture,
@@ -426,6 +427,8 @@
     int mCursorSelEnd;
     int mCursorCandStart;
     int mCursorCandEnd;
+    int mInitialSelStart;
+    int mInitialSelEnd;
 
     /**
      * The instance that has previously been sent to the input method.
@@ -468,6 +471,13 @@
     @Nullable
     @GuardedBy("mH")
     private InputMethodSessionWrapper mCurrentInputMethodSession = null;
+    /**
+     * Encapsulates IPCs to the currently connected AccessibilityServices.
+     */
+    @Nullable
+    @GuardedBy("mH")
+    private final SparseArray<InputMethodSessionWrapper> mAccessibilityInputMethodSession =
+            new SparseArray<>();
 
     InputChannel mCurChannel;
     ImeInputEventSender mCurSender;
@@ -499,6 +509,8 @@
     static final int MSG_TIMEOUT_INPUT_EVENT = 6;
     static final int MSG_FLUSH_INPUT_EVENT = 7;
     static final int MSG_REPORT_FULLSCREEN_MODE = 10;
+    static final int MSG_BIND_ACCESSIBILITY_SERVICE = 11;
+    static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
 
     private static boolean isAutofillUIShowing(View servedView) {
         AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
@@ -626,6 +638,7 @@
                 if (mCurrentInputMethodSession != null) {
                     mCurrentInputMethodSession.finishInput();
                 }
+                forAccessibilitySessions(InputMethodSessionWrapper::finishInput);
             }
         }
 
@@ -881,6 +894,7 @@
                         if (mBindSequence != sequence) {
                             return;
                         }
+                        clearAllAccessibilityBindingLocked();
                         clearBindingLocked();
                         // If we were actively using the last input method, then
                         // we would like to re-connect to the next input method.
@@ -896,6 +910,73 @@
                     }
                     return;
                 }
+                case MSG_BIND_ACCESSIBILITY_SERVICE: {
+                    final int id = msg.arg1;
+                    final InputBindResult res = (InputBindResult) msg.obj;
+                    if (DEBUG) {
+                        Log.i(TAG, "handleMessage: MSG_BIND_ACCESSIBILITY " + res.sequence
+                                + "," + res.id);
+                    }
+                    synchronized (mH) {
+                        if (mBindSequence < 0 || mBindSequence != res.sequence) {
+                            Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+                                    + ", given seq=" + res.sequence);
+                            if (res.channel != null && res.channel != mCurChannel) {
+                                res.channel.dispose();
+                            }
+                            return;
+                        }
+
+                        // Since IMM can start inputting text before a11y sessions are back,
+                        // we send a notification so that the a11y service knows the session is
+                        // registered and update the a11y service with the current cursor positions.
+                        if (res.accessibilitySessions != null) {
+                            InputMethodSessionWrapper wrapper =
+                                    InputMethodSessionWrapper.createOrNull(
+                                            res.accessibilitySessions.get(id));
+                            if (wrapper != null) {
+                                mAccessibilityInputMethodSession.put(id, wrapper);
+                                if (mServedInputConnection != null) {
+                                    wrapper.updateSelection(mInitialSelStart, mInitialSelEnd,
+                                            mCursorSelStart, mCursorSelEnd, mCursorCandStart,
+                                            mCursorCandEnd);
+                                } else {
+                                    // If an a11y service binds before input starts, we should still
+                                    // send a notification because the a11y service doesn't know it
+                                    // binds before or after input starts, it may wonder if it binds
+                                    // after input starts, why it doesn't receive a notification of
+                                    // the current cursor positions.
+                                    wrapper.updateSelection(-1, -1,
+                                            -1, -1, -1,
+                                            -1);
+                                }
+                            }
+                        }
+                        mBindSequence = res.sequence;
+                    }
+                    startInputInner(StartInputReason.BOUND_ACCESSIBILITY_SESSION_TO_IMMS, null,
+                            0, 0, 0);
+                    return;
+                }
+                case MSG_UNBIND_ACCESSIBILITY_SERVICE: {
+                    final int sequence = msg.arg1;
+                    final int id = msg.arg2;
+                    if (DEBUG) {
+                        Log.i(TAG, "handleMessage: MSG_UNBIND_ACCESSIBILITY_SERVICE "
+                                + sequence + " id=" + id);
+                    }
+                    synchronized (mH) {
+                        if (mBindSequence != sequence) {
+                            if (DEBUG) {
+                                Log.i(TAG, "mBindSequence =" + mBindSequence + " sequence ="
+                                        + sequence + " id=" + id);
+                            }
+                            return;
+                        }
+                        clearAccessibilityBindingLocked(id);
+                    }
+                    return;
+                }
                 case MSG_SET_ACTIVE: {
                     final boolean active = msg.arg1 != 0;
                     final boolean fullscreen = msg.arg2 != 0;
@@ -996,11 +1077,21 @@
         }
 
         @Override
+        public void onBindAccessibilityService(InputBindResult res, int id) {
+            mH.obtainMessage(MSG_BIND_ACCESSIBILITY_SERVICE, id, 0, res).sendToTarget();
+        }
+
+        @Override
         public void onUnbindMethod(int sequence, @UnbindReason int unbindReason) {
             mH.obtainMessage(MSG_UNBIND, sequence, unbindReason).sendToTarget();
         }
 
         @Override
+        public void onUnbindAccessibilityService(int sequence, int id) {
+            mH.obtainMessage(MSG_UNBIND_ACCESSIBILITY_SERVICE, sequence, id).sendToTarget();
+        }
+
+        @Override
         public void setActive(boolean active, boolean fullscreen, boolean reportToImeController) {
             mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0,
                     reportToImeController).sendToTarget();
@@ -1420,16 +1511,36 @@
     /**
      * Reset all of the state associated with being bound to an input method.
      */
+    @GuardedBy("mH")
     void clearBindingLocked() {
         if (DEBUG) Log.v(TAG, "Clearing binding!");
         clearConnectionLocked();
         setInputChannelLocked(null);
+        // We only reset sequence number for input method, but not accessibility.
         mBindSequence = -1;
         mCurId = null;
         mCurMethod = null; // for @UnsupportedAppUsage
         mCurrentInputMethodSession = null;
     }
 
+    /**
+     * Reset all of the state associated with being bound to an accessibility service.
+     */
+    @GuardedBy("mH")
+    void clearAccessibilityBindingLocked(int id) {
+        if (DEBUG) Log.v(TAG, "Clearing accessibility binding " + id);
+        mAccessibilityInputMethodSession.remove(id);
+    }
+
+    /**
+     * Reset all of the state associated with being bound to all ccessibility services.
+     */
+    @GuardedBy("mH")
+    void clearAllAccessibilityBindingLocked() {
+        if (DEBUG) Log.v(TAG, "Clearing all accessibility bindings");
+        mAccessibilityInputMethodSession.clear();
+    }
+
     void setInputChannelLocked(InputChannel channel) {
         if (mCurChannel == channel) {
             return;
@@ -1938,6 +2049,8 @@
             editorInfo.setInitialSurroundingTextInternal(textSnapshot.getSurroundingText());
             mCurrentInputMethodSession.invalidateInput(editorInfo, mServedInputConnection,
                     sessionId);
+            forAccessibilitySessions(wrapper -> wrapper.invalidateInput(editorInfo,
+                    mServedInputConnection, sessionId));
         }
     }
 
@@ -2080,6 +2193,8 @@
             if (ic != null) {
                 mCursorSelStart = tba.initialSelStart;
                 mCursorSelEnd = tba.initialSelEnd;
+                mInitialSelStart = mCursorSelStart;
+                mInitialSelEnd = mCursorSelEnd;
                 mCursorCandStart = -1;
                 mCursorCandEnd = -1;
                 mCursorRect.setEmpty();
@@ -2128,6 +2243,17 @@
                 mBindSequence = res.sequence;
                 mCurMethod = res.method; // for @UnsupportedAppUsage
                 mCurrentInputMethodSession = InputMethodSessionWrapper.createOrNull(res.method);
+                mAccessibilityInputMethodSession.clear();
+                if (res.accessibilitySessions != null) {
+                    for (int i = 0; i < res.accessibilitySessions.size(); i++) {
+                        InputMethodSessionWrapper wrapper = InputMethodSessionWrapper.createOrNull(
+                                res.accessibilitySessions.valueAt(i));
+                        if (wrapper != null) {
+                            mAccessibilityInputMethodSession.append(
+                                    res.accessibilitySessions.keyAt(i), wrapper);
+                        }
+                    }
+                }
                 mCurId = res.id;
             } else if (res.channel != null && res.channel != mCurChannel) {
                 res.channel.dispose();
@@ -2137,8 +2263,10 @@
                     mRestartOnNextWindowFocus = true;
                     break;
             }
-            if (mCurrentInputMethodSession != null && mCompletions != null) {
-                mCurrentInputMethodSession.displayCompletions(mCompletions);
+            if (mCompletions != null) {
+                if (mCurrentInputMethodSession != null) {
+                    mCurrentInputMethodSession.displayCompletions(mCompletions);
+                }
             }
         }
 
@@ -2369,6 +2497,8 @@
                 mCursorCandEnd = candidatesEnd;
                 mCurrentInputMethodSession.updateSelection(
                         oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
+                forAccessibilitySessions(wrapper -> wrapper.updateSelection(oldSelStart,
+                        oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd));
             }
         }
     }
@@ -3233,6 +3363,11 @@
         } else {
             p.println("  mCurMethod= null");
         }
+        for (int i = 0; i < mAccessibilityInputMethodSession.size(); i++) {
+            p.println("  mAccessibilityInputMethodSession("
+                    + mAccessibilityInputMethodSession.keyAt(i) + ")="
+                    + mAccessibilityInputMethodSession.valueAt(i));
+        }
         p.println("  mCurRootView=" + mCurRootView);
         p.println("  mServedView=" + getServedViewLocked());
         p.println("  mNextServedView=" + getNextServedViewLocked());
@@ -3377,4 +3512,10 @@
             }
         }
     }
+
+    private void forAccessibilitySessions(Consumer<InputMethodSessionWrapper> consumer) {
+        for (int i = 0; i < mAccessibilityInputMethodSession.size(); i++) {
+            consumer.accept(mAccessibilityInputMethodSession.valueAt(i));
+        }
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9efa583..c0c7641 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12295,7 +12295,7 @@
                     EXTRA_DATA_RENDERING_INFO_KEY,
                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
             ));
-            info.setTextSelectable(isTextSelectable());
+            info.setTextSelectable(isTextSelectable() || isTextEditable());
         } else {
             info.setAvailableExtraData(Arrays.asList(
                     EXTRA_DATA_RENDERING_INFO_KEY
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index b90e628..34a3418 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -493,7 +493,7 @@
             Log.d(TAG, "Transferring surface " + mSurfaceView.toString());
         }
 
-        mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage);
+        mSurfaceView.setChildSurfacePackage(mSurfacePackage);
     }
 
     void initIconAnimation(Drawable iconDrawable, long duration) {
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 3fa62e0..5ca29e6 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -512,7 +512,7 @@
         }
 
         /** @return the task info or null if this isn't a task */
-        @NonNull
+        @Nullable
         public ActivityManager.RunningTaskInfo getTaskInfo() {
             return mTaskInfo;
         }
diff --git a/core/java/com/android/internal/inputmethod/InputBindResult.java b/core/java/com/android/internal/inputmethod/InputBindResult.java
index 1357bac..e838401 100644
--- a/core/java/com/android/internal/inputmethod/InputBindResult.java
+++ b/core/java/com/android/internal/inputmethod/InputBindResult.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.SparseArray;
 import android.view.InputChannel;
 
 import com.android.internal.view.IInputMethodSession;
@@ -53,7 +54,8 @@
             ResultCode.ERROR_NOT_IME_TARGET_WINDOW,
             ResultCode.ERROR_NO_EDITOR,
             ResultCode.ERROR_DISPLAY_ID_MISMATCH,
-            ResultCode.ERROR_INVALID_DISPLAY_ID
+            ResultCode.ERROR_INVALID_DISPLAY_ID,
+            ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION
     })
     public @interface ResultCode {
         /**
@@ -168,6 +170,7 @@
          * display.
          */
         int ERROR_INVALID_DISPLAY_ID = 15;
+        int SUCCESS_WITH_ACCESSIBILITY_SESSION = 16;
     }
 
     @ResultCode
@@ -179,6 +182,11 @@
     public final IInputMethodSession method;
 
     /**
+     * The accessibility services.
+     */
+    public SparseArray<IInputMethodSession> accessibilitySessions;
+
+    /**
      * The input channel used to send input events to this IME.
      */
     public final InputChannel channel;
@@ -204,6 +212,8 @@
      *
      * @param result A result code defined in {@link ResultCode}.
      * @param method {@link IInputMethodSession} to interact with the IME.
+     * @param accessibilitySessions {@link IInputMethodSession} to interact with accessibility
+     *                              services.
      * @param channel {@link InputChannel} to forward input events to the IME.
      * @param id The {@link String} representations of the IME, which is the same as
      *           {@link android.view.inputmethod.InputMethodInfo#getId()} and
@@ -213,10 +223,12 @@
      *                                             {@code suppressesSpellChecker="true"}.
      */
     public InputBindResult(@ResultCode int result,
-            IInputMethodSession method, InputChannel channel, String id, int sequence,
+            IInputMethodSession method, SparseArray<IInputMethodSession> accessibilitySessions,
+            InputChannel channel, String id, int sequence,
             boolean isInputMethodSuppressingSpellChecker) {
         this.result = result;
         this.method = method;
+        this.accessibilitySessions = accessibilitySessions;
         this.channel = channel;
         this.id = id;
         this.sequence = sequence;
@@ -226,6 +238,19 @@
     private InputBindResult(Parcel source) {
         result = source.readInt();
         method = IInputMethodSession.Stub.asInterface(source.readStrongBinder());
+        int n = source.readInt();
+        if (n < 0) {
+            accessibilitySessions = null;
+        } else {
+            accessibilitySessions = new SparseArray<>(n);
+            while (n > 0) {
+                int key = source.readInt();
+                IInputMethodSession value =
+                        IInputMethodSession.Stub.asInterface(source.readStrongBinder());
+                accessibilitySessions.append(key, value);
+                n--;
+            }
+        }
         if (source.readInt() != 0) {
             channel = InputChannel.CREATOR.createFromParcel(source);
         } else {
@@ -254,6 +279,18 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(result);
         dest.writeStrongInterface(method);
+        if (accessibilitySessions == null) {
+            dest.writeInt(-1);
+        } else {
+            int n = accessibilitySessions.size();
+            dest.writeInt(n);
+            int i = 0;
+            while (i < n) {
+                dest.writeInt(accessibilitySessions.keyAt(i));
+                dest.writeStrongInterface(accessibilitySessions.valueAt(i));
+                i++;
+            }
+        }
         if (channel != null) {
             dest.writeInt(1);
             channel.writeToParcel(dest, flags);
@@ -329,7 +366,7 @@
     }
 
     private static InputBindResult error(@ResultCode int result) {
-        return new InputBindResult(result, null, null, null, -1, false);
+        return new InputBindResult(result, null, null, null, null, -1, false);
     }
 
     /**
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index bf094db..d669768 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -64,6 +64,8 @@
                 return "DEACTIVATED_BY_IMMS";
             case StartInputReason.SESSION_CREATED_BY_IME:
                 return "SESSION_CREATED_BY_IME";
+            case StartInputReason.BOUND_ACCESSIBILITY_SESSION_TO_IMMS:
+                return "BOUND_ACCESSIBILITY_SESSION_TO_IMMS";
             default:
                 return "Unknown=" + reason;
         }
@@ -91,6 +93,8 @@
                 return "SWITCH_IME_FAILED";
             case UnbindReason.SWITCH_USER:
                 return "SWITCH_USER";
+            case UnbindReason.ACCESSIBILITY_SERVICE_DISABLED:
+                return "ACCESSIBILITY_SERVICE_DISABLED";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/StartInputReason.java b/core/java/com/android/internal/inputmethod/StartInputReason.java
index 2ba708d..1263466 100644
--- a/core/java/com/android/internal/inputmethod/StartInputReason.java
+++ b/core/java/com/android/internal/inputmethod/StartInputReason.java
@@ -38,7 +38,9 @@
         StartInputReason.UNBOUND_FROM_IMMS,
         StartInputReason.ACTIVATED_BY_IMMS,
         StartInputReason.DEACTIVATED_BY_IMMS,
-        StartInputReason.SESSION_CREATED_BY_IME})
+        StartInputReason.SESSION_CREATED_BY_IME,
+        StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY,
+        StartInputReason.BOUND_ACCESSIBILITY_SESSION_TO_IMMS})
 public @interface StartInputReason {
     /**
      * Reason is not specified.
@@ -96,4 +98,14 @@
      * {@link com.android.internal.view.IInputSessionCallback#sessionCreated}.
      */
     int SESSION_CREATED_BY_IME = 10;
+    /**
+     * {@link android.accessibilityservice.AccessibilityService} is responding to
+     * {@link com.android.internal.view.IInputSessionWithIdCallback#sessionCreated}.
+     */
+    int SESSION_CREATED_BY_ACCESSIBILITY = 11;
+    /**
+     * {@link android.view.inputmethod.InputMethodManager} is responding to
+     * {@link com.android.internal.view.IInputMethodClient#onBindAccessibilityService(InputBindResult, int)}.
+     */
+    int BOUND_ACCESSIBILITY_SESSION_TO_IMMS = 12;
 }
diff --git a/core/java/com/android/internal/inputmethod/UnbindReason.java b/core/java/com/android/internal/inputmethod/UnbindReason.java
index f0f18f1..e926625 100644
--- a/core/java/com/android/internal/inputmethod/UnbindReason.java
+++ b/core/java/com/android/internal/inputmethod/UnbindReason.java
@@ -34,7 +34,9 @@
         UnbindReason.DISCONNECT_IME,
         UnbindReason.NO_IME,
         UnbindReason.SWITCH_IME_FAILED,
-        UnbindReason.SWITCH_USER})
+        UnbindReason.SWITCH_USER,
+        UnbindReason.ACCESSIBILITY_SERVICE_DISABLED
+})
 public @interface UnbindReason {
     /**
      * Reason is not specified.
@@ -66,4 +68,5 @@
      * user's active IME.
      */
     int SWITCH_USER = 6;
+    int ACCESSIBILITY_SERVICE_DISABLED = 7;
 }
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index b579be0..ec95baa 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -105,6 +105,7 @@
     public boolean allowIPv4;
     public boolean allowIPv6;
     public boolean isMetered = true;
+    public boolean requiresInternetValidation = false;
     public Network[] underlyingNetworks;
     public ProxyInfo proxyInfo;
 
@@ -131,6 +132,7 @@
         allowIPv4 = other.allowIPv4;
         allowIPv6 = other.allowIPv6;
         isMetered = other.isMetered;
+        requiresInternetValidation = other.requiresInternetValidation;
         underlyingNetworks = other.underlyingNetworks != null ? Arrays.copyOf(
                 other.underlyingNetworks, other.underlyingNetworks.length) : null;
         proxyInfo = other.proxyInfo;
@@ -189,6 +191,7 @@
         out.writeInt(allowIPv4 ? 1 : 0);
         out.writeInt(allowIPv6 ? 1 : 0);
         out.writeInt(isMetered ? 1 : 0);
+        out.writeInt(requiresInternetValidation ? 1 : 0);
         out.writeTypedArray(underlyingNetworks, flags);
         out.writeParcelable(proxyInfo, flags);
     }
@@ -216,6 +219,7 @@
             config.allowIPv4 = in.readInt() != 0;
             config.allowIPv6 = in.readInt() != 0;
             config.isMetered = in.readInt() != 0;
+            config.requiresInternetValidation = in.readInt() != 0;
             config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
             config.proxyInfo = in.readParcelable(null, android.net.ProxyInfo.class);
             return config;
@@ -248,6 +252,8 @@
                 .append(", allowBypass=").append(allowBypass)
                 .append(", allowIPv4=").append(allowIPv4)
                 .append(", allowIPv6=").append(allowIPv6)
+                .append(", isMetered=").append(isMetered)
+                .append(", requiresInternetValidation").append(requiresInternetValidation)
                 .append(", underlyingNetworks=").append(Arrays.toString(underlyingNetworks))
                 .append(", proxyInfo=").append(proxyInfo)
                 .append("}")
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 519faa8..576860d 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -144,23 +144,26 @@
     public final boolean isRestrictedToTestNetworks;             // 24
 
     public final boolean excludeLocalRoutes;                     // 25
+    public final boolean requiresInternetValidation;             // 26
 
     // Helper fields.
     @UnsupportedAppUsage
     public transient boolean saveLogin = false;
 
     public VpnProfile(String key) {
-        this(key, false, false);
+        this(key, false, false, false);
     }
 
     public VpnProfile(String key, boolean isRestrictedToTestNetworks) {
-        this(key, isRestrictedToTestNetworks, false);
+        this(key, isRestrictedToTestNetworks, false, false);
     }
 
-    public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes) {
+    public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes,
+            boolean requiresInternetValidation) {
         this.key = key;
         this.isRestrictedToTestNetworks = isRestrictedToTestNetworks;
         this.excludeLocalRoutes = excludeLocalRoutes;
+        this.requiresInternetValidation = requiresInternetValidation;
     }
 
     @UnsupportedAppUsage
@@ -191,6 +194,7 @@
         areAuthParamsInline = in.readBoolean();
         isRestrictedToTestNetworks = in.readBoolean();
         excludeLocalRoutes = in.readBoolean();
+        requiresInternetValidation = in.readBoolean();
     }
 
     /**
@@ -239,6 +243,7 @@
         out.writeBoolean(areAuthParamsInline);
         out.writeBoolean(isRestrictedToTestNetworks);
         out.writeBoolean(excludeLocalRoutes);
+        out.writeBoolean(requiresInternetValidation);
     }
 
     /**
@@ -258,9 +263,11 @@
             // 14-19: Standard profile, with option for serverCert, proxy
             // 24: Standard profile with serverCert, proxy and platform-VPN parameters
             // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks
-            // 26: Standard profile with platform-VPN parameters and excludeLocalRoutes
-            if ((values.length < 14 || values.length > 19)
-                    && values.length != 24 && values.length != 25 && values.length != 26) {
+            // 26:                                            ...and excludeLocalRoutes
+            //     (26 can only be found on dogfood devices)
+            // 27:                                            ...and requiresInternetValidation
+            if ((values.length < 14 || (values.length > 19 && values.length < 24)
+                    || values.length > 27)) {
                 return null;
             }
 
@@ -278,8 +285,15 @@
                 excludeLocalRoutes = false;
             }
 
+            final boolean requiresInternetValidation;
+            if (values.length >= 27) {
+                requiresInternetValidation = Boolean.parseBoolean(values[26]);
+            } else {
+                requiresInternetValidation = false;
+            }
+
             VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks,
-                    excludeLocalRoutes);
+                    excludeLocalRoutes, requiresInternetValidation);
             profile.name = values[0];
             profile.type = Integer.parseInt(values[1]);
             if (profile.type < 0 || profile.type > TYPE_MAX) {
@@ -390,6 +404,7 @@
         builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks);
 
         builder.append(VALUE_DELIMITER).append(excludeLocalRoutes);
+        builder.append(VALUE_DELIMITER).append(requiresInternetValidation);
 
         return builder.toString().getBytes(StandardCharsets.UTF_8);
     }
@@ -471,7 +486,7 @@
             key, type, server, username, password, dnsServers, searchDomains, routes, mppe,
             l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert,
             proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline,
-            isRestrictedToTestNetworks, excludeLocalRoutes);
+            isRestrictedToTestNetworks, excludeLocalRoutes, requiresInternetValidation);
     }
 
     /** Checks VPN profiles for interior equality. */
@@ -505,11 +520,12 @@
                 && maxMtu == other.maxMtu
                 && areAuthParamsInline == other.areAuthParamsInline
                 && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks
-                && excludeLocalRoutes == other.excludeLocalRoutes;
+                && excludeLocalRoutes == other.excludeLocalRoutes
+                && requiresInternetValidation == other.requiresInternetValidation;
     }
 
     @NonNull
-    public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() {
+    public static final Creator<VpnProfile> CREATOR = new Creator<>() {
         @Override
         public VpnProfile createFromParcel(Parcel in) {
             return new VpnProfile(in);
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index d8e89b4..888f830 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -22,10 +22,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import java.util.List;
 
 /**
  * Estimates power consumed by the ambient display
@@ -67,29 +63,6 @@
                         powerMah, powerModel);
     }
 
-    /**
-     * Ambient display power is the additional power the screen takes while in ambient display/
-     * screen doze/ always-on display (interchangeable terms) mode. Ambient display power should
-     * be hidden {@link BatteryStatsHelper#shouldHideSipper(BatterySipper)}, but should not be
-     * included in smearing {@link BatteryStatsHelper#removeHiddenBatterySippers(List)}.
-     */
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final long measuredEnergyUC = batteryStats.getScreenDozeMeasuredBatteryConsumptionUC();
-        final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
-        final int powerModel = getPowerModel(measuredEnergyUC);
-        final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs,
-                measuredEnergyUC);
-        if (powerMah > 0) {
-            BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0);
-            bs.usagePowerMah = powerMah;
-            bs.usageTimeMs = durationMs;
-            bs.sumPower();
-            sippers.add(bs);
-        }
-    }
-
     private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
         return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000;
     }
diff --git a/core/java/com/android/internal/os/BatteryChargeCalculator.java b/core/java/com/android/internal/os/BatteryChargeCalculator.java
index 71a1463..912ec8f 100644
--- a/core/java/com/android/internal/os/BatteryChargeCalculator.java
+++ b/core/java/com/android/internal/os/BatteryChargeCalculator.java
@@ -20,10 +20,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import java.util.List;
 
 /**
  * Estimates the battery discharge amounts.
@@ -81,10 +77,4 @@
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setConsumedPower(dischargeMah);
     }
-
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        // Not implemented. The computation is done by BatteryStatsHelper
-    }
 }
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
deleted file mode 100644
index dfd561a..0000000
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.internal.os;
-
-import android.compat.annotation.UnsupportedAppUsage;
-import android.os.BatteryStats.Uid;
-import android.os.Build;
-
-import java.util.List;
-
-/**
- * Contains power usage of an application, system service, or hardware type.
- *
- * @deprecated Please use BatteryStatsManager.getBatteryUsageStats instead.
- */
-@Deprecated
-public class BatterySipper implements Comparable<BatterySipper> {
-    @UnsupportedAppUsage
-    public int userId;
-    @UnsupportedAppUsage
-    public Uid uidObj;
-    @UnsupportedAppUsage
-    public DrainType drainType;
-
-    /**
-     * Smeared power from screen usage.
-     * We split the screen usage power and smear them among apps, based on activity time.
-     * The actual screen usage power may be measured or estimated, affecting the granularity and
-     * accuracy of the smearing, but the smearing algorithm is essentially the same.
-     */
-    public double screenPowerMah;
-
-    /**
-     * Smeared power using proportional method.
-     *
-     * we smear power usage from hidden sippers to all apps proportionally.(except for screen usage)
-     *
-     * @see BatteryStatsHelper#shouldHideSipper(BatterySipper)
-     * @see BatteryStatsHelper#removeHiddenBatterySippers(List)
-     */
-    public double proportionalSmearMah;
-
-    /**
-     * Total power that adding the smeared power.
-     *
-     * @see #sumPower()
-     */
-    public double totalSmearedPowerMah;
-
-    /**
-     * Total power before smearing
-     */
-    @UnsupportedAppUsage
-    public double totalPowerMah;
-
-    /**
-     * Whether we should hide this sipper
-     *
-     * @see BatteryStatsHelper#shouldHideSipper(BatterySipper)
-     */
-    public boolean shouldHide;
-
-    /**
-     * Generic usage time in milliseconds.
-     */
-    @UnsupportedAppUsage
-    public long usageTimeMs;
-
-    /**
-     * Generic power usage in mAh.
-     */
-    public double usagePowerMah;
-
-    // Subsystem usage times.
-    public long audioTimeMs;
-    public long bluetoothRunningTimeMs;
-    public long cameraTimeMs;
-    @UnsupportedAppUsage
-    public long cpuFgTimeMs;
-    @UnsupportedAppUsage
-    public long cpuTimeMs;
-    public long flashlightTimeMs;
-    @UnsupportedAppUsage
-    public long gpsTimeMs;
-    public long videoTimeMs;
-    @UnsupportedAppUsage
-    public long wakeLockTimeMs;
-    @UnsupportedAppUsage
-    public long wifiRunningTimeMs;
-
-    public long mobileRxPackets;
-    public long mobileTxPackets;
-    public long mobileActive;
-    public int mobileActiveCount;
-    public double mobilemspp;         // milliseconds per packet
-    public long wifiRxPackets;
-    public long wifiTxPackets;
-    public long mobileRxBytes;
-    public long mobileTxBytes;
-    public long wifiRxBytes;
-    public long wifiTxBytes;
-    public long btRxBytes;
-    public long btTxBytes;
-    public double percent;
-    public double noCoveragePercent;
-    @UnsupportedAppUsage
-    public String[] mPackages;
-    @UnsupportedAppUsage
-    public String packageWithHighestDrain;
-
-    // Measured in mAh (milli-ampere per hour).
-    // These are included when summed.
-    public double audioPowerMah;
-    public double bluetoothPowerMah;
-    public double cameraPowerMah;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public double cpuPowerMah;
-    public double flashlightPowerMah;
-    public double gpsPowerMah;
-    public double mobileRadioPowerMah;
-    public double sensorPowerMah;
-    public double videoPowerMah;
-    public double wakeLockPowerMah;
-    public double wifiPowerMah;
-    public double systemServiceCpuPowerMah;
-    public double[] customMeasuredPowerMah;
-
-    // Power that is re-attributed to other sippers. For example, for System Server
-    // this represents the power attributed to apps requesting system services.
-    // The value should be negative or zero.
-    public double powerReattributedToOtherSippersMah;
-
-    // Do not include this sipper in results because it is included
-    // in an aggregate sipper.
-    public boolean isAggregated;
-
-    //                           ****************
-    // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
-    // so the ordinal values (and therefore the order) must never change.
-    //                           ****************
-    @UnsupportedAppUsage(implicitMember =
-            "values()[Lcom/android/internal/os/BatterySipper$DrainType;")
-    public enum DrainType {
-        AMBIENT_DISPLAY,
-        @UnsupportedAppUsage
-        APP,
-        BLUETOOTH,
-        CAMERA,
-        CELL,
-        FLASHLIGHT,
-        IDLE,
-        MEMORY,
-        OVERCOUNTED,
-        PHONE,
-        SCREEN,
-        UNACCOUNTED,
-        USER,
-        WIFI,
-    }
-
-    @UnsupportedAppUsage
-    public BatterySipper(DrainType drainType, Uid uid, double value) {
-        this.totalPowerMah = value;
-        this.drainType = drainType;
-        uidObj = uid;
-    }
-
-    public void computeMobilemspp() {
-        long packets = mobileRxPackets + mobileTxPackets;
-        mobilemspp = packets > 0 ? (mobileActive / (double) packets) : 0;
-    }
-
-    @Override
-    public int compareTo(BatterySipper other) {
-        // Over-counted always goes to the bottom.
-        if (drainType != other.drainType) {
-            if (drainType == DrainType.OVERCOUNTED) {
-                // This is "larger"
-                return 1;
-            } else if (other.drainType == DrainType.OVERCOUNTED) {
-                return -1;
-            }
-        }
-        // Return the flipped value because we want the items in descending order
-        return Double.compare(other.totalPowerMah, totalPowerMah);
-    }
-
-    /**
-     * Gets a list of packages associated with the current user
-     */
-    @UnsupportedAppUsage
-    public String[] getPackages() {
-        return mPackages;
-    }
-
-    @UnsupportedAppUsage
-    public int getUid() {
-        // Bail out if the current sipper is not an App sipper.
-        if (uidObj == null) {
-            return 0;
-        }
-        return uidObj.getUid();
-    }
-
-    /**
-     * Add stats from other to this BatterySipper.
-     */
-    @UnsupportedAppUsage
-    public void add(BatterySipper other) {
-        totalPowerMah += other.totalPowerMah;
-        usageTimeMs += other.usageTimeMs;
-        usagePowerMah += other.usagePowerMah;
-        audioTimeMs += other.audioTimeMs;
-        cpuTimeMs += other.cpuTimeMs;
-        gpsTimeMs += other.gpsTimeMs;
-        wifiRunningTimeMs += other.wifiRunningTimeMs;
-        cpuFgTimeMs += other.cpuFgTimeMs;
-        videoTimeMs += other.videoTimeMs;
-        wakeLockTimeMs += other.wakeLockTimeMs;
-        cameraTimeMs += other.cameraTimeMs;
-        flashlightTimeMs += other.flashlightTimeMs;
-        bluetoothRunningTimeMs += other.bluetoothRunningTimeMs;
-        mobileRxPackets += other.mobileRxPackets;
-        mobileTxPackets += other.mobileTxPackets;
-        mobileActive += other.mobileActive;
-        mobileActiveCount += other.mobileActiveCount;
-        wifiRxPackets += other.wifiRxPackets;
-        wifiTxPackets += other.wifiTxPackets;
-        mobileRxBytes += other.mobileRxBytes;
-        mobileTxBytes += other.mobileTxBytes;
-        wifiRxBytes += other.wifiRxBytes;
-        wifiTxBytes += other.wifiTxBytes;
-        btRxBytes += other.btRxBytes;
-        btTxBytes += other.btTxBytes;
-        audioPowerMah += other.audioPowerMah;
-        wifiPowerMah += other.wifiPowerMah;
-        gpsPowerMah += other.gpsPowerMah;
-        cpuPowerMah += other.cpuPowerMah;
-        sensorPowerMah += other.sensorPowerMah;
-        mobileRadioPowerMah += other.mobileRadioPowerMah;
-        wakeLockPowerMah += other.wakeLockPowerMah;
-        cameraPowerMah += other.cameraPowerMah;
-        flashlightPowerMah += other.flashlightPowerMah;
-        bluetoothPowerMah += other.bluetoothPowerMah;
-        screenPowerMah += other.screenPowerMah;
-        videoPowerMah += other.videoPowerMah;
-        proportionalSmearMah += other.proportionalSmearMah;
-        totalSmearedPowerMah += other.totalSmearedPowerMah;
-        systemServiceCpuPowerMah += other.systemServiceCpuPowerMah;
-        if (other.customMeasuredPowerMah != null) {
-            if (customMeasuredPowerMah == null) {
-                customMeasuredPowerMah = new double[other.customMeasuredPowerMah.length];
-            }
-            if (customMeasuredPowerMah.length == other.customMeasuredPowerMah.length) {
-                // This should always be true.
-                for (int idx = 0; idx < other.customMeasuredPowerMah.length; idx++) {
-                    customMeasuredPowerMah[idx] += other.customMeasuredPowerMah[idx];
-                }
-            }
-        }
-        powerReattributedToOtherSippersMah += other.powerReattributedToOtherSippersMah;
-    }
-
-    /**
-     * Sum all the powers and store the value into `value`.
-     * Also sum the {@code smearedTotalPowerMah} by adding smeared powerMah.
-     *
-     * @return the sum of all the power in this BatterySipper.
-     */
-    public double sumPower() {
-        totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
-                sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
-                flashlightPowerMah + bluetoothPowerMah + audioPowerMah + videoPowerMah
-                + systemServiceCpuPowerMah;
-        if (customMeasuredPowerMah != null) {
-            for (int idx = 0; idx < customMeasuredPowerMah.length; idx++) {
-                totalPowerMah += customMeasuredPowerMah[idx];
-            }
-        }
-
-        // powerAttributedToOtherSippersMah is negative or zero
-        totalPowerMah = totalPowerMah + powerReattributedToOtherSippersMah;
-
-        totalSmearedPowerMah = totalPowerMah + screenPowerMah + proportionalSmearMah;
-
-        return totalPowerMah;
-    }
-}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
deleted file mode 100644
index 4515a09..0000000
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ /dev/null
@@ -1,703 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.hardware.SensorManager;
-import android.os.BatteryStats;
-import android.os.BatteryStats.Uid;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.MemoryFile;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.os.BatterySipper.DrainType;
-import com.android.internal.util.ArrayUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A helper class for retrieving the power usage information for all applications and services.
- *
- * The caller must initialize this class as soon as activity object is ready to use (for example, in
- * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
- *
- * @deprecated Please use BatteryStatsManager.getBatteryUsageStats instead.
- */
-@Deprecated
-public class BatteryStatsHelper {
-    private static final boolean DEBUG = false;
-
-    private static final String TAG = BatteryStatsHelper.class.getSimpleName();
-
-    private static BatteryStats sStatsXfer;
-    private static Intent sBatteryBroadcastXfer;
-    private static ArrayMap<File, BatteryStats> sFileXfer = new ArrayMap<>();
-
-    final private Context mContext;
-    final private boolean mCollectBatteryBroadcast;
-    final private boolean mWifiOnly;
-
-    private List<PowerCalculator> mPowerCalculators;
-
-    @UnsupportedAppUsage
-    private IBatteryStats mBatteryInfo;
-    private BatteryStats mStats;
-    private Intent mBatteryBroadcast;
-    @UnsupportedAppUsage
-    private PowerProfile mPowerProfile;
-
-    private String[] mSystemPackageArray;
-    private String[] mServicepackageArray;
-    private PackageManager mPackageManager;
-
-    /**
-     * List of apps using power.
-     */
-    @UnsupportedAppUsage
-    private final List<BatterySipper> mUsageList = new ArrayList<>();
-
-    private final List<BatterySipper> mMobilemsppList = new ArrayList<>();
-
-    private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
-
-    long mRawRealtimeUs;
-    long mRawUptimeUs;
-    long mBatteryRealtimeUs;
-    long mBatteryUptimeUs;
-    long mBatteryTimeRemainingUs;
-    long mChargeTimeRemainingUs;
-
-    private long mStatsPeriod = 0;
-
-    // The largest entry by power.
-    private double mMaxPower = 1;
-
-    // The largest real entry by power (not undercounted or overcounted).
-    private double mMaxRealPower = 1;
-
-    // Total computed power.
-    private double mComputedPower;
-    private double mTotalPower;
-    private double mMinDrainedPower;
-    private double mMaxDrainedPower;
-
-    public static boolean checkWifiOnly(Context context) {
-        final TelephonyManager tm = context.getSystemService(TelephonyManager.class);
-        if (tm == null) {
-            return false;
-        }
-        return !tm.isDataCapable();
-    }
-
-    @UnsupportedAppUsage
-    public BatteryStatsHelper(Context context) {
-        this(context, true);
-    }
-
-    @UnsupportedAppUsage
-    public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast) {
-        this(context, collectBatteryBroadcast, checkWifiOnly(context));
-    }
-
-    @UnsupportedAppUsage
-    public BatteryStatsHelper(Context context, boolean collectBatteryBroadcast, boolean wifiOnly) {
-        mContext = context;
-        mCollectBatteryBroadcast = collectBatteryBroadcast;
-        mWifiOnly = wifiOnly;
-        mPackageManager = context.getPackageManager();
-
-        final Resources resources = context.getResources();
-        mSystemPackageArray = resources.getStringArray(
-                com.android.internal.R.array.config_batteryPackageTypeSystem);
-        mServicepackageArray = resources.getStringArray(
-                com.android.internal.R.array.config_batteryPackageTypeService);
-    }
-
-    public void storeStatsHistoryInFile(String fname) {
-        synchronized (sFileXfer) {
-            File path = makeFilePath(mContext, fname);
-            sFileXfer.put(path, this.getStats());
-            FileOutputStream fout = null;
-            try {
-                fout = new FileOutputStream(path);
-                Parcel hist = Parcel.obtain();
-                getStats().writeToParcelWithoutUids(hist, 0);
-                byte[] histData = hist.marshall();
-                fout.write(histData);
-            } catch (IOException e) {
-                Log.w(TAG, "Unable to write history to file", e);
-            } finally {
-                if (fout != null) {
-                    try {
-                        fout.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-        }
-    }
-
-    public static BatteryStats statsFromFile(Context context, String fname) {
-        synchronized (sFileXfer) {
-            File path = makeFilePath(context, fname);
-            BatteryStats stats = sFileXfer.get(path);
-            if (stats != null) {
-                return stats;
-            }
-            FileInputStream fin = null;
-            try {
-                fin = new FileInputStream(path);
-                byte[] data = readFully(fin);
-                Parcel parcel = Parcel.obtain();
-                parcel.unmarshall(data, 0, data.length);
-                parcel.setDataPosition(0);
-                return com.android.internal.os.BatteryStatsImpl.CREATOR.createFromParcel(parcel);
-            } catch (IOException e) {
-                Log.w(TAG, "Unable to read history to file", e);
-            } finally {
-                if (fin != null) {
-                    try {
-                        fin.close();
-                    } catch (IOException e) {
-                    }
-                }
-            }
-        }
-        return getStats(IBatteryStats.Stub.asInterface(
-                ServiceManager.getService(BatteryStats.SERVICE_NAME)), true);
-    }
-
-    @UnsupportedAppUsage
-    public static void dropFile(Context context, String fname) {
-        makeFilePath(context, fname).delete();
-    }
-
-    private static File makeFilePath(Context context, String fname) {
-        return new File(context.getFilesDir(), fname);
-    }
-
-    /** Clears the current stats and forces recreating for future use. */
-    @UnsupportedAppUsage
-    public void clearStats() {
-        mStats = null;
-    }
-
-    @UnsupportedAppUsage
-    public BatteryStats getStats() {
-        return getStats(true /* updateAll */);
-    }
-
-    /** Retrieves stats from BatteryService, optionally getting updated numbers */
-    public BatteryStats getStats(boolean updateAll) {
-        if (mStats == null) {
-            load(updateAll);
-        }
-        return mStats;
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public Intent getBatteryBroadcast() {
-        if (mBatteryBroadcast == null && mCollectBatteryBroadcast) {
-            load();
-        }
-        return mBatteryBroadcast;
-    }
-
-    public PowerProfile getPowerProfile() {
-        return mPowerProfile;
-    }
-
-    public void create(BatteryStats stats) {
-        mPowerProfile = new PowerProfile(mContext);
-        mStats = stats;
-    }
-
-    @UnsupportedAppUsage
-    public void create(Bundle icicle) {
-        if (icicle != null) {
-            mStats = sStatsXfer;
-            mBatteryBroadcast = sBatteryBroadcastXfer;
-        }
-        mBatteryInfo = IBatteryStats.Stub.asInterface(
-                ServiceManager.getService(BatteryStats.SERVICE_NAME));
-        mPowerProfile = new PowerProfile(mContext);
-    }
-
-    @UnsupportedAppUsage
-    public void storeState() {
-        sStatsXfer = mStats;
-        sBatteryBroadcastXfer = mBatteryBroadcast;
-    }
-
-    /**
-     * Refreshes the power usage list.
-     */
-    @UnsupportedAppUsage
-    public void refreshStats(int statsType, int asUser) {
-        SparseArray<UserHandle> users = new SparseArray<>(1);
-        users.put(asUser, new UserHandle(asUser));
-        refreshStats(statsType, users);
-    }
-
-    /**
-     * Refreshes the power usage list.
-     */
-    @UnsupportedAppUsage
-    public void refreshStats(int statsType, List<UserHandle> asUsers) {
-        final int n = asUsers.size();
-        SparseArray<UserHandle> users = new SparseArray<>(n);
-        for (int i = 0; i < n; ++i) {
-            UserHandle userHandle = asUsers.get(i);
-            users.put(userHandle.getIdentifier(), userHandle);
-        }
-        refreshStats(statsType, users);
-    }
-
-    /**
-     * Refreshes the power usage list.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
-        refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
-                SystemClock.uptimeMillis() * 1000);
-    }
-
-    public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
-            long rawUptimeUs) {
-        if (statsType != BatteryStats.STATS_SINCE_CHARGED) {
-            Log.w(TAG, "refreshStats called for statsType " + statsType + " but only "
-                    + "STATS_SINCE_CHARGED is supported. Using STATS_SINCE_CHARGED instead.");
-        }
-
-        // Initialize mStats if necessary.
-        getStats();
-
-        mMaxPower = 0;
-        mMaxRealPower = 0;
-        mComputedPower = 0;
-        mTotalPower = 0;
-
-        mUsageList.clear();
-        mMobilemsppList.clear();
-
-        if (mStats == null) {
-            return;
-        }
-
-        if (mPowerCalculators == null) {
-            mPowerCalculators = new ArrayList<>();
-
-            // Power calculators are applied in the order of registration
-            mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
-            if (!mWifiOnly) {
-                mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
-            }
-            mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new SensorPowerCalculator(
-                    mContext.getSystemService(SensorManager.class)));
-            mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new MediaPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
-            mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
-
-            mPowerCalculators.add(new UserPowerCalculator());
-        }
-
-        for (int i = 0, size = mPowerCalculators.size(); i < size; i++) {
-            mPowerCalculators.get(i).reset();
-        }
-
-        mStatsType = statsType;
-        mRawUptimeUs = rawUptimeUs;
-        mRawRealtimeUs = rawRealtimeUs;
-        mBatteryUptimeUs = mStats.getBatteryUptime(rawUptimeUs);
-        mBatteryRealtimeUs = mStats.getBatteryRealtime(rawRealtimeUs);
-        mBatteryTimeRemainingUs = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
-        mChargeTimeRemainingUs = mStats.computeChargeTimeRemaining(rawRealtimeUs);
-        mStatsPeriod = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
-
-        if (DEBUG) {
-            Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs / 1000) + " uptime="
-                    + (rawUptimeUs / 1000));
-            Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtimeUs / 1000) + " uptime="
-                    + (mBatteryUptimeUs / 1000));
-            Log.d(TAG, "Battery type time: realtime=" + (mStatsPeriod / 1000) + " uptime="
-                    + (mStats.computeBatteryUptime(rawRealtimeUs, mStatsType) / 1000));
-        }
-        mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
-                * mPowerProfile.getBatteryCapacity()) / 100;
-        mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
-                * mPowerProfile.getBatteryCapacity()) / 100;
-
-        // Create list of (almost all) sippers, calculate their usage, and put them in mUsageList.
-        processAppUsage(asUsers);
-
-        Collections.sort(mUsageList);
-
-        Collections.sort(mMobilemsppList,
-                (lhs, rhs) -> Double.compare(rhs.mobilemspp, lhs.mobilemspp));
-
-        // At this point, we've sorted the list so we are guaranteed the max values are at the top.
-        // We have only added real powers so far.
-        if (!mUsageList.isEmpty()) {
-            mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
-            final int usageListCount = mUsageList.size();
-            for (int i = 0; i < usageListCount; i++) {
-                mComputedPower += mUsageList.get(i).totalPowerMah;
-            }
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "Accuracy: total computed=" + PowerCalculator.formatCharge(mComputedPower)
-                    + ", min discharge=" + PowerCalculator.formatCharge(mMinDrainedPower)
-                    + ", max discharge=" + PowerCalculator.formatCharge(mMaxDrainedPower));
-        }
-
-        mTotalPower = mComputedPower;
-        if (mStats.getLowDischargeAmountSinceCharge() > 1) {
-            if (mMinDrainedPower > mComputedPower) {
-                double amount = mMinDrainedPower - mComputedPower;
-                mTotalPower = mMinDrainedPower;
-                BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);
-
-                // Insert the BatterySipper in its sorted position.
-                int index = Collections.binarySearch(mUsageList, bs);
-                if (index < 0) {
-                    index = -(index + 1);
-                }
-                mUsageList.add(index, bs);
-                mMaxPower = Math.max(mMaxPower, amount);
-            } else if (mMaxDrainedPower < mComputedPower) {
-                double amount = mComputedPower - mMaxDrainedPower;
-
-                // Insert the BatterySipper in its sorted position.
-                BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
-                int index = Collections.binarySearch(mUsageList, bs);
-                if (index < 0) {
-                    index = -(index + 1);
-                }
-                mUsageList.add(index, bs);
-                mMaxPower = Math.max(mMaxPower, amount);
-            }
-        }
-
-        // Smear it!
-        final double hiddenPowerMah = removeHiddenBatterySippers(mUsageList);
-        final double totalRemainingPower = getTotalPower() - hiddenPowerMah;
-        if (Math.abs(totalRemainingPower) > 1e-3) {
-            for (int i = 0, size = mUsageList.size(); i < size; i++) {
-                final BatterySipper sipper = mUsageList.get(i);
-                if (!sipper.shouldHide) {
-                    sipper.proportionalSmearMah = hiddenPowerMah
-                            * ((sipper.totalPowerMah + sipper.screenPowerMah)
-                            / totalRemainingPower);
-                    sipper.sumPower();
-                }
-            }
-        }
-    }
-
-    private void processAppUsage(SparseArray<UserHandle> asUsers) {
-        final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
-
-        final ArrayList<BatterySipper> sippers = new ArrayList<>(uidStats.size());
-
-        for (int iu = 0, size = uidStats.size(); iu < size; iu++) {
-            final Uid u = uidStats.valueAt(iu);
-            sippers.add(new BatterySipper(DrainType.APP, u, 0));
-        }
-
-        for (int i = 0, size = mPowerCalculators.size(); i < size; i++) {
-            final PowerCalculator calculator = mPowerCalculators.get(i);
-            calculator.calculate(sippers, mStats, mRawRealtimeUs, mRawUptimeUs, mStatsType,
-                    asUsers);
-        }
-
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper sipper = sippers.get(i);
-            final double totalPower = sipper.sumPower();
-            if (DEBUG && totalPower != 0) {
-                Log.d(TAG, String.format("UID %d: total power=%s", sipper.getUid(),
-                        PowerCalculator.formatCharge(totalPower)));
-            }
-
-            // Add the sipper to the list if it is consuming power.
-            if (totalPower != 0 || sipper.getUid() == 0) {
-                if (sipper.drainType == DrainType.APP) {
-                    sipper.computeMobilemspp();
-                    if (sipper.mobilemspp != 0) {
-                        mMobilemsppList.add(sipper);
-                    }
-                }
-
-                if (!sipper.isAggregated) {
-                    mUsageList.add(sipper);
-                }
-            }
-        }
-    }
-
-    @UnsupportedAppUsage
-    public List<BatterySipper> getUsageList() {
-        return mUsageList;
-    }
-
-    public List<BatterySipper> getMobilemsppList() {
-        return mMobilemsppList;
-    }
-
-    public long getStatsPeriod() {
-        return mStatsPeriod;
-    }
-
-    public int getStatsType() {
-        return mStatsType;
-    }
-
-    @UnsupportedAppUsage
-    public double getMaxPower() {
-        return mMaxPower;
-    }
-
-    public double getMaxRealPower() {
-        return mMaxRealPower;
-    }
-
-    @UnsupportedAppUsage
-    public double getTotalPower() {
-        return mTotalPower;
-    }
-
-    public double getComputedPower() {
-        return mComputedPower;
-    }
-
-    public double getMinDrainedPower() {
-        return mMinDrainedPower;
-    }
-
-    public double getMaxDrainedPower() {
-        return mMaxDrainedPower;
-    }
-
-    public static byte[] readFully(FileInputStream stream) throws java.io.IOException {
-        return readFully(stream, stream.available());
-    }
-
-    public static byte[] readFully(FileInputStream stream, int avail) throws java.io.IOException {
-        int pos = 0;
-        byte[] data = new byte[avail];
-        while (true) {
-            int amt = stream.read(data, pos, data.length - pos);
-            //Log.i("foo", "Read " + amt + " bytes at " + pos
-            //        + " of avail " + data.length);
-            if (amt <= 0) {
-                //Log.i("foo", "**** FINISHED READING: pos=" + pos
-                //        + " len=" + data.length);
-                return data;
-            }
-            pos += amt;
-            avail = stream.available();
-            if (avail > data.length - pos) {
-                byte[] newData = new byte[pos + avail];
-                System.arraycopy(data, 0, newData, 0, pos);
-                data = newData;
-            }
-        }
-    }
-
-    /**
-     * Mark the {@link BatterySipper} that we should hide.
-     *
-     * @param sippers sipper list that need to check and remove
-     * @return the total power of the hidden items of {@link BatterySipper}
-     * for proportional smearing
-     */
-    public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
-        double proportionalSmearPowerMah = 0;
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper sipper = sippers.get(i);
-            sipper.shouldHide = shouldHideSipper(sipper);
-            if (sipper.shouldHide) {
-                if (sipper.drainType != DrainType.OVERCOUNTED
-                        && sipper.drainType != DrainType.SCREEN
-                        && sipper.drainType != DrainType.AMBIENT_DISPLAY
-                        && sipper.drainType != DrainType.UNACCOUNTED
-                        && sipper.drainType != DrainType.BLUETOOTH
-                        && sipper.drainType != DrainType.WIFI
-                        && sipper.drainType != DrainType.IDLE) {
-                    // Don't add it if it is overcounted, unaccounted or screen
-                    proportionalSmearPowerMah += sipper.totalPowerMah;
-                }
-            }
-        }
-        return proportionalSmearPowerMah;
-    }
-
-    /**
-     * Check whether we should hide the battery sipper.
-     */
-    public boolean shouldHideSipper(BatterySipper sipper) {
-        final DrainType drainType = sipper.drainType;
-
-        return drainType == DrainType.IDLE
-                || drainType == DrainType.CELL
-                || drainType == DrainType.SCREEN
-                || drainType == DrainType.AMBIENT_DISPLAY
-                || drainType == DrainType.UNACCOUNTED
-                || drainType == DrainType.OVERCOUNTED
-                || isTypeService(sipper)
-                || isTypeSystem(sipper);
-    }
-
-    /**
-     * Check whether {@code sipper} is type service
-     */
-    public boolean isTypeService(BatterySipper sipper) {
-        final String[] packages = mPackageManager.getPackagesForUid(sipper.getUid());
-        if (packages == null) {
-            return false;
-        }
-
-        for (String packageName : packages) {
-            if (ArrayUtils.contains(mServicepackageArray, packageName)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Check whether {@code sipper} is type system
-     */
-    public boolean isTypeSystem(BatterySipper sipper) {
-        final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
-        sipper.mPackages = mPackageManager.getPackagesForUid(uid);
-        // Classify all the sippers to type system if the range of uid is 0...FIRST_APPLICATION_UID
-        if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
-            return true;
-        } else if (sipper.mPackages != null) {
-            for (final String packageName : sipper.mPackages) {
-                if (ArrayUtils.contains(mSystemPackageArray, packageName)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    public long convertUsToMs(long timeUs) {
-        return timeUs / 1000;
-    }
-
-    public long convertMsToUs(long timeMs) {
-        return timeMs * 1000;
-    }
-
-    @VisibleForTesting
-    public void setPackageManager(PackageManager packageManager) {
-        mPackageManager = packageManager;
-    }
-
-    @VisibleForTesting
-    public void setSystemPackageArray(String[] array) {
-        mSystemPackageArray = array;
-    }
-
-    @VisibleForTesting
-    public void setServicePackageArray(String[] array) {
-        mServicepackageArray = array;
-    }
-
-    @UnsupportedAppUsage
-    private void load() {
-        load(true);
-    }
-
-    private void load(boolean updateAll) {
-        if (mBatteryInfo == null) {
-            return;
-        }
-        mStats = getStats(mBatteryInfo, updateAll);
-        if (mCollectBatteryBroadcast) {
-            mBatteryBroadcast = mContext.registerReceiver(null,
-                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-        }
-    }
-
-    private static BatteryStatsImpl getStats(IBatteryStats service, boolean updateAll) {
-        try {
-            ParcelFileDescriptor pfd = service.getStatisticsStream(updateAll);
-            if (pfd != null) {
-                if (false) {
-                    Log.d(TAG, "selinux context: "
-                            + SELinux.getFileContext(pfd.getFileDescriptor()));
-                }
-                try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
-                    byte[] data = readFully(fis, MemoryFile.getSize(pfd.getFileDescriptor()));
-                    Parcel parcel = Parcel.obtain();
-                    parcel.unmarshall(data, 0, data.length);
-                    parcel.setDataPosition(0);
-                    BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
-                            .createFromParcel(parcel);
-                    return stats;
-                } catch (IOException e) {
-                    Log.w(TAG, "Unable to read statistics stream", e);
-                }
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "RemoteException:", e);
-        }
-        return new BatteryStatsImpl();
-    }
-}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5ba45c9..70b9639 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -9211,7 +9211,7 @@
          * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
          * since last marked. Also sets the mark time for both these timers.
          *
-         * @see BatteryStatsHelper#getProcessForegroundTimeMs
+         * @see CpuPowerCalculator
          *
          * @param doCalc if true, then calculate the minimum; else don't bother and return 0. Either
          *               way, the mark is set.
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index e4d5fb7..a1c1917 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -73,7 +73,7 @@
                 mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
-                if (!BatteryStatsHelper.checkWifiOnly(mContext)) {
+                if (!BatteryStats.checkWifiOnly(mContext)) {
                     mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
                 }
                 mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index afa41a7..2ebf689 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -21,14 +21,11 @@
 import android.os.BatteryStats.ControllerActivityCounter;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.Process;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.util.Arrays;
-import java.util.List;
 
 public class BluetoothPowerCalculator extends PowerCalculator {
     private static final String TAG = "BluetoothPowerCalc";
@@ -106,7 +103,7 @@
                 powerAndDuration.durationMs - powerAndDuration.totalDurationMs);
         if (DEBUG) {
             Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs)
-                    + " power=" + formatCharge(powerAndDuration.powerMah));
+                    + " power=" + BatteryStats.formatCharge(powerAndDuration.powerMah));
         }
 
         builder.getAggregateBatteryConsumerBuilder(
@@ -159,73 +156,6 @@
         }
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        if (!mHasBluetoothPowerController || !batteryStats.hasBluetoothActivityReporting()) {
-            return;
-        }
-
-        PowerAndDuration powerAndDuration = new PowerAndDuration();
-
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                calculateApp(app, app.uidObj, statsType, powerAndDuration);
-            }
-        }
-
-        BatterySipper bs = new BatterySipper(BatterySipper.DrainType.BLUETOOTH, null, 0);
-        final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(measuredChargeUC);
-        final ControllerActivityCounter activityCounter =
-                batteryStats.getBluetoothControllerActivity();
-        calculatePowerAndDuration(null, powerModel, measuredChargeUC, activityCounter, false,
-                powerAndDuration);
-
-        // Subtract what the apps used, but clamp to 0.
-        final double powerMah = Math.max(0,
-                powerAndDuration.powerMah - powerAndDuration.totalPowerMah);
-        final long durationMs = Math.max(0,
-                powerAndDuration.durationMs - powerAndDuration.totalDurationMs);
-        if (DEBUG && powerMah != 0) {
-            Log.d(TAG, "Bluetooth active: time=" + (durationMs)
-                    + " power=" + formatCharge(powerMah));
-        }
-
-        bs.bluetoothPowerMah = powerMah;
-        bs.bluetoothRunningTimeMs = durationMs;
-
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            BatterySipper app = sippers.get(i);
-            if (app.getUid() == Process.BLUETOOTH_UID) {
-                if (DEBUG) Log.d(TAG, "Bluetooth adding sipper " + app + ": cpu=" + app.cpuTimeMs);
-                app.isAggregated = true;
-                bs.add(app);
-            }
-        }
-        if (bs.sumPower() > 0) {
-            sippers.add(bs);
-        }
-    }
-
-    private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
-            PowerAndDuration powerAndDuration) {
-        final long measuredChargeUC = u.getBluetoothMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(measuredChargeUC);
-        final ControllerActivityCounter activityCounter = u.getBluetoothControllerActivity();
-        calculatePowerAndDuration(u, powerModel, measuredChargeUC, activityCounter,
-                false, powerAndDuration);
-
-        app.bluetoothRunningTimeMs = powerAndDuration.durationMs;
-        app.bluetoothPowerMah = powerAndDuration.powerMah;
-        app.btRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_RX_DATA, statsType);
-        app.btTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_BT_TX_DATA, statsType);
-
-        powerAndDuration.totalDurationMs += powerAndDuration.durationMs;
-        powerAndDuration.totalPowerMah += powerAndDuration.powerMah;
-    }
-
     /** Returns bluetooth power usage based on the best data available. */
     private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid,
             @BatteryConsumer.PowerModel int powerModel,
diff --git a/core/java/com/android/internal/os/CameraPowerCalculator.java b/core/java/com/android/internal/os/CameraPowerCalculator.java
index 7bccab5..d0749e0 100644
--- a/core/java/com/android/internal/os/CameraPowerCalculator.java
+++ b/core/java/com/android/internal/os/CameraPowerCalculator.java
@@ -69,14 +69,4 @@
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
     }
-
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        final long durationMs = mPowerEstimator.calculateDuration(u.getCameraTurnedOnTimer(),
-                rawRealtimeUs, statsType);
-        final double powerMah = mPowerEstimator.calculatePower(durationMs);
-        app.cameraTimeMs = durationMs;
-        app.cameraPowerMah = powerMah;
-    }
 }
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 9940a0a..1fc2baf 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -20,13 +20,11 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.util.Arrays;
-import java.util.List;
 
 public class CpuPowerCalculator extends PowerCalculator {
     private static final String TAG = "CpuPowerCalculator";
@@ -217,29 +215,6 @@
         }
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        Result result = new Result();
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                calculateApp(app, app.uidObj, statsType, result);
-            }
-        }
-    }
-
-    private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, Result result) {
-        final long consumptionUC = u.getCpuMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC);
-        calculatePowerAndDuration(u, powerModel, consumptionUC, statsType, result);
-
-        app.cpuPowerMah = result.powerMah;
-        app.cpuTimeMs = result.durationMs;
-        app.cpuFgTimeMs = result.durationFgMs;
-        app.packageWithHighestDrain = result.packageWithHighestDrain;
-    }
-
     private void calculatePowerAndDuration(BatteryStats.Uid u,
             @BatteryConsumer.PowerModel int powerModel, long consumptionUC, int statsType,
             Result result) {
@@ -258,7 +233,7 @@
 
         if (DEBUG && (durationMs != 0 || powerMah != 0)) {
             Log.d(TAG, "UID " + u.getUid() + ": CPU time=" + durationMs + " ms power="
-                    + formatCharge(powerMah));
+                    + BatteryStats.formatCharge(powerMah));
         }
 
         // Keep track of the package with highest drain.
@@ -325,7 +300,7 @@
                     if (DEBUG) {
                         Log.d(TAG, "UID " + u.getUid() + ": CPU cluster #" + cluster
                                 + " clusterTimeMs=" + cpuClusterTimes[cluster]
-                                + " power=" + formatCharge(power));
+                                + " power=" + BatteryStats.formatCharge(power));
                     }
                 }
             } else {
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index 4cb7ef1..cbbb526 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -102,16 +102,6 @@
         return newTotalPowerMah;
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        updateCustomMeasuredPowerMah(app, u.getCustomConsumerMeasuredBatteryConsumptionUC());
-    }
-
-    private void updateCustomMeasuredPowerMah(BatterySipper sipper, long[] measuredChargeUC) {
-        sipper.customMeasuredPowerMah = calculateMeasuredEnergiesMah(measuredChargeUC);
-    }
-
     private double[] calculateMeasuredEnergiesMah(long[] measuredChargeUC) {
         if (measuredChargeUC == null) {
             return null;
diff --git a/core/java/com/android/internal/os/FlashlightPowerCalculator.java b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
index 7d3f962..ce3e7b9 100644
--- a/core/java/com/android/internal/os/FlashlightPowerCalculator.java
+++ b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
@@ -66,14 +66,4 @@
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, powerMah);
     }
-
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        final long durationMs = mPowerEstimator.calculateDuration(u.getFlashlightTurnedOnTimer(),
-                rawRealtimeUs, statsType);
-        final double powerMah = mPowerEstimator.calculatePower(durationMs);
-        app.flashlightTimeMs = durationMs;
-        app.flashlightPowerMah = powerMah;
-    }
 }
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index a836ddb..0f78306 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -21,11 +21,8 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.util.SparseArray;
 
-import java.util.List;
-
 /**
  * Estimates the amount of power consumed by the GNSS (e.g. GPS).
  */
@@ -100,41 +97,6 @@
         return powerMah;
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs, statsType);
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                final long consumptionUC =
-                        app.uidObj.getGnssMeasuredBatteryConsumptionUC();
-                final int powerModel = getPowerModel(consumptionUC);
-                calculateApp(app, app.uidObj, powerModel, rawRealtimeUs, averageGnssPowerMa,
-                        consumptionUC);
-            }
-        }
-    }
-
-    private void calculateApp(BatterySipper app, BatteryStats.Uid u,
-            @BatteryConsumer.PowerModel int powerModel, long rawRealtimeUs,
-            double averageGnssPowerMa, long measuredChargeUC) {
-        final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
-
-        final double powerMah;
-        switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
-                powerMah = uCtoMah(measuredChargeUC);
-                break;
-            case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
-            default:
-                powerMah = computePower(durationMs, averageGnssPowerMa);
-        }
-
-        app.gpsTimeMs = durationMs;
-        app.gpsPowerMah = powerMah;
-    }
-
     private long computeDuration(BatteryStats.Uid u, long rawRealtimeUs, int statsType) {
         final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
         final BatteryStats.Uid.Sensor sensor = sensorStats.get(BatteryStats.Uid.Sensor.GPS);
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index 9491b3b..5b2052e 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -20,11 +20,7 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.UserHandle;
 import android.util.Log;
-import android.util.SparseArray;
-
-import java.util.List;
 
 /**
  * Estimates the amount of power consumed when the device is idle.
@@ -64,20 +60,6 @@
         }
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs, statsType);
-
-        if (mPowerMah != 0) {
-            BatterySipper bs = new BatterySipper(BatterySipper.DrainType.IDLE, null, 0);
-            bs.usagePowerMah = mPowerMah;
-            bs.usageTimeMs = mDurationMs;
-            bs.sumPower();
-            sippers.add(bs);
-        }
-    }
-
     /**
      * Calculates the baseline power usage for the device when it is in suspend and idle.
      * The device is drawing POWER_CPU_SUSPEND power at its lowest power state.
@@ -97,9 +79,9 @@
         mPowerMah = suspendPowerMah + idlePowerMah;
         if (DEBUG && mPowerMah != 0) {
             Log.d(TAG, "Suspend: time=" + (batteryRealtimeUs / 1000)
-                    + " power=" + formatCharge(suspendPowerMah));
+                    + " power=" + BatteryStats.formatCharge(suspendPowerMah));
             Log.d(TAG, "Idle: time=" + (batteryUptimeUs / 1000)
-                    + " power=" + formatCharge(idlePowerMah));
+                    + " power=" + BatteryStats.formatCharge(idlePowerMah));
         }
         mDurationMs = batteryRealtimeUs / 1000;
     }
diff --git a/core/java/com/android/internal/os/MediaPowerCalculator.java b/core/java/com/android/internal/os/MediaPowerCalculator.java
deleted file mode 100644
index fff96da..0000000
--- a/core/java/com/android/internal/os/MediaPowerCalculator.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.internal.os;
-
-import android.os.BatteryConsumer;
-import android.os.BatteryStats;
-
-/**
- * A {@link PowerCalculator} to calculate power consumed by audio and video hardware.
- *
- * Also see {@link PowerProfile#POWER_AUDIO} and {@link PowerProfile#POWER_VIDEO}.
- */
-public class MediaPowerCalculator extends PowerCalculator {
-    private static final int MS_IN_HR = 1000 * 60 * 60;
-    private final double mAudioAveragePowerMa;
-    private final double mVideoAveragePowerMa;
-
-    public MediaPowerCalculator(PowerProfile profile) {
-        mAudioAveragePowerMa = profile.getAveragePower(PowerProfile.POWER_AUDIO);
-        mVideoAveragePowerMa = profile.getAveragePower(PowerProfile.POWER_VIDEO);
-    }
-
-    @Override
-    public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
-        return powerComponent == BatteryConsumer.POWER_COMPONENT_VIDEO
-                || powerComponent == BatteryConsumer.POWER_COMPONENT_AUDIO;
-    }
-
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        // Calculate audio power usage, an estimate based on the average power routed to different
-        // components like speaker, bluetooth, usb-c, earphone, etc.
-        final BatteryStats.Timer audioTimer = u.getAudioTurnedOnTimer();
-        if (audioTimer == null) {
-            app.audioTimeMs = 0;
-            app.audioPowerMah = 0;
-        } else {
-            final long totalTime = audioTimer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
-            app.audioTimeMs = totalTime;
-            app.audioPowerMah = (totalTime * mAudioAveragePowerMa) / MS_IN_HR;
-        }
-
-        // Calculate video power usage.
-        final BatteryStats.Timer videoTimer = u.getVideoTurnedOnTimer();
-        if (videoTimer == null) {
-            app.videoTimeMs = 0;
-            app.videoPowerMah = 0;
-        } else {
-            final long totalTime = videoTimer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
-            app.videoTimeMs = totalTime;
-            app.videoPowerMah = (totalTime * mVideoAveragePowerMa) / MS_IN_HR;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 0440a58..0d3040c 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -4,11 +4,7 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.UserHandle;
 import android.util.LongSparseArray;
-import android.util.SparseArray;
-
-import java.util.List;
 
 public class MemoryPowerCalculator extends PowerCalculator {
     public static final String TAG = "MemoryPowerCalculator";
@@ -41,20 +37,6 @@
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY, powerMah);
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
-        final double powerMah = calculatePower(batteryStats, rawRealtimeUs, statsType);
-        BatterySipper memory = new BatterySipper(BatterySipper.DrainType.MEMORY, null, 0);
-        memory.usageTimeMs = durationMs;
-        memory.usagePowerMah = powerMah;
-        memory.sumPower();
-        if (memory.totalPowerMah > 0) {
-            sippers.add(memory);
-        }
-    }
-
     private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
         long usageDurationMs = 0;
         LongSparseArray<? extends BatteryStats.Timer> timers = batteryStats.getKernelMemoryStats();
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index 3a50b73..f4624de 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -20,13 +20,10 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.telephony.CellSignalStrength;
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.util.List;
-
 public class MobileRadioPowerCalculator extends PowerCalculator {
     private static final String TAG = "MobRadioPowerCalculator";
     private static final boolean DEBUG = PowerCalculator.DEBUG;
@@ -170,65 +167,6 @@
         }
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        PowerAndDuration total = new PowerAndDuration();
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                final BatteryStats.Uid u = app.uidObj;
-                calculateApp(app, u, statsType, total);
-            }
-        }
-
-        BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
-        final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC);
-        calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC);
-        if (total.remainingPowerMah != 0) {
-            if (total.signalDurationMs != 0) {
-                radio.noCoveragePercent =
-                        total.noCoverageDurationMs * 100.0 / total.signalDurationMs;
-            }
-            radio.mobileActive = total.durationMs;
-            radio.mobileActiveCount = batteryStats.getMobileRadioActiveUnknownCount(statsType);
-            radio.mobileRadioPowerMah = total.remainingPowerMah;
-            radio.sumPower();
-        }
-        if (radio.totalPowerMah > 0) {
-            sippers.add(radio);
-        }
-    }
-
-    private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
-            PowerAndDuration total) {
-        app.mobileActive = calculateDuration(u, statsType);
-
-        final long consumptionUC =  u.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC);
-        app.mobileRadioPowerMah = calculatePower(u, powerModel, app.mobileActive, consumptionUC);
-        total.totalAppDurationMs += app.mobileActive;
-
-        // Add cost of mobile traffic.
-        app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
-                statsType);
-        app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
-                statsType);
-        app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
-        app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
-                statsType);
-        app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
-                statsType);
-
-        if (DEBUG && app.mobileRadioPowerMah != 0) {
-            Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
-                    + (app.mobileRxPackets + app.mobileTxPackets)
-                    + " active time " + app.mobileActive
-                    + " power=" + formatCharge(app.mobileRadioPowerMah));
-        }
-    }
-
     private long calculateDuration(BatteryStats.Uid u, int statsType) {
         return u.getMobileRadioActiveTime(statsType) / 1000;
     }
@@ -262,7 +200,7 @@
                 final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
                 if (DEBUG && p != 0) {
                     Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
-                            + formatCharge(p));
+                            + BatteryStats.formatCharge(p));
                 }
                 powerMah += p;
             }
@@ -281,8 +219,8 @@
         if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
             final double p = calcScanTimePowerMah(scanningTimeMs);
             if (DEBUG && p != 0) {
-                Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(
-                        p));
+                Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
+                        + " power=" + BatteryStats.formatCharge(p));
             }
             powerMah += p;
 
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 7310314..cb893de 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -20,10 +20,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import java.util.List;
 
 /**
  * Estimates power consumed by telephony.
@@ -54,18 +50,4 @@
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs);
         }
     }
-
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final long phoneOnTimeMs = batteryStats.getPhoneOnTime(rawRealtimeUs, statsType) / 1000;
-        final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
-        if (phoneOnPower != 0) {
-            BatterySipper bs = new BatterySipper(BatterySipper.DrainType.PHONE, null, 0);
-            bs.usagePowerMah = phoneOnPower;
-            bs.usageTimeMs = phoneOnTimeMs;
-            bs.sumPower();
-            sippers.add(bs);
-        }
-    }
 }
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 28c0f5a..ec785b8 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -21,12 +21,9 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.util.SparseArray;
 
 import java.io.PrintWriter;
-import java.util.List;
-import java.util.Locale;
 
 /**
  * Calculates power use of a device subsystem for an app.
@@ -43,34 +40,6 @@
     public abstract boolean isPowerComponentSupported(
             @BatteryConsumer.PowerComponent int powerComponent);
 
-
-    /**
-     * Attributes the total amount of power used by this subsystem to various consumers such
-     * as apps.
-     *
-     * @param sippers       A list of battery sippers that contains battery attribution data.
-     *                      The calculator may modify the list.
-     * @param batteryStats  The recorded battery stats.
-     * @param rawRealtimeUs The raw system realtime in microseconds.
-     * @param rawUptimeUs   The raw system uptime in microseconds.
-     * @param statsType     The type of stats. As of {@link android.os.Build.VERSION_CODES#Q}, this
-     *                      can only be {@link BatteryStats#STATS_SINCE_CHARGED}, since
-     *                      {@link BatteryStats#STATS_CURRENT} and
-     *                      {@link BatteryStats#STATS_SINCE_UNPLUGGED} are deprecated.
-     * @param asUsers       An array of users for which the attribution is requested.  It may
-     *                      contain {@link UserHandle#USER_ALL} to indicate that the attribution
-     *                      should be performed for all users.
-     */
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                calculateApp(app, app.uidObj, rawRealtimeUs, rawUptimeUs, statsType);
-            }
-        }
-    }
-
     /**
      * Attributes the total amount of power used by this subsystem to various consumers such
      * as apps.
@@ -95,21 +64,6 @@
 
     /**
      * Calculate the amount of power an app used for this subsystem.
-     * @param app The BatterySipper that represents the power use of an app.
-     * @param u The recorded stats for the app.
-     * @param rawRealtimeUs The raw system realtime in microseconds.
-     * @param rawUptimeUs The raw system uptime in microseconds.
-     * @param statsType The type of stats. As of {@link android.os.Build.VERSION_CODES#Q}, this can
-     *                  only be {@link BatteryStats#STATS_SINCE_CHARGED}, since
-     *                  {@link BatteryStats#STATS_CURRENT} and
-     *                  {@link BatteryStats#STATS_SINCE_UNPLUGGED} are deprecated.
-     */
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-                                      long rawUptimeUs, int statsType) {
-    }
-
-    /**
-     * Calculate the amount of power an app used for this subsystem.
      * @param app The UidBatteryConsumer.Builder that represents the power use of an app.
      * @param u The recorded stats for the app.
      * @param rawRealtimeUs The raw system realtime in microseconds.
@@ -145,38 +99,7 @@
      * Prints formatted amount of power in milli-amp-hours.
      */
     public static void printPowerMah(PrintWriter pw, double powerMah) {
-        pw.print(formatCharge(powerMah));
-    }
-
-    /**
-     * Converts charge in mAh to string.
-     */
-    public static String formatCharge(double power) {
-        if (power == 0) return "0";
-
-        final String format;
-        if (power < .00001) {
-            format = "%.8f";
-        } else if (power < .0001) {
-            format = "%.7f";
-        } else if (power < .001) {
-            format = "%.6f";
-        } else if (power < .01) {
-            format = "%.5f";
-        } else if (power < .1) {
-            format = "%.4f";
-        } else if (power < 1) {
-            format = "%.3f";
-        } else if (power < 10) {
-            format = "%.2f";
-        } else if (power < 100) {
-            format = "%.1f";
-        } else {
-            format = "%.0f";
-        }
-
-        // Use English locale because this is never used in UI (only in checkin and dump).
-        return String.format(Locale.ENGLISH, format, power);
+        pw.print(BatteryStats.formatCharge(powerMah));
     }
 
     static double uCtoMah(long chargeUC) {
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 110b6b6..67d3d6e 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -24,7 +24,6 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.text.format.DateUtils;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -32,8 +31,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.List;
-
 /**
  * Estimates power consumed by the screen(s)
  */
@@ -125,48 +122,6 @@
     }
 
     /**
-     * Screen power is the additional power the screen takes while the device is running.
-     */
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
-        final long consumptionUC = batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC);
-        calculateTotalDurationAndPower(totalPowerAndDuration, powerModel, batteryStats,
-                rawRealtimeUs, statsType, consumptionUC);
-        if (totalPowerAndDuration.powerMah == 0) {
-            return;
-        }
-
-        // First deal with the SCREEN BatterySipper (since we need this for smearing over apps).
-        final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
-        bs.usagePowerMah = totalPowerAndDuration.powerMah;
-        bs.usageTimeMs = totalPowerAndDuration.durationMs;
-        bs.sumPower();
-        sippers.add(bs);
-
-        // Now deal with each app's BatterySipper. The results are stored in the screenPowerMah
-        // field, which is considered smeared, but the method depends on the data source.
-        switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
-                final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
-                for (int i = sippers.size() - 1; i >= 0; i--) {
-                    final BatterySipper app = sippers.get(i);
-                    if (app.drainType == BatterySipper.DrainType.APP) {
-                        calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.uidObj,
-                                rawRealtimeUs);
-                        app.screenPowerMah = appPowerAndDuration.powerMah;
-                    }
-                }
-                break;
-            case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
-            default:
-                smearScreenBatterySipper(sippers, bs, rawRealtimeUs);
-        }
-    }
-
-    /**
      * Stores duration and power information in totalPowerAndDuration.
      */
     private void calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration,
@@ -219,7 +174,7 @@
                         brightnessTime) * (bin + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
                 if (DEBUG && binPowerMah != 0) {
                     Slog.d(TAG, "Screen bin #" + bin + ": time=" + brightnessTime
-                            + " power=" + formatCharge(binPowerMah));
+                            + " power=" + BatteryStats.formatCharge(binPowerMah));
                 }
                 power += binPowerMah;
             }
@@ -228,37 +183,8 @@
     }
 
     /**
-     * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
-     * time, and store this in the {@link BatterySipper#screenPowerMah} field.
-     */
-    @VisibleForTesting
-    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper,
-            long rawRealtimeUs) {
-        long totalActivityTimeMs = 0;
-        final SparseLongArray activityTimeArray = new SparseLongArray();
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatteryStats.Uid uid = sippers.get(i).uidObj;
-            if (uid != null) {
-                final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
-                activityTimeArray.put(uid.getUid(), timeMs);
-                totalActivityTimeMs += timeMs;
-            }
-        }
-
-        if (screenSipper != null && totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
-            final double totalScreenPowerMah = screenSipper.totalPowerMah;
-            for (int i = sippers.size() - 1; i >= 0; i--) {
-                final BatterySipper sipper = sippers.get(i);
-                sipper.screenPowerMah = totalScreenPowerMah
-                        * activityTimeArray.get(sipper.getUid(), 0)
-                        / totalActivityTimeMs;
-            }
-        }
-    }
-
-    /**
-     * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
-     * time, and store this in the {@link BatterySipper#screenPowerMah} field.
+     * Smear the screen on power usage among {@code UidBatteryConsumers}, based on ratio of
+     * foreground activity time.
      */
     private void smearScreenBatteryDrain(
             SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders,
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
index 495a6d9..4a9c91d 100644
--- a/core/java/com/android/internal/os/SensorPowerCalculator.java
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -72,12 +72,6 @@
         return powerMah;
     }
 
-    @Override
-    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
-            long rawUptimeUs, int statsType) {
-        app.sensorPowerMah = calculatePowerMah(u, rawRealtimeUs, statsType);
-    }
-
     private long calculateDuration(BatteryStats.Uid u, long rawRealtimeUs, int statsType) {
         long durationMs = 0;
         final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index d7872ba..3a3df87 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -22,12 +22,9 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.util.List;
-
 /**
  * Estimates the amount of power consumed by the System Server handling requests from
  * a given app.
@@ -121,55 +118,6 @@
                         systemServicePowerMah);
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType,
-            SparseArray<UserHandle> asUsers) {
-        final BatteryStats.Uid systemUid = batteryStats.getUidStats().get(Process.SYSTEM_UID);
-        if (systemUid == null) {
-            return;
-        }
-
-        final long consumptionUC = systemUid.getCpuMeasuredBatteryConsumptionUC();
-        double systemServicePowerMah;
-        if (getPowerModel(consumptionUC) == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            systemServicePowerMah = calculatePowerUsingMeasuredConsumption(batteryStats,
-                    systemUid, consumptionUC);
-        } else {
-            systemServicePowerMah = calculatePowerUsingPowerProfile(batteryStats);
-        }
-
-        BatterySipper systemServerSipper = null;
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                if (app.getUid() == Process.SYSTEM_UID) {
-                    systemServerSipper = app;
-                    break;
-                }
-            }
-        }
-
-        if (systemServerSipper != null) {
-            systemServicePowerMah = Math.min(systemServicePowerMah, systemServerSipper.sumPower());
-
-            // The system server power needs to be adjusted because part of it got
-            // distributed to applications
-            systemServerSipper.powerReattributedToOtherSippersMah = -systemServicePowerMah;
-        }
-
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                if (app != systemServerSipper) {
-                    final BatteryStats.Uid uid = app.uidObj;
-                    app.systemServiceCpuPowerMah =
-                            systemServicePowerMah * uid.getProportionalSystemServiceUsage();
-                }
-            }
-        }
-    }
-
     private double calculatePowerUsingMeasuredConsumption(BatteryStats batteryStats,
             BatteryStats.Uid systemUid, long consumptionUC) {
         // Use the PowerProfile based model to estimate the ratio between the power consumed
diff --git a/core/java/com/android/internal/os/UserPowerCalculator.java b/core/java/com/android/internal/os/UserPowerCalculator.java
index b590bf7..22cff6e 100644
--- a/core/java/com/android/internal/os/UserPowerCalculator.java
+++ b/core/java/com/android/internal/os/UserPowerCalculator.java
@@ -27,8 +27,6 @@
 
 import com.android.internal.util.ArrayUtils;
 
-import java.util.List;
-
 /**
  * Computes power consumed by Users
  */
@@ -65,40 +63,4 @@
             }
         }
     }
-
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
-        if (forAllUsers) {
-            return;
-        }
-
-        SparseArray<BatterySipper> userSippers = new SparseArray<>();
-
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            BatterySipper sipper = sippers.get(i);
-            final int uid = sipper.getUid();
-            final int userId = UserHandle.getUserId(uid);
-            if (asUsers.get(userId) == null
-                    && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
-                // We are told to just report this user's apps as one accumulated entry.
-                BatterySipper userSipper = userSippers.get(userId);
-                if (userSipper == null) {
-                    userSipper = new BatterySipper(BatterySipper.DrainType.USER, null, 0);
-                    userSipper.userId = userId;
-                    userSippers.put(userId, userSipper);
-                }
-                userSipper.add(sipper);
-                sipper.isAggregated = true;
-            }
-        }
-
-        for (int i = 0; i < userSippers.size(); i++) {
-            BatterySipper sipper = userSippers.valueAt(i);
-            if (sipper.sumPower() > 0) {
-                sippers.add(sipper);
-            }
-        }
-    }
 }
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index bceb209..0251e1c 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -21,13 +21,10 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.util.List;
-
 public class WakelockPowerCalculator extends PowerCalculator {
     private static final String TAG = "WakelockPowerCalculator";
     private static final boolean DEBUG = PowerCalculator.DEBUG;
@@ -105,42 +102,6 @@
                         appPowerMah);
     }
 
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final PowerAndDuration result = new PowerAndDuration();
-        BatterySipper osSipper = null;
-        double osPowerMah = 0;
-        long osDurationMs = 0;
-        long totalAppDurationMs = 0;
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                calculateApp(result, app.uidObj, rawRealtimeUs, statsType);
-                app.wakeLockTimeMs = result.durationMs;
-                app.wakeLockPowerMah = result.powerMah;
-                totalAppDurationMs += result.durationMs;
-
-                if (app.getUid() == Process.ROOT_UID) {
-                    osSipper = app;
-                    osPowerMah = result.powerMah;
-                    osDurationMs = result.durationMs;
-                }
-            }
-        }
-
-        // The device has probably been awake for longer than the screen on
-        // time and application wake lock time would account for.  Assign
-        // this remainder to the OS, if possible.
-        if (osSipper != null) {
-            calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs, statsType,
-                    osPowerMah, osDurationMs, totalAppDurationMs);
-            osSipper.wakeLockTimeMs = result.durationMs;
-            osSipper.wakeLockPowerMah = result.powerMah;
-            osSipper.sumPower();
-        }
-    }
-
     private void calculateApp(PowerAndDuration result, BatteryStats.Uid u, long rawRealtimeUs,
             int statsType) {
         long wakeLockTimeUs = 0;
@@ -163,7 +124,7 @@
         result.powerMah = mPowerEstimator.calculatePower(result.durationMs);
         if (DEBUG && result.powerMah != 0) {
             Log.d(TAG, "UID " + u.getUid() + ": wake " + result.durationMs
-                    + " power=" + formatCharge(result.powerMah));
+                    + " power=" + BatteryStats.formatCharge(result.powerMah));
         }
     }
 
@@ -175,7 +136,8 @@
         if (wakeTimeMillis > 0) {
             final double power = mPowerEstimator.calculatePower(wakeTimeMillis);
             if (DEBUG) {
-                Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power " + formatCharge(power));
+                Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis
+                        + " power " + BatteryStats.formatCharge(power));
             }
             result.durationMs = osDurationMs + wakeTimeMillis;
             result.powerMah = osPowerMah + power;
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index ad291a4..8c3fb86 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -19,14 +19,11 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.Process;
 import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.util.Arrays;
-import java.util.List;
 
 /**
  * WiFi power calculator for when BatteryStats supports energy reporting
@@ -156,62 +153,6 @@
                         totalAppPowerMah, powerModel);
     }
 
-    /**
-     * We do per-app blaming of WiFi activity. If energy info is reported from the controller,
-     * then only the WiFi process gets blamed here since we normalize power calculations and
-     * assign all the power drain to apps. If energy info is not reported, we attribute the
-     * difference between total running time of WiFi for all apps and the actual running time
-     * of WiFi to the WiFi subsystem.
-     */
-    @Override
-    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
-            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-
-        final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0);
-
-        long totalAppDurationMs = 0;
-        double totalAppPowerMah = 0;
-        final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic();
-        for (int i = sippers.size() - 1; i >= 0; i--) {
-            final BatterySipper app = sippers.get(i);
-            if (app.drainType == BatterySipper.DrainType.APP) {
-                final long consumptionUC =
-                        app.uidObj.getWifiMeasuredBatteryConsumptionUC();
-                final int powerModel = getPowerModel(consumptionUC);
-                calculateApp(powerDurationAndTraffic, app.uidObj, powerModel, rawRealtimeUs,
-                        statsType, batteryStats.hasWifiActivityReporting(), consumptionUC);
-
-                totalAppDurationMs += powerDurationAndTraffic.durationMs;
-                totalAppPowerMah += powerDurationAndTraffic.powerMah;
-
-                app.wifiPowerMah = powerDurationAndTraffic.powerMah;
-                app.wifiRunningTimeMs = powerDurationAndTraffic.durationMs;
-                app.wifiRxBytes = powerDurationAndTraffic.wifiRxBytes;
-                app.wifiRxPackets = powerDurationAndTraffic.wifiRxPackets;
-                app.wifiTxBytes = powerDurationAndTraffic.wifiTxBytes;
-                app.wifiTxPackets = powerDurationAndTraffic.wifiTxPackets;
-                if (app.getUid() == Process.WIFI_UID) {
-                    if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs);
-                    app.isAggregated = true;
-                    bs.add(app);
-                }
-            }
-        }
-
-        final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC);
-        calculateRemaining(powerDurationAndTraffic, powerModel, batteryStats, rawRealtimeUs,
-                statsType, batteryStats.hasWifiActivityReporting(), totalAppDurationMs,
-                totalAppPowerMah, consumptionUC);
-
-        bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs;
-        bs.wifiPowerMah += powerDurationAndTraffic.powerMah;
-
-        if (bs.sumPower() > 0) {
-            sippers.add(bs);
-        }
-    }
-
     private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic,
             BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
             long rawRealtimeUs, int statsType, boolean hasWifiActivityReporting,
@@ -251,7 +192,7 @@
 
                 if (DEBUG && powerDurationAndTraffic.powerMah != 0) {
                     Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime
-                            + "ms tx=" + txTime + "ms power=" + formatCharge(
+                            + "ms tx=" + txTime + "ms power=" + BatteryStats.formatCharge(
                             powerDurationAndTraffic.powerMah));
                 }
 
@@ -306,7 +247,7 @@
             }
 
             if (DEBUG && powerDurationAndTraffic.powerMah != 0) {
-                Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge(
+                Log.d(TAG, "UID " + u.getUid() + ": power=" + BatteryStats.formatCharge(
                         powerDurationAndTraffic.powerMah));
             }
         }
@@ -353,7 +294,8 @@
         powerDurationAndTraffic.powerMah = Math.max(0, totalPowerMah - totalAppPowerMah);
 
         if (DEBUG) {
-            Log.d(TAG, "left over WiFi power: " + formatCharge(powerDurationAndTraffic.powerMah));
+            Log.d(TAG, "left over WiFi power: " + BatteryStats.formatCharge(
+                    powerDurationAndTraffic.powerMah));
         }
     }
 
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 611f644..f258f84 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -713,7 +713,7 @@
                 "--setuid=1000",
                 "--setgid=1000",
                 "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
-                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011,3012",
+                        + "1024,1032,1065,3001,3002,3003,3005,3006,3007,3009,3010,3011,3012",
                 "--capabilities=" + capabilities + "," + capabilities,
                 "--nice-name=system_server",
                 "--runtime-args",
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index ecc5777..354dd9a 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -4,8 +4,4 @@
 per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
 per-file Protocol* = etancohen@google.com, lorenzo@google.com
 per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
-<<<<<<< TARGET BRANCH (83dd0c Merge "(ImsService API changes for Better IMS Threading) Ims)
-per-file DataClass* = eugenesusla@google.com
-=======
 per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
->>>>>>> SOURCE BRANCH (2727d1 Merge "Adds OWNERS to com.androd.internal.util.Dump*" am: 0b)
diff --git a/core/java/com/android/internal/util/dump/DumpableContainerImpl.java b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java
index d391684..2e56ebf 100644
--- a/core/java/com/android/internal/util/dump/DumpableContainerImpl.java
+++ b/core/java/com/android/internal/util/dump/DumpableContainerImpl.java
@@ -24,11 +24,12 @@
 import java.io.PrintWriter;
 import java.util.Objects;
 
-// TODO(b/149254050): add unit tests
 /**
  * Helper class for {@link DumpableContainer} implementations - they can "implement it by
  * association", i.e., by delegating the interface methods to a {@code DumpableContainerImpl}.
  *
+ * <p>This class is not thread safe.
+ *
  * @hide
  */
 public final class DumpableContainerImpl implements DumpableContainer {
@@ -58,6 +59,38 @@
         return true;
     }
 
+    @Override
+    public boolean removeDumpable(Dumpable dumpable) {
+        Objects.requireNonNull(dumpable, "dumpable");
+        String name = dumpable.getDumpableName();
+        if (name == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Tried to remove nameless dumpable: " + dumpable);
+            }
+            return false;
+        }
+
+        Dumpable candidate = mDumpables.get(name);
+        if (candidate == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Dumpable with name " + name + " not found");
+            }
+            return false;
+        }
+
+        // Make sure it's the right one
+        if (candidate != dumpable) {
+            Log.w(TAG, "removeDumpable(): passed dumpable (" + dumpable + ") named " + name
+                    + ", but internal dumpable with that name is " + candidate);
+            return false;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Removing dumpable named " + name);
+        }
+        mDumpables.remove(name);
+        return true;
+    }
+
     /**
      * Dumps the number of dumpable, without a newline.
      */
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index e72afdd..8430c08 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -24,7 +24,9 @@
  */
 oneway interface IInputMethodClient {
     void onBindMethod(in InputBindResult res);
+    void onBindAccessibilityService(in InputBindResult res, int id);
     void onUnbindMethod(int sequence, int unbindReason);
+    void onUnbindAccessibilityService(int sequence, int id);
     void setActive(boolean active, boolean fullscreen, boolean reportToImeController);
     void scheduleStartInputIfNecessary(boolean fullscreen);
     void reportFullscreenMode(boolean fullscreen);
diff --git a/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl b/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl
new file mode 100644
index 0000000..8fbdefe
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputSessionWithIdCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package com.android.internal.view;
+
+ import com.android.internal.view.IInputMethodSession;
+
+/**
+ * Helper interface for IInputMethod to allow the input method to notify the client when a new
+ * session has been created.
+ */
+oneway interface IInputSessionWithIdCallback {
+    void sessionCreated(IInputMethodSession session, int id);
+}
\ No newline at end of file
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 55f1369..4f13a9c 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -30,6 +30,11 @@
 
 namespace android {
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gTransactionClassInfo;
+
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) {
     ScopedUtfChars name(env, jName);
     sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str());
@@ -86,6 +91,14 @@
     return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl));
 }
 
+static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr,
+                                               jlong frameNum) {
+    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+    SurfaceComposerClient::Transaction* transaction = queue->gatherPendingTransactions(frameNum);
+    return env->NewObject(gTransactionClassInfo.clazz, gTransactionClassInfo.ctor,
+                          reinterpret_cast<jlong>(transaction));
+}
+
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         // clang-format off
@@ -98,6 +111,7 @@
         {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
         {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions},
         {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl},
+        {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions}
         // clang-format on
 };
 
@@ -105,6 +119,11 @@
     int res = jniRegisterNativeMethods(env, "android/graphics/BLASTBufferQueue",
             gMethods, NELEM(gMethods));
     LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    jclass transactionClazz = FindClassOrDie(env, "android/view/SurfaceControl$Transaction");
+    gTransactionClassInfo.clazz = MakeGlobalRefOrDie(env, transactionClazz);
+    gTransactionClassInfo.ctor =
+            GetMethodIDOrDie(env, gTransactionClassInfo.clazz, "<init>", "(J)V");
     return 0;
 }
 
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index 3af55fe..d2d7213 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -81,27 +81,37 @@
 
     void binderDied(const wp<hardware::IBinder>& who)
     {
-        if (mObject != NULL) {
-            JNIEnv* env = javavm_to_jnienv(mVM);
+        JNIEnv* env = javavm_to_jnienv(mVM);
 
-            env->CallStaticVoidMethod(gProxyOffsets.proxy_class, gProxyOffsets.sendDeathNotice, mObject, mCookie);
+        // Serialize with our containing HwBinderDeathRecipientList so that we can't
+        // delete the global ref on object while the list is being iterated.
+        sp<HwBinderDeathRecipientList> list = mList.promote();
+        if (list == nullptr) return;
+
+        jobject object;
+        {
+            AutoMutex _l(list->lock());
+
+            // this function now owns the global ref - to the rest of the code, it looks like
+            // this binder already died, but we won't actually delete the reference until
+            // the Java code has processed the death
+            object = mObject;
+
+            // Demote from strong ref to weak for after binderDied() has been delivered,
+            // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed.
+            mObjectWeak = env->NewWeakGlobalRef(mObject);
+            mObject = nullptr;
+        }
+
+        if (object != nullptr) {
+            env->CallStaticVoidMethod(gProxyOffsets.proxy_class, gProxyOffsets.sendDeathNotice,
+                                      object, mCookie);
             if (env->ExceptionCheck()) {
                 ALOGE("Uncaught exception returned from death notification.");
                 env->ExceptionClear();
             }
 
-            // Serialize with our containing HwBinderDeathRecipientList so that we can't
-            // delete the global ref on mObject while the list is being iterated.
-            sp<HwBinderDeathRecipientList> list = mList.promote();
-            if (list != NULL) {
-                AutoMutex _l(list->lock());
-
-                // Demote from strong ref to weak after binderDied() has been delivered,
-                // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed.
-                mObjectWeak = env->NewWeakGlobalRef(mObject);
-                env->DeleteGlobalRef(mObject);
-                mObject = NULL;
-            }
+            env->DeleteGlobalRef(object);
         }
     }
 
@@ -115,7 +125,7 @@
         }
     }
 
-    bool matches(jobject obj) {
+    bool matchesLocked(jobject obj) {
         bool result;
         JNIEnv* env = javavm_to_jnienv(mVM);
 
@@ -129,7 +139,7 @@
         return result;
     }
 
-    void warnIfStillLive() {
+    void warnIfStillLiveLocked() {
         if (mObject != NULL) {
             // Okay, something is wrong -- we have a hard reference to a live death
             // recipient on the VM side, but the list is being torn down.
@@ -176,7 +186,7 @@
     AutoMutex _l(mLock);
 
     for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) {
-        deathRecipient->warnIfStillLive();
+        deathRecipient->warnIfStillLiveLocked();
     }
 }
 
@@ -201,7 +211,7 @@
     AutoMutex _l(mLock);
 
     for(auto iter = mList.rbegin(); iter != mList.rend(); iter++) {
-        if ((*iter)->matches(recipient)) {
+        if ((*iter)->matchesLocked(recipient)) {
             return (*iter);
         }
     }
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index a6fbf094..7c67cbc 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -235,7 +235,9 @@
 void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
 {
     ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
+    DIR *d;
     char proc_path[255];
+    struct dirent *de;
 
     if (!verifyGroup(env, grp)) {
         return;
@@ -275,8 +277,84 @@
         }
     }
 
-    if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
-        signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
+    sprintf(proc_path, "/proc/%d/task", pid);
+    if (!(d = opendir(proc_path))) {
+        // If the process exited on us, don't generate an exception
+        if (errno != ENOENT)
+            signalExceptionForGroupError(env, errno, pid);
+        return;
+    }
+
+    while ((de = readdir(d))) {
+        int t_pid;
+        int t_pri;
+        std::string taskprofile;
+
+        if (de->d_name[0] == '.')
+            continue;
+        t_pid = atoi(de->d_name);
+
+        if (!t_pid) {
+            ALOGE("Error getting pid for '%s'\n", de->d_name);
+            continue;
+        }
+
+        t_pri = getpriority(PRIO_PROCESS, t_pid);
+
+        if (t_pri <= ANDROID_PRIORITY_AUDIO) {
+            int scheduler = sched_getscheduler(t_pid) & ~SCHED_RESET_ON_FORK;
+            if ((scheduler == SCHED_FIFO) || (scheduler == SCHED_RR)) {
+                // This task wants to stay in its current audio group so it can keep its budget
+                // don't update its cpuset or cgroup
+                continue;
+            }
+        }
+
+        errno = 0;
+        // grp == SP_BACKGROUND. Set background cpuset policy profile for all threads.
+        if (grp == SP_BACKGROUND) {
+            if (!SetTaskProfiles(t_pid, {"CPUSET_SP_BACKGROUND"}, true)) {
+                signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);
+                break;
+            }
+            continue;
+        }
+
+        // grp != SP_BACKGROUND. Only change the cpuset cgroup for low priority thread, so it could
+        // preserve it sched policy profile setting.
+        if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
+            switch (grp) {
+                case SP_SYSTEM:
+                    taskprofile = "ServiceCapacityLow";
+                    break;
+                case SP_RESTRICTED:
+                    taskprofile = "ServiceCapacityRestricted";
+                    break;
+                case SP_FOREGROUND:
+                case SP_AUDIO_APP:
+                case SP_AUDIO_SYS:
+                    taskprofile = "ProcessCapacityHigh";
+                    break;
+                case SP_TOP_APP:
+                    taskprofile = "ProcessCapacityMax";
+                    break;
+                default:
+                    taskprofile = "ProcessCapacityNormal";
+                    break;
+            }
+            if (!SetTaskProfiles(t_pid, {taskprofile}, true)) {
+                signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);
+                break;
+            }
+        // Change the cpuset policy profile for non-low priority thread according to the grp
+        } else {
+            if (!SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true)) {
+                signalExceptionForGroupError(env, errno ? errno : EPERM, t_pid);
+                break;
+            }
+        }
+    }
+    closedir(d);
 }
 
 void android_os_Process_setProcessFrozen(
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2736ba63..2d0466b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -199,6 +199,8 @@
     <protected-broadcast
         android:name="android.bluetooth.headsetclient.profile.action.LAST_VTAG" />
     <protected-broadcast
+        android:name="android.bluetooth.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" />
@@ -1895,7 +1897,7 @@
         android:label="@string/permlab_changeWifiState"
         android:protectionLevel="normal" />
 
-    <!-- @SystemApi @hide Allows applications to enable/disable wifi auto join. This permission
+    <!-- Allows applications to enable/disable wifi auto join. This permission
          is used to let OEMs grant their trusted app access to a subset of privileged wifi APIs
          to improve wifi performance.
          <p>Not for use by third-party applications. -->
@@ -1933,7 +1935,7 @@
     <permission android:name="android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi @hide Allows an application to modify any wifi configuration, even if created
+    <!-- Allows an application to modify any wifi configuration, even if created
      by another application. Once reconfigured the original creator cannot make any further
      modifications.
      <p>Not for use by third-party applications. -->
@@ -6616,14 +6618,6 @@
                   android:exported="false">
         </activity>
 
-        <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity"
-                  android:theme="@style/Theme.Dialog.Confirmation"
-                  android:excludeFromRecents="true"
-                  android:process=":ui"
-                  android:label="@string/log_access_confirmation_title"
-                  android:exported="false">
-        </activity>
-
         <activity android:name="com.android.server.notification.NASLearnMoreActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true"
diff --git a/core/res/res/anim/popup_enter_material.xml b/core/res/res/anim/popup_enter_material.xml
index 79de26b..ef5b7c0 100644
--- a/core/res/res/anim/popup_enter_material.xml
+++ b/core/res/res/anim/popup_enter_material.xml
@@ -16,7 +16,14 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false" >
-    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-           android:interpolator="@interpolator/decelerate_cubic"
-           android:duration="@android:integer/config_activityShortDur" />
+    <alpha
+        android:fromAlpha="0.0"
+        android:toAlpha="1.0"
+        android:interpolator="@interpolator/standard"
+        android:duration="@android:integer/config_activityDefaultDur" />
+    <translate
+        android:fromYDelta="20dp"
+        android:toYDelta="0"
+        android:interpolator="@interpolator/standard"
+        android:duration="@android:integer/config_activityDefaultDur" />
 </set>
diff --git a/core/res/res/anim/popup_exit_material.xml b/core/res/res/anim/popup_exit_material.xml
index 7d7d5c5..1efa702 100644
--- a/core/res/res/anim/popup_exit_material.xml
+++ b/core/res/res/anim/popup_exit_material.xml
@@ -16,7 +16,14 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="false" >
-    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-           android:interpolator="@interpolator/decelerate_cubic"
-           android:duration="@android:integer/config_activityShortDur"/>
+    <alpha
+        android:fromAlpha="1.0"
+        android:toAlpha="0.0"
+        android:interpolator="@interpolator/standard_accelerate"
+        android:duration="@android:integer/config_activityShortDur" />
+    <translate
+        android:fromYDelta="0"
+        android:toYDelta="-10dp"
+        android:interpolator="@interpolator/standard_accelerate"
+        android:duration="@android:integer/config_activityShortDur" />
 </set>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8df5871..090b1c52 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3910,6 +3910,8 @@
             <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_MULTI_FINGER_GESTURES}. -->
             <flag name="flagRequestMultiFingerGestures" value="0x00001000" />
             <flag name="flagSendMotionEvents" value="0x0004000" />
+            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_INPUT_METHOD_EDITOR}. -->
+            <flag name="flagInputMethodEditor" value="0x0008000" />
         </attr>
         <!-- Component name of an activity that allows the user to modify
              the settings for this service. This setting cannot be changed at runtime. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6a86b1c..775527d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4082,6 +4082,16 @@
    -->
     <string name="config_defaultAmbientContextDetectionService" translatable="false"></string>
 
+    <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent. -->
+    <string translatable="false" name="config_defaultAmbientContextConsentComponent"></string>
+
+    <!-- Intent extra key for the caller's package name while requesting ambient context consent.
+     -->
+    <string translatable="false" name="config_ambientContextPackageNameExtraKey"></string>
+
+    <!-- Intent extra key for the event code int array while requesting ambient context consent. -->
+    <string translatable="false" name="config_ambientContextEventArrayExtraKey"></string>
+
     <!-- The component name for the system-wide captions service.
          This service must be trusted, as it controls part of the UI of the volume bar.
          Example: "com.android.captions/.SystemCaptionsService"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3e821f2..6297ed9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4242,7 +4242,7 @@
          match and total number of matches found in the current page. [CHAR LIMIT=NONE] -->
     <string name="matches_found">{ count, plural,
         =1 {# match}
-        other {# of {total}}}
+        other {# of {total}}
     }
     </string>
 
@@ -5719,20 +5719,6 @@
     <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] -->
     <string name="harmful_app_warning_title">Harmful app detected</string>
 
-    <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] -->
-    <string name="log_access_confirmation_title">System log access request</string>
-    <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] -->
-    <string name="log_access_confirmation_allow">Only this time</string>
-    <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] -->
-    <string name="log_access_confirmation_deny">Don\u2019t allow</string>
-
-    <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]-->
-    <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging.
-        These logs might contain information that apps and services on your device have written.</string>
-
-    <!-- Privacy notice do not show [CHAR LIMIT=20] -->
-    <string name="log_access_do_not_show_again">Don\u2019t show again</string>
-
     <!-- Text describing a permission request for one app to show another app's
          slices [CHAR LIMIT=NONE] -->
     <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a79eec2..d731180 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3868,11 +3868,6 @@
   <java-symbol type="string" name="harmful_app_warning_title" />
   <java-symbol type="layout" name="harmful_app_warning_dialog" />
 
-  <java-symbol type="string" name="log_access_confirmation_allow" />
-  <java-symbol type="string" name="log_access_confirmation_deny" />
-  <java-symbol type="string" name="log_access_confirmation_title" />
-  <java-symbol type="string" name="log_access_confirmation_body" />
-
   <java-symbol type="string" name="config_defaultAssistantAccessComponent" />
 
   <java-symbol type="string" name="slices_permission_request" />
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
deleted file mode 100644
index 260b65a..0000000
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- *
- */
-
-package com.android.internal.os;
-
-import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.BatteryStats;
-import android.os.Process;
-import android.text.format.DateUtils;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import junit.framework.TestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class BatteryStatsHelperTest extends TestCase {
-    private static final long TIME_FOREGROUND_ACTIVITY_ZERO = 0;
-    private static final long TIME_FOREGROUND_ACTIVITY = 100 * DateUtils.MINUTE_IN_MILLIS * 1000;
-    private static final long TIME_STATE_FOREGROUND_MS = 10 * DateUtils.MINUTE_IN_MILLIS;
-    private static final long TIME_STATE_FOREGROUND_US = TIME_STATE_FOREGROUND_MS * 1000;
-
-    private static final int UID = 123456;
-    private static final double BATTERY_SCREEN_USAGE = 300;
-    private static final double BATTERY_SYSTEM_USAGE = 600;
-    private static final double BATTERY_WIFI_USAGE = 200;
-    private static final double BATTERY_IDLE_USAGE = 600;
-    private static final double BATTERY_BLUETOOTH_USAGE = 300;
-    private static final double BATTERY_OVERACCOUNTED_USAGE = 500;
-    private static final double BATTERY_UNACCOUNTED_USAGE = 700;
-    private static final double BATTERY_APP_USAGE = 100;
-    private static final double TOTAL_BATTERY_USAGE = 1000;
-    private static final double PRECISION = 0.001;
-
-    @Mock
-    private BatteryStats.Uid mUid;
-    @Mock
-    private BatterySipper mWifiBatterySipper;
-    @Mock
-    private BatterySipper mBluetoothBatterySipper;
-    @Mock
-    private BatterySipper mIdleBatterySipper;
-    @Mock
-    private BatterySipper mNormalBatterySipper;
-    @Mock
-    private BatterySipper mScreenBatterySipper;
-    @Mock
-    private BatterySipper mOvercountedBatterySipper;
-    @Mock
-    private BatterySipper mUnaccountedBatterySipper;
-    @Mock
-    private BatterySipper mSystemBatterySipper;
-    @Mock
-    private BatterySipper mCellBatterySipper;
-    @Mock
-    private PackageManager mPackageManager;
-
-    private BatteryStatsHelper mBatteryStatsHelper;
-    private Context mContext;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
-        mNormalBatterySipper.totalPowerMah = TOTAL_BATTERY_USAGE;
-        when(mNormalBatterySipper.getUid()).thenReturn(UID);
-        mNormalBatterySipper.uidObj = mUid;
-
-
-        mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN;
-        mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE;
-
-        mSystemBatterySipper.drainType = BatterySipper.DrainType.APP;
-        mSystemBatterySipper.totalPowerMah = BATTERY_SYSTEM_USAGE;
-        mSystemBatterySipper.uidObj = mUid;
-        when(mSystemBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID);
-
-        mOvercountedBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED;
-        mOvercountedBatterySipper.totalPowerMah = BATTERY_OVERACCOUNTED_USAGE;
-
-        mUnaccountedBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED;
-        mUnaccountedBatterySipper.totalPowerMah = BATTERY_UNACCOUNTED_USAGE;
-
-        mWifiBatterySipper.drainType = BatterySipper.DrainType.WIFI;
-        mWifiBatterySipper.totalPowerMah = BATTERY_WIFI_USAGE;
-
-        mBluetoothBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
-        mBluetoothBatterySipper.totalPowerMah = BATTERY_BLUETOOTH_USAGE;
-
-        mIdleBatterySipper.drainType = BatterySipper.DrainType.IDLE;
-        mIdleBatterySipper.totalPowerMah = BATTERY_IDLE_USAGE;
-
-        mContext = InstrumentationRegistry.getContext();
-        mBatteryStatsHelper = spy(new BatteryStatsHelper(mContext));
-        mBatteryStatsHelper.setPackageManager(mPackageManager);
-    }
-
-    @Test
-    public void testShouldHideSipper_TypeUnAccounted_ReturnTrue() {
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED;
-        assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testShouldHideSipper_TypeOverAccounted_ReturnTrue() {
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.OVERCOUNTED;
-        assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testShouldHideSipper_TypeIdle_ReturnTrue() {
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE;
-        assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testShouldHideSipper_TypeCell_ReturnTrue() {
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL;
-        assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testShouldHideSipper_TypeScreen_ReturnTrue() {
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.SCREEN;
-        assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testShouldHideSipper_TypeSystem_ReturnTrue() {
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
-        when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID);
-        assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testShouldHideSipper_UidNormal_ReturnFalse() {
-        mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
-        assertThat(mBatteryStatsHelper.shouldHideSipper(mNormalBatterySipper)).isFalse();
-    }
-
-    @Test
-    public void testRemoveHiddenBatterySippers_ContainsHiddenSippers_RemoveAndReturnValue() {
-        final List<BatterySipper> sippers = new ArrayList<>();
-        sippers.add(mNormalBatterySipper);
-        sippers.add(mScreenBatterySipper);
-        sippers.add(mSystemBatterySipper);
-        sippers.add(mOvercountedBatterySipper);
-        sippers.add(mUnaccountedBatterySipper);
-        sippers.add(mWifiBatterySipper);
-        sippers.add(mBluetoothBatterySipper);
-        sippers.add(mIdleBatterySipper);
-        doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper);
-
-        final double totalUsage = mBatteryStatsHelper.removeHiddenBatterySippers(sippers);
-
-        assertThat(mNormalBatterySipper.shouldHide).isFalse();
-        assertThat(mScreenBatterySipper.shouldHide).isTrue();
-        assertThat(mSystemBatterySipper.shouldHide).isTrue();
-        assertThat(mOvercountedBatterySipper.shouldHide).isTrue();
-        assertThat(mUnaccountedBatterySipper.shouldHide).isTrue();
-        assertThat(totalUsage).isWithin(PRECISION).of(BATTERY_SYSTEM_USAGE);
-    }
-
-    @Test
-    public void testSmearScreenBatterySipper() {
-        final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class);
-        final BatterySipper sipperNull = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO,
-                BATTERY_APP_USAGE, 0 /* uid */, true /* isUidNull */, spc);
-        final BatterySipper sipperBg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO,
-                BATTERY_APP_USAGE, 1 /* uid */, false /* isUidNull */, spc);
-        final BatterySipper sipperFg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY,
-                BATTERY_APP_USAGE, 2 /* uid */, false /* isUidNull */, spc);
-
-        final List<BatterySipper> sippers = new ArrayList<>();
-        sippers.add(sipperNull);
-        sippers.add(sipperBg);
-        sippers.add(sipperFg);
-
-        spc.smearScreenBatterySipper(sippers, mScreenBatterySipper, 0);
-
-        assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0);
-        assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0);
-        assertThat(sipperFg.screenPowerMah).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE);
-    }
-
-    @Test
-    public void testIsTypeSystem_systemPackage_returnTrue() {
-        final String[] systemPackages = {"com.android.system"};
-        mBatteryStatsHelper.setSystemPackageArray(systemPackages);
-        doReturn(UID).when(mNormalBatterySipper).getUid();
-        doReturn(systemPackages).when(mPackageManager).getPackagesForUid(UID);
-
-        assertThat(mBatteryStatsHelper.isTypeSystem(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testIsTypeService_servicePackage_returnTrue() {
-        final String[] servicePackages = {"com.android.service"};
-        mBatteryStatsHelper.setServicePackageArray(servicePackages);
-        doReturn(UID).when(mNormalBatterySipper).getUid();
-        doReturn(servicePackages).when(mPackageManager).getPackagesForUid(UID);
-
-        assertThat(mBatteryStatsHelper.isTypeService(mNormalBatterySipper)).isTrue();
-    }
-
-    @Test
-    public void testGetProcessForegroundTimeMs_largerActivityTime_returnMinTime() {
-        final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class);
-        doReturn(TIME_STATE_FOREGROUND_US + 500).when(spc)
-                .getForegroundActivityTotalTimeUs(eq(mUid), anyLong());
-        doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP),
-                anyLong(), anyInt());
-
-        final long time = spc.getProcessForegroundTimeMs(mUid, 1000);
-
-        assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
-    }
-
-    private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
-            int uidCode, boolean isUidNull, ScreenPowerCalculator spc) {
-        final BatterySipper sipper = mock(BatterySipper.class);
-        sipper.drainType = BatterySipper.DrainType.APP;
-        sipper.totalPowerMah = totalPowerMah;
-        doReturn(uidCode).when(sipper).getUid();
-        if (!isUidNull) {
-            final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS);
-            doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid), anyLong());
-            doReturn(uidCode).when(uid).getUid();
-            sipper.uidObj = uid;
-        }
-
-        return sipper;
-    }
-
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index be8045d..d9b98a5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -41,6 +41,7 @@
 import android.bluetooth.UidTraffic;
 import android.os.BatteryStats;
 import android.os.BluetoothBatteryStats;
+import android.os.Parcel;
 import android.os.WakeLockStats;
 import android.os.WorkSource;
 import android.util.SparseArray;
@@ -583,11 +584,42 @@
         mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, true, 9000, 9000);
         mBatteryStatsImpl.noteBluetoothScanResultsFromSourceLocked(ws, 42, 9000, 9000);
 
-        BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
-                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 9000, 8000, 12000, 0);
-        info.setUidTraffic(ImmutableList.of(
-                new UidTraffic(10042, 3000, 4000),
-                new UidTraffic(10043, 5000, 8000)));
+
+
+        final Parcel uidTrafficParcel1 = Parcel.obtain();
+        final Parcel uidTrafficParcel2 = Parcel.obtain();
+
+        uidTrafficParcel1.writeInt(10042);
+        uidTrafficParcel1.writeLong(3000);
+        uidTrafficParcel1.writeLong(4000);
+        uidTrafficParcel1.setDataPosition(0);
+        uidTrafficParcel2.writeInt(10043);
+        uidTrafficParcel2.writeLong(5000);
+        uidTrafficParcel2.writeLong(8000);
+        uidTrafficParcel2.setDataPosition(0);
+
+        List<UidTraffic> uidTrafficList = ImmutableList.of(
+                UidTraffic.CREATOR.createFromParcel(uidTrafficParcel1),
+                UidTraffic.CREATOR.createFromParcel(uidTrafficParcel2));
+
+        final Parcel btActivityEnergyInfoParcel = Parcel.obtain();
+        btActivityEnergyInfoParcel.writeLong(1000);
+        btActivityEnergyInfoParcel.writeInt(
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE);
+        btActivityEnergyInfoParcel.writeLong(9000);
+        btActivityEnergyInfoParcel.writeLong(8000);
+        btActivityEnergyInfoParcel.writeLong(12000);
+        btActivityEnergyInfoParcel.writeLong(0);
+        btActivityEnergyInfoParcel.writeTypedList(uidTrafficList);
+        btActivityEnergyInfoParcel.setDataPosition(0);
+
+        BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR
+                .createFromParcel(btActivityEnergyInfoParcel);
+
+        uidTrafficParcel1.recycle();
+        uidTrafficParcel2.recycle();
+        btActivityEnergyInfoParcel.recycle();
+
         mBatteryStatsImpl.updateBluetoothStateLocked(info, -1, 1000, 1000);
 
         BluetoothBatteryStats stats =
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 92c2d43..ace39fb 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -30,7 +30,6 @@
         BatteryStatsCounterTest.class,
         BatteryStatsDualTimerTest.class,
         BatteryStatsDurationTimerTest.class,
-        BatteryStatsHelperTest.class,
         BatteryStatsHistoryIteratorTest.class,
         BatteryStatsHistoryTest.class,
         BatteryStatsImplTest.class,
@@ -69,7 +68,6 @@
         LongSamplingCounterTest.class,
         LongSamplingCounterArrayTest.class,
         MobileRadioPowerCalculatorTest.class,
-        PowerCalculatorTest.class,
         PowerProfileTest.class,
         ScreenPowerCalculatorTest.class,
         SensorPowerCalculatorTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index ed035e5..448f666 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -24,6 +24,7 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStatsQuery;
+import android.os.Parcel;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
 import android.os.WorkSource;
@@ -37,6 +38,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @SuppressWarnings("GuardedBy")
@@ -90,11 +93,13 @@
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
 
-        BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000,
-                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000);
-        info1.setUidTraffic(ImmutableList.of(
-                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
-                new UidTraffic(APP_UID, 3000, 4000)));
+
+        List<UidTraffic> trafficList1 = ImmutableList.of(
+                createUidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
+                createUidTraffic(APP_UID, 3000, 4000));
+        BluetoothActivityEnergyInfo info1 = createBtEnergyInfo(2000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000,
+                trafficList1);
 
         batteryStats.updateBluetoothStateLocked(info1,
                 0/*1_000_000*/, 2000, 2000);
@@ -102,11 +107,14 @@
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000);
 
-        BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000,
-                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000);
-        info2.setUidTraffic(ImmutableList.of(
-                new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000),
-                new UidTraffic(APP_UID, 7000, 8000)));
+
+        List<UidTraffic> trafficList2 = ImmutableList.of(
+                createUidTraffic(Process.BLUETOOTH_UID, 5000, 6000),
+                createUidTraffic(APP_UID, 7000, 8000));
+        BluetoothActivityEnergyInfo info2 = createBtEnergyInfo(4000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000,
+                trafficList2);
+
 
         batteryStats.updateBluetoothStateLocked(info2,
                 0 /*5_000_000 */, 4000, 4000);
@@ -202,11 +210,14 @@
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
 
-        BluetoothActivityEnergyInfo info1 = new BluetoothActivityEnergyInfo(2000,
-                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000);
-        info1.setUidTraffic(ImmutableList.of(
-                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
-                new UidTraffic(APP_UID, 3000, 4000)));
+
+        List<UidTraffic> trafficList1 = ImmutableList.of(
+                createUidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
+                createUidTraffic(APP_UID, 3000, 4000));
+        BluetoothActivityEnergyInfo info1 = createBtEnergyInfo(2000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000,
+                trafficList1);
+
 
         batteryStats.updateBluetoothStateLocked(info1,
                 1_000_000, 2000, 2000);
@@ -214,11 +225,13 @@
         uid.setProcessStateForTest(
                 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000);
 
-        BluetoothActivityEnergyInfo info2 = new BluetoothActivityEnergyInfo(4000,
-                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000);
-        info2.setUidTraffic(ImmutableList.of(
-                new UidTraffic(Process.BLUETOOTH_UID, 5000, 6000),
-                new UidTraffic(APP_UID, 7000, 8000)));
+        List<UidTraffic> trafficList2 = ImmutableList.of(
+                createUidTraffic(Process.BLUETOOTH_UID, 5000, 6000),
+                createUidTraffic(APP_UID, 7000, 8000));
+        BluetoothActivityEnergyInfo info2 = createBtEnergyInfo(4000,
+                BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000,
+                trafficList2);
+
 
         batteryStats.updateBluetoothStateLocked(info2,
                 5_000_000, 4000, 4000);
@@ -280,12 +293,15 @@
     }
 
     private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) {
-        final BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000,
+        List<UidTraffic> trafficList = ImmutableList.of(
+                createUidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
+                createUidTraffic(APP_UID, 3000, 4000));
+
+
+        final BluetoothActivityEnergyInfo info = createBtEnergyInfo(1000,
                 BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 7000, 5000, 0,
-                reportedEnergyUc);
-        info.setUidTraffic(ImmutableList.of(
-                new UidTraffic(Process.BLUETOOTH_UID, 1000, 2000),
-                new UidTraffic(APP_UID, 3000, 4000)));
+                reportedEnergyUc, trafficList);
+
         mStatsRule.getBatteryStats().updateBluetoothStateLocked(info,
                 consumedEnergyUc, 1000, 1000);
     }
@@ -304,4 +320,34 @@
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
         assertThat(usageDurationMillis).isEqualTo(durationMs);
     }
+
+    private UidTraffic createUidTraffic(int uid, long traffic1, long traffic2) {
+        final Parcel uidTrafficParcel = Parcel.obtain();
+        uidTrafficParcel.writeInt(uid);
+        uidTrafficParcel.writeLong(traffic1);
+        uidTrafficParcel.writeLong(traffic2);
+        uidTrafficParcel.setDataPosition(0);
+
+        UidTraffic traffic = UidTraffic.CREATOR.createFromParcel(uidTrafficParcel);
+        uidTrafficParcel.recycle();
+        return traffic;
+    }
+
+    private BluetoothActivityEnergyInfo createBtEnergyInfo(long timestamp, int stackState,
+            long txTime, long rxTime, long idleTime, long energyUsed, List<UidTraffic> traffic) {
+        final Parcel btActivityEnergyInfoParcel = Parcel.obtain();
+        btActivityEnergyInfoParcel.writeLong(timestamp);
+        btActivityEnergyInfoParcel.writeInt(stackState);
+        btActivityEnergyInfoParcel.writeLong(txTime);
+        btActivityEnergyInfoParcel.writeLong(rxTime);
+        btActivityEnergyInfoParcel.writeLong(idleTime);
+        btActivityEnergyInfoParcel.writeLong(energyUsed);
+        btActivityEnergyInfoParcel.writeTypedList(traffic);
+        btActivityEnergyInfoParcel.setDataPosition(0);
+
+        BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR
+                .createFromParcel(btActivityEnergyInfoParcel);
+        btActivityEnergyInfoParcel.recycle();
+        return info;
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/PowerCalculatorTest.java
deleted file mode 100644
index 4bd5724..0000000
--- a/core/tests/coretests/src/com/android/internal/os/PowerCalculatorTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- *
- */
-
-package com.android.internal.os;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.os.BatteryStats;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import junit.framework.TestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PowerCalculatorTest extends TestCase {
-    private static final long US_IN_HR = 1000L * 1000L * 60L * 60L;
-
-    @Mock
-    private PowerProfile mPowerProfile;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    /** Test {@link MediaPowerCalculator#calculateApp} */
-    @Test
-    public void testMediaPowerCalculator() {
-        when(mPowerProfile.getAveragePower(PowerProfile.POWER_AUDIO)).thenReturn(12.0);
-        when(mPowerProfile.getAveragePower(PowerProfile.POWER_VIDEO)).thenReturn(25.0);
-
-        BatteryStats.Uid u = mock(BatteryStats.Uid.class);
-        BatteryStats.Timer audioTimer = mock(BatteryStats.Timer.class);
-        when(u.getAudioTurnedOnTimer()).thenReturn(audioTimer);
-        when(audioTimer.getTotalTimeLocked(2L * US_IN_HR, 0)).thenReturn(2L * US_IN_HR);
-        BatteryStats.Timer videoTimer = mock(BatteryStats.Timer.class);
-        when(u.getVideoTurnedOnTimer()).thenReturn(videoTimer);
-        when(videoTimer.getTotalTimeLocked(2L * US_IN_HR, 0)).thenReturn(1L * US_IN_HR);
-
-        MediaPowerCalculator mediaPowerCalculator = new MediaPowerCalculator(mPowerProfile);
-        BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
-
-        mediaPowerCalculator.calculate(List.of(app), null, 2L * US_IN_HR, 2L * US_IN_HR, 0, null);
-        assertEquals(49.0, app.sumPower());
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
index 4c58dfa..589e4f9 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
@@ -27,6 +27,7 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.concurrent.atomic.AtomicReference;
 
 public final class DumpableContainerImplTest {
 
@@ -41,7 +42,7 @@
 
     @Test
     public void testAddDumpable_dumpableWithoutName() {
-        Dumpable noNamer = new Dumpable() {
+        Dumpable namelessDumpable = new Dumpable() {
 
             @Override
             public String getDumpableName() {
@@ -54,7 +55,7 @@
             }
 
         };
-        assertThrows(NullPointerException.class, () -> mImpl.addDumpable(noNamer));
+        assertThrows(NullPointerException.class, () -> mImpl.addDumpable(namelessDumpable));
     }
 
     @Test
@@ -178,11 +179,76 @@
                         + "......6 Args: 4,8,15,16,23,42,\n");
     }
 
+    @Test
+    public void testRemoveDumpable_null() {
+        assertThrows(NullPointerException.class, () -> mImpl.removeDumpable(null));
+    }
+
+    @Test
+    public void testARemoveDumpable_dumpableWithoutName() {
+        // Need a non-null name initially otherwise it won't be added
+        AtomicReference<String> name = new AtomicReference<>("A Dumpable Has No Name");
+        Dumpable dumpable = new Dumpable() {
+
+            @Override
+            public String getDumpableName() {
+                return name.get();
+            }
+
+            @Override
+            public void dump(PrintWriter writer, String[] args) {
+                throw new UnsupportedOperationException("D'OH!");
+            }
+
+        };
+        assertWithMessage("addDumpable(with name)").that(mImpl.addDumpable(dumpable)).isTrue();
+
+        name.set(null);
+        assertWithMessage("removeDumpable(nameless)").that(mImpl.removeDumpable(dumpable))
+                .isFalse();
+    }
+
+    @Test
+    public void testRemoveDumpable_empty() {
+        CustomDumpable dumpable = new CustomDumpable("The name is Bond", "James Bond!");
+
+        assertWithMessage("removeDumpable()").that(mImpl.removeDumpable(dumpable)).isFalse();
+    }
+
+    @Test
+    public void testRemoveDumpable_sameNameButDifferentDumpable() {
+        CustomDumpable real = new CustomDumpable("Slim Shade", "Please stand up!");
+        CustomDumpable fake = new CustomDumpable("Slim Shade", "Please stand up!");
+
+        mImpl.addDumpable(real);
+
+        assertWithMessage("removeDumpable(fake)").that(mImpl.removeDumpable(fake)).isFalse();
+        assertWithMessage("removeDumpable(real)").that(mImpl.removeDumpable(real)).isTrue();
+    }
+
+    @Test
+    public void testRemoveDumpable_existing() {
+        CustomDumpable dumpable = new CustomDumpable("Homer", "D'ohmp!");
+
+        mImpl.addDumpable(dumpable);
+        mImpl.listDumpables("...", mWriter);
+        assertWithMessage("listDumpables()").that(getOutput()).isEqualTo("...1 dumpables: Homer\n");
+
+        assertWithMessage("removeDumpable()").that(mImpl.removeDumpable(dumpable)).isTrue();
+        resetOutput();
+        mImpl.listDumpables("...", mWriter);
+        assertWithMessage("listDumpables(...)").that(getOutput()).isEqualTo("...No dumpables\n");
+    }
+
     private String getOutput() {
         mSw.flush();
         return mSw.toString();
     }
 
+    private void resetOutput() {
+        mSw.getBuffer().setLength(0);
+    }
+
     private static final class CustomDumpable implements Dumpable {
         public final String name;
         public final String content;
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index a9e730d..2678c79d 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -39,6 +39,8 @@
     private static native long nativeGetLastAcquiredFrameNum(long ptr);
     private static native void nativeApplyPendingTransactions(long ptr, long frameNumber);
     private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr);
+    private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr,
+            long frameNumber);
 
     /** Create a new connection with the surface flinger. */
     public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@@ -159,4 +161,17 @@
     public boolean isSameSurfaceControl(SurfaceControl sc) {
         return nativeIsSameSurfaceControl(mNativeObject, sc.mNativeObject);
     }
+
+    /**
+     * Get any transactions that were passed to {@link #mergeWithNextTransaction} with the
+     * specified frameNumber. This is intended to ensure transactions don't get stuck as pending
+     * if the specified frameNumber is never drawn.
+     *
+     * @param frameNumber The frameNumber used to determine which transactions to apply.
+     * @return a Transaction that contains the merge of all the transactions that were sent to
+     *         mergeWithNextTransaction
+     */
+    public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) {
+        return nativeGatherPendingTransactions(mNativeObject, frameNumber);
+    }
 }
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index ee41148..ee0d647 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -290,7 +290,7 @@
      * context at a time.
      *
      * @param texName The name of the OpenGL ES texture that will be created.  This texture name
-     * must be unusued in the OpenGL ES context that is current on the calling thread.
+     * must be unused in the OpenGL ES context that is current on the calling thread.
      */
     public void attachToGLContext(int texName) {
         int err = nativeAttachToGLContext(texName);
diff --git a/keystore/OWNERS b/keystore/OWNERS
index a63ca46..7ab9d76 100644
--- a/keystore/OWNERS
+++ b/keystore/OWNERS
@@ -1,4 +1,4 @@
+eranm@google.com
 jbires@google.com
 jdanis@google.com
-robbarnes@google.com
 swillden@google.com
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
index fc6ea9a..5beaa87 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml
@@ -22,6 +22,8 @@
 
     <!-- The background of the top-level layout acts as the background dim. -->
 
+    <!-- Setting the alpha of the dialog container to 0, since it shouldn't be visible until the
+         enter animation starts. -->
     <LinearLayout
         android:id="@+id/letterbox_education_dialog_container"
         android:layout_width="wrap_content"
@@ -30,7 +32,8 @@
         android:gravity="center_horizontal"
         android:orientation="vertical"
         android:background="@drawable/letterbox_education_dialog_background"
-        android:padding="24dp">
+        android:padding="24dp"
+        android:alpha="0">
 
         <ImageView
             android:id="@+id/letterbox_education_icon"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index c52d87d..79e6242 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -367,4 +367,9 @@
     void hideBadge() {
         mAppIcon.setVisibility(GONE);
     }
+
+    @Override
+    public String toString() {
+        return "BadgedImageView{" + mBubble + "}";
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 57cb7a5..6ed1ba9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -24,6 +24,7 @@
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM;
@@ -616,7 +617,7 @@
         return mTaskViewTransitions;
     }
 
-    /** Contains information to help position things on the screen.  */
+    /** Contains information to help position things on the screen. */
     BubblePositioner getPositioner() {
         return mBubblePositioner;
     }
@@ -659,8 +660,8 @@
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                 PixelFormat.TRANSLUCENT);
 
         mWmLayoutParams.setTrustedOverlay();
@@ -750,7 +751,7 @@
         // First clear any existing keys that might be stored.
         mSavedBubbleKeysPerUser.remove(userId);
         // Add in all active bubbles for the current user.
-        for (Bubble bubble: mBubbleData.getBubbles()) {
+        for (Bubble bubble : mBubbleData.getBubbles()) {
             mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
         }
     }
@@ -982,9 +983,9 @@
     /**
      * Adds or updates a bubble associated with the provided notification entry.
      *
-     * @param notif the notification associated with this bubble.
+     * @param notif          the notification associated with this bubble.
      * @param suppressFlyout this bubble suppress flyout or not.
-     * @param showInShade this bubble show in shade or not.
+     * @param showInShade    this bubble show in shade or not.
      */
     @VisibleForTesting
     public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) {
@@ -992,11 +993,17 @@
         mSysuiProxy.setNotificationInterruption(notif.getKey());
         if (!notif.getRanking().isTextChanged()
                 && (notif.getBubbleMetadata() != null
-                    && !notif.getBubbleMetadata().getAutoExpandBubble())
+                && !notif.getBubbleMetadata().getAutoExpandBubble())
                 && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
             // Update the bubble but don't promote it out of overflow
             Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
             b.setEntry(notif);
+        } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) {
+            // Update the bubble but don't promote it out of overflow
+            Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey());
+            if (b != null) {
+                b.setEntry(notif);
+            }
         } else {
             Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
             inflateAndAdd(bubble, suppressFlyout, showInShade);
@@ -1170,6 +1177,18 @@
 
         @Override
         public void applyUpdate(BubbleData.Update update) {
+            if (DEBUG_BUBBLE_CONTROLLER) {
+                Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null)
+                        + " bubbleRemoved="
+                        + (update.removedBubbles != null && update.removedBubbles.size() > 0)
+                        + " bubbleUpdated=" + (update.updatedBubble != null)
+                        + " orderChanged=" + update.orderChanged
+                        + " expandedChanged=" + update.expandedChanged
+                        + " selectionChanged=" + update.selectionChanged
+                        + " suppressed=" + (update.suppressedBubble != null)
+                        + " unsuppressed=" + (update.unsuppressedBubble != null));
+            }
+
             ensureStackViewCreated();
 
             // Lazy load overflow bubbles from disk
@@ -1249,6 +1268,14 @@
                 mStackView.updateBubble(update.updatedBubble);
             }
 
+            if (update.suppressedBubble != null && mStackView != null) {
+                mStackView.setBubbleSuppressed(update.suppressedBubble, true);
+            }
+
+            if (update.unsuppressedBubble != null && mStackView != null) {
+                mStackView.setBubbleSuppressed(update.unsuppressedBubble, false);
+            }
+
             // At this point, the correct bubbles are inflated in the stack.
             // Make sure the order in bubble data is reflected in bubble row.
             if (update.orderChanged && mStackView != null) {
@@ -1263,14 +1290,6 @@
                 }
             }
 
-            if (update.suppressedBubble != null && mStackView != null) {
-                mStackView.setBubbleVisibility(update.suppressedBubble, false);
-            }
-
-            if (update.unsuppressedBubble != null && mStackView != null) {
-                mStackView.setBubbleVisibility(update.unsuppressedBubble, true);
-            }
-
             // Expanding? Apply this last.
             if (update.expandedChanged && update.expanded) {
                 if (mStackView != null) {
@@ -1398,7 +1417,7 @@
      * that should filter out any invalid bubbles, but should protect SysUI side just in case.
      *
      * @param context the context to use.
-     * @param entry the entry to bubble.
+     * @param entry   the entry to bubble.
      */
     static boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
         PendingIntent intent = entry.getBubbleMetadata() != null
@@ -1531,7 +1550,7 @@
                     String groupKey) {
                 return mSuppressedBubbleKeys.contains(key)
                         || (mSuppressedGroupToNotifKeys.containsKey(groupKey)
-                                && key.equals(mSuppressedGroupToNotifKeys.get(groupKey)));
+                        && key.equals(mSuppressedGroupToNotifKeys.get(groupKey)));
             }
 
             @Nullable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 51b7eaa..9961ad7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -224,7 +224,8 @@
     }
 
     public boolean hasAnyBubbleWithKey(String key) {
-        return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key);
+        return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key)
+                || hasSuppressedBubbleWithKey(key);
     }
 
     public boolean hasBubbleInStackWithKey(String key) {
@@ -235,6 +236,20 @@
         return getOverflowBubbleWithKey(key) != null;
     }
 
+    /**
+     * Check if there are any bubbles suppressed with the given notification <code>key</code>
+     */
+    public boolean hasSuppressedBubbleWithKey(String key) {
+        return mSuppressedBubbles.values().stream().anyMatch(b -> b.getKey().equals(key));
+    }
+
+    /**
+     * Check if there are any bubbles suppressed with the given <code>LocusId</code>
+     */
+    public boolean isSuppressedWithLocusId(LocusId locusId) {
+        return mSuppressedBubbles.get(locusId) != null;
+    }
+
     @Nullable
     public BubbleViewProvider getSelectedBubble() {
         return mSelectedBubble;
@@ -356,11 +371,11 @@
             boolean isSuppressed = mSuppressedBubbles.containsKey(locusId);
             if (isSuppressed && (!bubble.isSuppressed() || !bubble.isSuppressable())) {
                 mSuppressedBubbles.remove(locusId);
-                mStateChange.unsuppressedBubble = bubble;
+                doUnsuppress(bubble);
             } else if (!isSuppressed && (bubble.isSuppressed()
                     || bubble.isSuppressable() && mVisibleLocusIds.contains(locusId))) {
                 mSuppressedBubbles.put(locusId, bubble);
-                mStateChange.suppressedBubble = bubble;
+                doSuppress(bubble);
             }
         }
         dispatchPendingChanges();
@@ -532,16 +547,19 @@
         if (mPendingBubbles.containsKey(key)) {
             mPendingBubbles.remove(key);
         }
+
+        boolean shouldRemoveHiddenBubble = reason == Bubbles.DISMISS_NOTIF_CANCEL
+                || reason == Bubbles.DISMISS_GROUP_CANCELLED
+                || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE
+                || reason == Bubbles.DISMISS_BLOCKED
+                || reason == Bubbles.DISMISS_SHORTCUT_REMOVED
+                || reason == Bubbles.DISMISS_PACKAGE_REMOVED
+                || reason == Bubbles.DISMISS_USER_CHANGED;
+
         int indexToRemove = indexForKey(key);
         if (indexToRemove == -1) {
             if (hasOverflowBubbleWithKey(key)
-                    && (reason == Bubbles.DISMISS_NOTIF_CANCEL
-                        || reason == Bubbles.DISMISS_GROUP_CANCELLED
-                        || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE
-                        || reason == Bubbles.DISMISS_BLOCKED
-                        || reason == Bubbles.DISMISS_SHORTCUT_REMOVED
-                        || reason == Bubbles.DISMISS_PACKAGE_REMOVED
-                        || reason == Bubbles.DISMISS_USER_CHANGED)) {
+                    && shouldRemoveHiddenBubble) {
 
                 Bubble b = getOverflowBubbleWithKey(key);
                 if (DEBUG_BUBBLE_DATA) {
@@ -555,6 +573,17 @@
                 mStateChange.bubbleRemoved(b, reason);
                 mStateChange.removedOverflowBubble = b;
             }
+            if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
+                Bubble b = getSuppressedBubbleWithKey(key);
+                if (DEBUG_BUBBLE_DATA) {
+                    Log.d(TAG, "Cancel suppressed bubble: " + b);
+                }
+                if (b != null) {
+                    mSuppressedBubbles.remove(b.getLocusId());
+                    b.stopInflation();
+                    mStateChange.bubbleRemoved(b, reason);
+                }
+            }
             return;
         }
         Bubble bubbleToRemove = mBubbles.get(indexToRemove);
@@ -579,19 +608,73 @@
 
         // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
         if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
-            // Move selection to the new bubble at the same position.
-            int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
-            BubbleViewProvider newSelected = mBubbles.get(newIndex);
-            setSelectedBubbleInternal(newSelected);
+            setNewSelectedIndex(indexToRemove);
         }
         maybeSendDeleteIntent(reason, bubbleToRemove);
     }
 
+    private void setNewSelectedIndex(int indexOfSelected) {
+        if (mBubbles.isEmpty()) {
+            Log.w(TAG, "Bubbles list empty when attempting to select index: " + indexOfSelected);
+            return;
+        }
+        // Move selection to the new bubble at the same position.
+        int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1);
+        if (DEBUG_BUBBLE_DATA) {
+            Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected);
+        }
+        BubbleViewProvider newSelected = mBubbles.get(newIndex);
+        setSelectedBubbleInternal(newSelected);
+    }
+
+    private void doSuppress(Bubble bubble) {
+        if (DEBUG_BUBBLE_DATA) {
+            Log.d(TAG, "doSuppressed: " + bubble);
+        }
+        mStateChange.suppressedBubble = bubble;
+        bubble.setSuppressBubble(true);
+
+        int indexToRemove = mBubbles.indexOf(bubble);
+        // Order changes if we are not suppressing the last bubble
+        mStateChange.orderChanged = !(mBubbles.size() - 1 == indexToRemove);
+        mBubbles.remove(indexToRemove);
+
+        // Update selection if we suppressed the selected bubble
+        if (Objects.equals(mSelectedBubble, bubble)) {
+            if (mBubbles.isEmpty()) {
+                // Don't use setSelectedBubbleInternal because we don't want to trigger an
+                // applyUpdate
+                mSelectedBubble = null;
+            } else {
+                // Mark new first bubble as selected
+                setNewSelectedIndex(0);
+            }
+        }
+    }
+
+    private void doUnsuppress(Bubble bubble) {
+        if (DEBUG_BUBBLE_DATA) {
+            Log.d(TAG, "doUnsuppressed: " + bubble);
+        }
+        bubble.setSuppressBubble(false);
+        mStateChange.unsuppressedBubble = bubble;
+        mBubbles.add(bubble);
+        if (mBubbles.size() > 1) {
+            // See where the bubble actually lands
+            repackAll();
+            mStateChange.orderChanged = true;
+        }
+        if (mBubbles.get(0) == bubble) {
+            // Unsuppressed bubble is sorted to first position. Mark it as the selected.
+            setNewSelectedIndex(0);
+        }
+    }
+
     void overflowBubble(@DismissReason int reason, Bubble bubble) {
         if (bubble.getPendingIntentCanceled()
                 || !(reason == Bubbles.DISMISS_AGED
-                    || reason == Bubbles.DISMISS_USER_GESTURE
-                    || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
+                || reason == Bubbles.DISMISS_USER_GESTURE
+                || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
             return;
         }
         if (DEBUG_BUBBLE_DATA) {
@@ -619,7 +702,7 @@
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "dismissAll: reason=" + reason);
         }
-        if (mBubbles.isEmpty()) {
+        if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) {
             return;
         }
         setExpandedInternal(false);
@@ -627,6 +710,10 @@
         while (!mBubbles.isEmpty()) {
             doRemove(mBubbles.get(0).getKey(), reason);
         }
+        while (!mSuppressedBubbles.isEmpty()) {
+            Bubble bubble = mSuppressedBubbles.removeAt(0);
+            doRemove(bubble.getKey(), reason);
+        }
         dispatchPendingChanges();
     }
 
@@ -635,11 +722,15 @@
      * and if there's a matching bubble for that locusId then the bubble may be hidden or shown
      * depending on the visibility of the locusId.
      *
-     * @param taskId the taskId associated with the locusId visibility change.
+     * @param taskId  the taskId associated with the locusId visibility change.
      * @param locusId the locusId whose visibility has changed.
      * @param visible whether the task with the locusId is visible or not.
      */
     public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) {
+        if (DEBUG_BUBBLE_DATA) {
+            Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible);
+        }
+
         Bubble matchingBubble = getBubbleInStackWithLocusId(locusId);
         // Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled.
         if (visible && (matchingBubble == null || matchingBubble.getTaskId() != taskId)) {
@@ -648,20 +739,22 @@
             mVisibleLocusIds.remove(locusId);
         }
         if (matchingBubble == null) {
-            return;
+            // Check if there is a suppressed bubble for this LocusId
+            matchingBubble = mSuppressedBubbles.get(locusId);
+            if (matchingBubble == null) {
+                return;
+            }
         }
         boolean isAlreadySuppressed = mSuppressedBubbles.get(locusId) != null;
         if (visible && !isAlreadySuppressed && matchingBubble.isSuppressable()
                 && taskId != matchingBubble.getTaskId()) {
             mSuppressedBubbles.put(locusId, matchingBubble);
-            matchingBubble.setSuppressBubble(true);
-            mStateChange.suppressedBubble = matchingBubble;
+            doSuppress(matchingBubble);
             dispatchPendingChanges();
         } else if (!visible) {
             Bubble unsuppressedBubble = mSuppressedBubbles.remove(locusId);
             if (unsuppressedBubble != null) {
-                unsuppressedBubble.setSuppressBubble(false);
-                mStateChange.unsuppressedBubble = unsuppressedBubble;
+                doUnsuppress(unsuppressedBubble);
             }
             dispatchPendingChanges();
         }
@@ -720,14 +813,14 @@
     /**
      * Logs the bubble UI event.
      *
-     * @param provider The bubble view provider that is being interacted on. Null value indicates
-     *               that the user interaction is not specific to one bubble.
-     * @param action The user interaction enum
+     * @param provider    The bubble view provider that is being interacted on. Null value indicates
+     *                    that the user interaction is not specific to one bubble.
+     * @param action      The user interaction enum
      * @param packageName SystemUI package
      * @param bubbleCount Number of bubbles in the stack
      * @param bubbleIndex Index of bubble in the stack
-     * @param normalX Normalized x position of the stack
-     * @param normalY Normalized y position of the stack
+     * @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,
             int bubbleCount, int bubbleIndex, float normalX, float normalY) {
@@ -869,6 +962,9 @@
         if (b == null) {
             b = getOverflowBubbleWithKey(key);
         }
+        if (b == null) {
+            b = getSuppressedBubbleWithKey(key);
+        }
         return b;
     }
 
@@ -946,6 +1042,23 @@
         return null;
     }
 
+    /**
+     * Get a suppressed bubble with given notification <code>key</code>
+     *
+     * @param key notification key
+     * @return bubble that matches or null
+     */
+    @Nullable
+    @VisibleForTesting(visibility = PRIVATE)
+    public Bubble getSuppressedBubbleWithKey(String key) {
+        for (Bubble b : mSuppressedBubbles.values()) {
+            if (b.getKey().equals(key)) {
+                return b;
+            }
+        }
+        return null;
+    }
+
     @VisibleForTesting(visibility = PRIVATE)
     void setTimeSource(TimeSource timeSource) {
         mTimeSource = timeSource;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 7ab6835..c2eb08c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -168,26 +168,27 @@
 
     private static final SurfaceSynchronizer DEFAULT_SURFACE_SYNCHRONIZER =
             new SurfaceSynchronizer() {
-        @Override
-        public void syncSurfaceAndRun(Runnable callback) {
-            Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
-                // Just wait 2 frames. There is no guarantee, but this is usually enough time that
-                // the requested change is reflected on the screen.
-                // TODO: Once SurfaceFlinger provide APIs to sync the state of {@code View} and
-                // surfaces, rewrite this logic with them.
-                private int mFrameWait = 2;
-
                 @Override
-                public void doFrame(long frameTimeNanos) {
-                    if (--mFrameWait > 0) {
-                        Choreographer.getInstance().postFrameCallback(this);
-                    } else {
-                        callback.run();
-                    }
+                public void syncSurfaceAndRun(Runnable callback) {
+                    Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
+                        // Just wait 2 frames. There is no guarantee, but this is usually enough
+                        // time that the requested change is reflected on the screen.
+                        // TODO: Once SurfaceFlinger provide APIs to sync the state of
+                        //  {@code View} and surfaces, rewrite this logic with them.
+                        private int mFrameWait = 2;
+
+                        @Override
+                        public void doFrame(long frameTimeNanos) {
+                            if (--mFrameWait > 0) {
+                                Choreographer.getInstance().postFrameCallback(this);
+                            } else {
+                                callback.run();
+                            }
+                        }
+                    };
+                    Choreographer.getInstance().postFrameCallback(frameCallback);
                 }
-            });
-        }
-    };
+            };
     private final BubbleController mBubbleController;
     private final BubbleData mBubbleData;
     private StackViewState mStackViewState = new StackViewState();
@@ -781,7 +782,7 @@
         mPositioner = mBubbleController.getPositioner();
 
         final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[] {android.R.attr.dialogCornerRadius});
+                new int[]{android.R.attr.dialogCornerRadius});
         mCornerRadius = ta.getDimensionPixelSize(0, 0);
         ta.recycle();
 
@@ -942,7 +943,7 @@
         });
 
         // If the stack itself is clicked, it means none of its touchable views (bubbles, flyouts,
-         // TaskView, etc.) were touched. Collapse the stack if it's expanded.
+        // TaskView, etc.) were touched. Collapse the stack if it's expanded.
         setOnClickListener(view -> {
             if (mShowingManage) {
                 showManageMenu(false /* show */);
@@ -1656,7 +1657,12 @@
                 return;
             }
         }
-        Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+        // If a bubble is suppressed, it is not attached to the container. Clean it up.
+        if (bubble.isSuppressed()) {
+            bubble.cleanupViews();
+        } else {
+            Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+        }
     }
 
     private void updateOverflowVisibility() {
@@ -1842,11 +1848,30 @@
         }
     }
 
-    void setBubbleVisibility(Bubble b, boolean visible) {
-        if (b.getIconView() != null) {
-            b.getIconView().setVisibility(visible ? VISIBLE : GONE);
+    void setBubbleSuppressed(Bubble bubble, boolean suppressed) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
+            Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble);
         }
-        // TODO(b/181166384): Animate in / out & handle adjusting how the bubbles overlap
+        if (suppressed) {
+            int index = getBubbleIndex(bubble);
+            mBubbleContainer.removeViewAt(index);
+            updateExpandedView();
+        } else {
+            if (bubble.getIconView() == null) {
+                return;
+            }
+            if (bubble.getIconView().getParent() != null) {
+                Log.e(TAG, "Bubble is already added to parent. Can't unsuppress: " + bubble);
+                return;
+            }
+            int index = mBubbleData.getBubbles().indexOf(bubble);
+            // Add the view back to the correct position
+            mBubbleContainer.addView(bubble.getIconView(), index,
+                    new LayoutParams(mPositioner.getBubbleSize(),
+                            mPositioner.getBubbleSize()));
+            updateBubbleShadows(false /* showForAllBubbles */);
+            requestUpdate();
+        }
     }
 
     /**
@@ -2191,7 +2216,7 @@
             PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
                     .spring(DynamicAnimation.TRANSLATION_Y,
                             mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize,
-                    mTranslateSpringConfig)
+                            mTranslateSpringConfig)
                     .start();
         }
 
@@ -3040,14 +3065,14 @@
      * Logs the bubble UI event.
      *
      * @param provider the bubble view provider that is being interacted on. Null value indicates
-     *               that the user interaction is not specific to one bubble.
-     * @param action the user interaction enum.
+     *                 that the user interaction is not specific to one bubble.
+     * @param action   the user interaction enum.
      */
     private void logBubbleEvent(@Nullable BubbleViewProvider provider, int action) {
         final String packageName =
                 (provider != null && provider instanceof Bubble)
-                    ? ((Bubble) provider).getPackageName()
-                    : "null";
+                        ? ((Bubble) provider).getPackageName()
+                        : "null";
         mBubbleData.logBubbleEvent(provider,
                 action,
                 packageName,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
index 4ec2c8d..55052e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
@@ -364,6 +364,11 @@
         final int oldIndex = indexOfChild(view);
 
         super.removeView(view);
+        if (view.getParent() != null) {
+            // View still has a parent. This could have been added as a transient view.
+            // Remove it from transient views.
+            super.removeTransientView(view);
+        }
         addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */);
 
         if (mController != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 60b6433..3ba056a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -750,6 +750,12 @@
             // Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
             // to the back of the stack, it'll be largely invisible so don't bother animating it in.
             animateInBubble(child, index);
+        } else {
+            // We are not animating the bubble in. Make sure it has the right alpha and scale values
+            // in case this view was previously removed and is being re-added.
+            child.setAlpha(1f);
+            child.setScaleX(1f);
+            child.setScaleY(1f);
         }
     }
 
@@ -785,23 +791,24 @@
             }
         };
 
+        boolean swapped = false;
         for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
             View view = bubbleViews.get(newIndex);
             final int oldIndex = mLayout.indexOfChild(view);
-            animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+            swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+        }
+        if (!swapped) {
+            // All bubbles were at the right position. Make sure badges and z order is correct.
+            updateAllIcons.run();
         }
     }
 
-    private void animateSwap(View view, int oldIndex, int newIndex,
+    private boolean animateSwap(View view, int oldIndex, int newIndex,
             Runnable updateAllIcons, Runnable finishReorder) {
         if (newIndex == oldIndex) {
-            // Add new bubble to index 0; move existing bubbles down
-            updateBadgesAndZOrder(view, newIndex);
-            if (newIndex == 0) {
-                animateInBubble(view, newIndex);
-            } else {
-                moveToFinalIndex(view, newIndex, finishReorder);
-            }
+            // View order did not change. Make sure position is correct.
+            moveToFinalIndex(view, newIndex, finishReorder);
+            return false;
         } else {
             // Reorder existing bubbles
             if (newIndex == 0) {
@@ -809,6 +816,7 @@
             } else {
                 moveToFinalIndex(view, newIndex, finishReorder);
             }
+            return true;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 4d279bc..656dae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -40,6 +40,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -74,8 +75,22 @@
     private final SparseArray<PerDisplayOnInsetsChangedListener> mOnInsetsChangedListeners =
             new SparseArray<>(0);
 
-    /** The showing UIs by task id. */
-    private final SparseArray<CompatUIWindowManager> mActiveLayouts = new SparseArray<>(0);
+    /**
+     * The active Compat Control UI layouts by task id.
+     *
+     * <p>An active layout is a layout that is eligible to be shown for the associated task but
+     * isn't necessarily shown at a given time.
+     */
+    private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0);
+
+    /**
+     * The active Letterbox Education layout if there is one (there can be at most one active).
+     *
+     * <p>An active layout is a layout that is eligible to be shown for the associated task but
+     * isn't necessarily shown at a given time.
+     */
+    @Nullable
+    private LetterboxEduWindowManager mActiveLetterboxEduLayout;
 
     /** Avoid creating display context frequently for non-default display. */
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@@ -135,14 +150,12 @@
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (taskInfo.configuration == null || taskListener == null) {
             // Null token means the current foreground activity is not in compatibility mode.
-            removeLayout(taskInfo.taskId);
-        } else if (mActiveLayouts.contains(taskInfo.taskId)) {
-            // UI already exists, update the UI layout.
-            updateLayout(taskInfo, taskListener);
-        } else {
-            // Create a new compat UI.
-            createLayout(taskInfo, taskListener);
+            removeLayouts(taskInfo.taskId);
+            return;
         }
+
+        createOrUpdateCompatLayout(taskInfo, taskListener);
+        createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
     }
 
     @Override
@@ -159,7 +172,7 @@
         final List<Integer> toRemoveTaskIds = new ArrayList<>();
         forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
         for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
-            removeLayout(toRemoveTaskIds.get(i));
+            removeLayouts(toRemoveTaskIds.get(i));
         }
     }
 
@@ -218,26 +231,39 @@
         return mDisplaysWithIme.contains(displayId);
     }
 
-    private void createLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
-        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
-        if (context == null) {
-            Log.e(TAG, "Cannot get context for display " + taskInfo.displayId);
+    private void createOrUpdateCompatLayout(TaskInfo taskInfo,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
+        if (layout != null) {
+            // UI already exists, update the UI layout.
+            if (!layout.updateCompatInfo(taskInfo, taskListener,
+                    showOnDisplay(layout.getDisplayId()))) {
+                // The layout is no longer eligible to be shown, remove from active layouts.
+                mActiveCompatLayouts.remove(taskInfo.taskId);
+            }
             return;
         }
 
-        final CompatUIWindowManager compatUIWindowManager =
-                createLayout(context, taskInfo, taskListener);
-        mActiveLayouts.put(taskInfo.taskId, compatUIWindowManager);
-        compatUIWindowManager.createLayout(showOnDisplay(taskInfo.displayId), taskInfo);
+        // Create a new UI layout.
+        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+        if (context == null) {
+            return;
+        }
+        layout = createCompatUiWindowManager(context, taskInfo, taskListener);
+        if (layout.createLayout(showOnDisplay(taskInfo.displayId))) {
+            // The new layout is eligible to be shown, add it the active layouts.
+            mActiveCompatLayouts.put(taskInfo.taskId, layout);
+        }
     }
 
     @VisibleForTesting
-    CompatUIWindowManager createLayout(Context context, TaskInfo taskInfo,
+    CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
             ShellTaskOrganizer.TaskListener taskListener) {
         final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
-                taskInfo.configuration, mSyncQueue, mCallback, taskInfo.taskId, taskListener,
+                taskInfo, mSyncQueue, mCallback, taskListener,
                 mDisplayController.getDisplayLayout(taskInfo.displayId), mHasShownSizeCompatHint,
                 mHasShownCameraCompatHint);
+        // TODO(b/218304113): updates values only if hints are actually shown to the user.
         // Only show hints for the first time.
         if (taskInfo.topActivityInSizeCompat) {
             mHasShownSizeCompatHint = true;
@@ -248,19 +274,53 @@
         return compatUIWindowManager;
     }
 
-    private void updateLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
-        final CompatUIWindowManager layout = mActiveLayouts.get(taskInfo.taskId);
-        if (layout == null) {
+    private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        if (mActiveLetterboxEduLayout != null
+                && mActiveLetterboxEduLayout.getTaskId() == taskInfo.taskId) {
+            // UI already exists, update the UI layout.
+            if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener,
+                    showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) {
+                // The layout is no longer eligible to be shown, clear active layout.
+                mActiveLetterboxEduLayout = null;
+            }
             return;
         }
-        layout.updateCompatInfo(taskInfo, taskListener, showOnDisplay(layout.getDisplayId()));
+
+        // Create a new UI layout.
+        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+        if (context == null) {
+            return;
+        }
+        LetterboxEduWindowManager newLayout = new LetterboxEduWindowManager(context, taskInfo,
+                mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+                this::onLetterboxEduDismissed);
+        if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+            // The new layout is eligible to be shown, make it the active layout.
+            if (mActiveLetterboxEduLayout != null) {
+                // Release the previous layout since at most one can be active.
+                // Since letterbox education is only shown once to the user, releasing the previous
+                // layout is only a precaution.
+                mActiveLetterboxEduLayout.release();
+            }
+            mActiveLetterboxEduLayout = newLayout;
+        }
     }
 
-    private void removeLayout(int taskId) {
-        final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
+    private void onLetterboxEduDismissed() {
+        mActiveLetterboxEduLayout = null;
+    }
+
+    private void removeLayouts(int taskId) {
+        final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
         if (layout != null) {
             layout.release();
-            mActiveLayouts.remove(taskId);
+            mActiveCompatLayouts.remove(taskId);
+        }
+
+        if (mActiveLetterboxEduLayout != null && mActiveLetterboxEduLayout.getTaskId() == taskId) {
+            mActiveLetterboxEduLayout.release();
+            mActiveLetterboxEduLayout = null;
         }
     }
 
@@ -278,28 +338,34 @@
             if (display != null) {
                 context = mContext.createDisplayContext(display);
                 mDisplayContextCache.put(displayId, new WeakReference<>(context));
+            } else {
+                Log.e(TAG, "Cannot get context for display " + displayId);
             }
         }
         return context;
     }
 
-    private void forAllLayoutsOnDisplay(int displayId, Consumer<CompatUIWindowManager> callback) {
+    private void forAllLayoutsOnDisplay(int displayId,
+            Consumer<CompatUIWindowManagerAbstract> callback) {
         forAllLayouts(layout -> layout.getDisplayId() == displayId, callback);
     }
 
-    private void forAllLayouts(Consumer<CompatUIWindowManager> callback) {
+    private void forAllLayouts(Consumer<CompatUIWindowManagerAbstract> callback) {
         forAllLayouts(layout -> true, callback);
     }
 
-    private void forAllLayouts(Predicate<CompatUIWindowManager> condition,
-            Consumer<CompatUIWindowManager> callback) {
-        for (int i = 0; i < mActiveLayouts.size(); i++) {
-            final int taskId = mActiveLayouts.keyAt(i);
-            final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
+    private void forAllLayouts(Predicate<CompatUIWindowManagerAbstract> condition,
+            Consumer<CompatUIWindowManagerAbstract> callback) {
+        for (int i = 0; i < mActiveCompatLayouts.size(); i++) {
+            final int taskId = mActiveCompatLayouts.keyAt(i);
+            final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
             if (layout != null && condition.test(layout)) {
                 callback.accept(layout);
             }
         }
+        if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) {
+            callback.accept(mActiveLetterboxEduLayout);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 9c001a3..3a37b5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -25,7 +25,6 @@
 import android.app.TaskInfo;
 import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -36,6 +35,8 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
+import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
 
 /**
  * Window manager for the Size Compat restart button and Camera Compat control.
@@ -43,18 +44,19 @@
 class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
 
     /**
-     * The Compat UI should be the topmost child of the Task in case there can be more than one
-     * child.
+     * The Compat UI should be below the Letterbox Education.
      */
-    private static final int Z_ORDER = Integer.MAX_VALUE;
+    private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1;
 
-    private final CompatUIController.CompatUICallback mCallback;
+    private final CompatUICallback mCallback;
 
     // Remember the last reported states in case visibility changes due to keyguard or IME updates.
     @VisibleForTesting
     boolean mHasSizeCompat;
+
+    @VisibleForTesting
     @CameraCompatControlState
-    private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
+    int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
 
     @VisibleForTesting
     boolean mShouldShowSizeCompatHint;
@@ -65,12 +67,14 @@
     @VisibleForTesting
     CompatUILayout mLayout;
 
-    CompatUIWindowManager(Context context, Configuration taskConfig,
-            SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback,
-            int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
+    CompatUIWindowManager(Context context, TaskInfo taskInfo,
+            SyncTransactionQueue syncQueue, CompatUICallback callback,
+            ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
             boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) {
-        super(context, taskConfig, syncQueue, taskId, taskListener, displayLayout);
+        super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
+        mHasSizeCompat = taskInfo.topActivityInSizeCompat;
+        mCameraCompatControlState = taskInfo.cameraCompatControlState;
         mShouldShowSizeCompatHint = !hasShownSizeCompatHint;
         mShouldShowCameraCompatHint = !hasShownCameraCompatHint;
     }
@@ -80,7 +84,6 @@
         return Z_ORDER;
     }
 
-
     @Override
     protected @Nullable View getLayout() {
         return mLayout;
@@ -96,16 +99,6 @@
         return mHasSizeCompat || shouldShowCameraControl();
     }
 
-    /**
-     * Updates the internal state with respect to {@code taskInfo} and calls {@link
-     * #createLayout(boolean)}.
-     */
-    void createLayout(boolean canShow, TaskInfo taskInfo) {
-        mHasSizeCompat = taskInfo.topActivityInSizeCompat;
-        mCameraCompatControlState = taskInfo.cameraCompatControlState;
-        createLayout(canShow);
-    }
-
     @Override
     protected View createLayout() {
         mLayout = inflateLayout();
@@ -127,19 +120,23 @@
     }
 
     @Override
-    public void updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+    public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
             boolean canShow) {
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
         mHasSizeCompat = taskInfo.topActivityInSizeCompat;
         mCameraCompatControlState = taskInfo.cameraCompatControlState;
 
-        super.updateCompatInfo(taskInfo, taskListener, canShow);
+        if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+            return false;
+        }
 
         if (prevHasSizeCompat != mHasSizeCompat
                 || prevCameraCompatControlState != mCameraCompatControlState) {
             updateVisibilityOfViews();
         }
+
+        return true;
     }
 
     /** Called when the restart button is clicked. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index b9a9db1..bdf9d51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -49,7 +49,7 @@
  *
  * <p>Holds view hierarchy of a root surface and helps to inflate and manage layout.
  */
-abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
+public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
 
     protected final SyncTransactionQueue mSyncQueue;
     protected final int mDisplayId;
@@ -75,15 +75,15 @@
     @Nullable
     protected SurfaceControl mLeash;
 
-    protected CompatUIWindowManagerAbstract(Context context, Configuration taskConfig,
-            SyncTransactionQueue syncQueue, int taskId,
-            ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout) {
-        super(taskConfig, null /* rootSurface */, null /* hostInputToken */);
+    protected CompatUIWindowManagerAbstract(Context context, TaskInfo taskInfo,
+            SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+            DisplayLayout displayLayout) {
+        super(taskInfo.configuration, null /* rootSurface */, null /* hostInputToken */);
         mContext = context;
         mSyncQueue = syncQueue;
-        mTaskConfig = taskConfig;
+        mTaskConfig = taskInfo.configuration;
         mDisplayId = mContext.getDisplayId();
-        mTaskId = taskId;
+        mTaskId = taskInfo.taskId;
         mTaskListener = taskListener;
         mDisplayLayout = displayLayout;
         mStableBounds = new Rect();
@@ -105,12 +105,18 @@
      * Inflates and inits the layout of this window manager on to the root surface if both {@code
      * canShow} and {@link #eligibleToShowLayout} are true.
      *
+     * <p>Doesn't do anything if layout is not eligible to be shown.
+     *
      * @param canShow whether the layout is allowed to be shown by the parent controller.
+     * @return whether the layout is eligible to be shown.
      */
-    void createLayout(boolean canShow) {
-        if (!canShow || !eligibleToShowLayout() || getLayout() != null) {
-            // Wait until layout should be visible.
-            return;
+    protected boolean createLayout(boolean canShow) {
+        if (!eligibleToShowLayout()) {
+            return false;
+        }
+        if (!canShow || getLayout() != null) {
+            // Wait until layout should be visible, or layout was already created.
+            return true;
         }
 
         if (mViewHost != null) {
@@ -123,6 +129,8 @@
         mViewHost.setView(createLayout(), getWindowLayoutParams());
 
         updateSurfacePosition();
+
+        return true;
     }
 
     /** Inflates and inits the layout of this window manager. */
@@ -174,9 +182,12 @@
     /**
      * Called when compat info changed.
      *
+     * <p>The window manager is released if the layout is no longer eligible to be shown.
+     *
      * @param canShow whether the layout is allowed to be shown by the parent controller.
+     * @return whether the layout is eligible to be shown.
      */
-    void updateCompatInfo(TaskInfo taskInfo,
+    protected boolean updateCompatInfo(TaskInfo taskInfo,
             ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
         final Configuration prevTaskConfig = mTaskConfig;
         final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
@@ -186,12 +197,16 @@
         // Update configuration.
         setConfiguration(mTaskConfig);
 
+        if (!eligibleToShowLayout()) {
+            release();
+            return false;
+        }
+
         View layout = getLayout();
         if (layout == null || prevTaskListener != taskListener) {
             // TaskListener changed, recreate the layout for new surface parent.
             release();
-            createLayout(canShow);
-            return;
+            return createLayout(canShow);
         }
 
         boolean boundsUpdated = !mTaskConfig.windowConfiguration.getBounds().equals(
@@ -207,6 +222,8 @@
             // Update layout for RTL.
             layout.setLayoutDirection(mTaskConfig.getLayoutDirection());
         }
+
+        return true;
     }
 
 
@@ -248,16 +265,16 @@
         mTaskListener.attachChildSurfaceToTask(mTaskId, b);
     }
 
-    int getDisplayId() {
+    public int getDisplayId() {
         return mDisplayId;
     }
 
-    int getTaskId() {
+    public int getTaskId() {
         return mTaskId;
     }
 
     /** Releases the surface control and tears down the view hierarchy. */
-    void release() {
+    public void release() {
         // Hiding before releasing to avoid flickering when transitioning to the Home screen.
         View layout = getLayout();
         if (layout != null) {
@@ -278,7 +295,7 @@
     }
 
     /** Re-layouts the view host and updates the surface position. */
-    void relayout() {
+    public void relayout() {
         if (mViewHost == null) {
             return;
         }
@@ -334,7 +351,7 @@
     }
 
     /** Gets the layout params. */
-    private WindowManager.LayoutParams getWindowLayoutParams() {
+    protected WindowManager.LayoutParams getWindowLayoutParams() {
         View layout = getLayout();
         if (layout == null) {
             return new WindowManager.LayoutParams();
@@ -345,7 +362,7 @@
     }
 
     /** Gets the layout params given the width and height of the layout. */
-    private WindowManager.LayoutParams getWindowLayoutParams(int width, int height) {
+    protected WindowManager.LayoutParams getWindowLayoutParams(int width, int height) {
         final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
                 // Cannot be wrap_content as this determines the actual window size
                 width, height,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
new file mode 100644
index 0000000..eff2602
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 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.compatui.letterboxedu;
+
+import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.IntProperty;
+import android.util.Log;
+import android.util.Property;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.animation.Animation;
+
+import com.android.internal.policy.TransitionAnimation;
+
+/**
+ * Controls the enter/exit animations of the letterbox education.
+ */
+// TODO(b/215316431): Add tests
+class LetterboxEduAnimationController {
+    private static final String TAG = "LetterboxEduAnimation";
+
+    private final TransitionAnimation mTransitionAnimation;
+    private final String mPackageName;
+    @AnyRes
+    private final int mAnimStyleResId;
+
+    @Nullable
+    private Animation mDialogAnimation;
+    @Nullable
+    private Animator mBackgroundDimAnimator;
+
+    LetterboxEduAnimationController(Context context) {
+        mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, TAG);
+        mAnimStyleResId = (new ContextThemeWrapper(context,
+                android.R.style.ThemeOverlay_Material_Dialog).getTheme()).obtainStyledAttributes(
+                com.android.internal.R.styleable.Window).getResourceId(
+                com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+        mPackageName = context.getPackageName();
+    }
+
+    /**
+     * Starts both background dim fade-in animation and the dialog enter animation.
+     */
+    void startEnterAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+        // Cancel any previous animation if it's still running.
+        cancelAnimation();
+
+        final View dialogContainer = layout.getDialogContainer();
+        mDialogAnimation = loadAnimation(WindowAnimation_windowEnterAnimation);
+        if (mDialogAnimation == null) {
+            endCallback.run();
+            return;
+        }
+        mDialogAnimation.setAnimationListener(getAnimationListener(
+                /* startCallback= */ () -> dialogContainer.setAlpha(1),
+                /* endCallback= */ () -> {
+                    mDialogAnimation = null;
+                    endCallback.run();
+                }));
+
+        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(),
+                /* endAlpha= */ LetterboxEduDialogLayout.BACKGROUND_DIM_ALPHA,
+                mDialogAnimation.getDuration());
+        mBackgroundDimAnimator.addListener(getDimAnimatorListener());
+
+        dialogContainer.startAnimation(mDialogAnimation);
+        mBackgroundDimAnimator.start();
+    }
+
+    /**
+     * Starts both the background dim fade-out animation and the dialog exit animation.
+     */
+    void startExitAnimation(@Nullable LetterboxEduDialogLayout layout, Runnable endCallback) {
+        // Cancel any previous animation if it's still running.
+        cancelAnimation();
+
+        if (layout == null) {
+            endCallback.run();
+            return;
+        }
+
+        final View dialogContainer = layout.getDialogContainer();
+        mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation);
+        if (mDialogAnimation == null) {
+            endCallback.run();
+            return;
+        }
+        mDialogAnimation.setAnimationListener(getAnimationListener(
+                /* startCallback= */ () -> {},
+                /* endCallback= */ () -> {
+                    dialogContainer.setAlpha(0);
+                    mDialogAnimation = null;
+                    endCallback.run();
+                }));
+
+        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(), /* endAlpha= */ 0,
+                mDialogAnimation.getDuration());
+        mBackgroundDimAnimator.addListener(getDimAnimatorListener());
+
+        dialogContainer.startAnimation(mDialogAnimation);
+        mBackgroundDimAnimator.start();
+    }
+
+    /**
+     * Cancels all animations and resets the state of the controller.
+     */
+    void cancelAnimation() {
+        if (mDialogAnimation != null) {
+            mDialogAnimation.cancel();
+            mDialogAnimation = null;
+        }
+        if (mBackgroundDimAnimator != null) {
+            mBackgroundDimAnimator.cancel();
+            mBackgroundDimAnimator = null;
+        }
+    }
+
+    private Animation loadAnimation(int animAttr) {
+        Animation animation = mTransitionAnimation.loadAnimationAttr(mPackageName, mAnimStyleResId,
+                animAttr, /* translucent= */ false);
+        if (animation == null) {
+            Log.e(TAG, "Failed to load animation " + animAttr);
+        }
+        return animation;
+    }
+
+    private Animation.AnimationListener getAnimationListener(Runnable startCallback,
+            Runnable endCallback) {
+        return new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+                startCallback.run();
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                endCallback.run();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {}
+        };
+    }
+
+    private AnimatorListenerAdapter getDimAnimatorListener() {
+        return new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mBackgroundDimAnimator = null;
+            }
+        };
+    }
+
+    private static Animator getAlphaAnimator(
+            Drawable drawable, int endAlpha, long duration) {
+        Animator animator = ObjectAnimator.ofInt(drawable, DRAWABLE_ALPHA, endAlpha);
+        animator.setDuration(duration);
+        return animator;
+    }
+
+    private static final Property<Drawable, Integer> DRAWABLE_ALPHA = new IntProperty<Drawable>(
+            "alpha") {
+        @Override
+        public void setValue(Drawable object, int value) {
+            object.setAlpha(value);
+        }
+
+        @Override
+        public Integer get(Drawable object) {
+            return object.getAlpha();
+        }
+    };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
index fa75d14..b22b829 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -17,7 +17,9 @@
 package com.android.wm.shell.compatui.letterboxedu;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.FrameLayout;
 
 import com.android.wm.shell.R;
@@ -33,7 +35,11 @@
 
     // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
     // 204 is simply 255 * 0.8.
-    private static final int BACKGROUND_DIM_ALPHA = 204;
+    static final int BACKGROUND_DIM_ALPHA = 204;
+
+    private LetterboxEduWindowManager mWindowManager;
+    private View mDialogContainer;
+    private Drawable mBackgroundDim;
 
     public LetterboxEduDialogLayout(Context context) {
         this(context, null);
@@ -52,6 +58,25 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
+    void inject(LetterboxEduWindowManager windowManager) {
+        mWindowManager = windowManager;
+    }
+
+    View getDialogContainer() {
+        return mDialogContainer;
+    }
+
+    Drawable getBackgroundDim() {
+        return mBackgroundDim;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        // Need to relayout after visibility changes since they affect size.
+        mWindowManager.relayout();
+    }
+
     /**
      * Register a callback for the dismiss button and background dim.
      *
@@ -64,12 +89,15 @@
         setOnClickListener(view -> callback.run());
         // We add a no-op on-click listener to the dialog container so that clicks on it won't
         // propagate to the listener of the layout (which represents the background dim).
-        findViewById(R.id.letterbox_education_dialog_container).setOnClickListener(view -> {});
+        mDialogContainer.setOnClickListener(view -> {});
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        getBackground().mutate().setAlpha(BACKGROUND_DIM_ALPHA);
+        mDialogContainer = findViewById(R.id.letterbox_education_dialog_container);
+        mBackgroundDim = getBackground().mutate();
+        // Set the alpha of the background dim to 0 for enter animation.
+        mBackgroundDim.setAlpha(0);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
new file mode 100644
index 0000000..d5aefb1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2022 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.compatui.letterboxedu;
+
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
+
+/**
+ * Window manager for the Letterbox Education.
+ */
+// TODO(b/215316431): Add tests
+public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
+
+    /**
+     * The Letterbox Education should be the topmost child of the Task in case there can be more
+     * than one child.
+     */
+    public static final int Z_ORDER = Integer.MAX_VALUE;
+
+    /**
+     * The name of the {@link SharedPreferences} that holds which user has seen the Letterbox
+     * Education for specific packages and which user has seen the full dialog for any package.
+     */
+    private static final String HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME =
+            "has_seen_letterbox_education";
+
+    /**
+     * The {@link SharedPreferences} instance for {@link #HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME}.
+     */
+    private final SharedPreferences mSharedPreferences;
+
+    private final LetterboxEduAnimationController mAnimationController;
+
+    // Remember the last reported state in case visibility changes due to keyguard or IME updates.
+    private boolean mEligibleForLetterboxEducation;
+
+    @Nullable
+    private LetterboxEduDialogLayout mLayout;
+
+    private final Runnable mOnDismissCallback;
+
+    public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
+            SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+            DisplayLayout displayLayout, Runnable onDismissCallback) {
+        super(context, taskInfo, syncQueue, taskListener, displayLayout);
+        mOnDismissCallback = onDismissCallback;
+        mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
+        mAnimationController = new LetterboxEduAnimationController(context);
+        mSharedPreferences = mContext.getSharedPreferences(HAS_SEEN_LETTERBOX_EDUCATION_PREF_NAME,
+                Context.MODE_PRIVATE);
+    }
+
+    @Override
+    protected int getZOrder() {
+        return Z_ORDER;
+    }
+
+    @Override
+    protected @Nullable View getLayout() {
+        return mLayout;
+    }
+
+    @Override
+    protected void removeLayout() {
+        mLayout = null;
+    }
+
+    @Override
+    protected boolean eligibleToShowLayout() {
+        // If the layout isn't null then it was previously showing, and we shouldn't check if the
+        // user has seen the letterbox education before.
+        return mEligibleForLetterboxEducation && (mLayout != null
+                || !getHasSeenLetterboxEducation());
+    }
+
+    @Override
+    protected View createLayout() {
+        setSeenLetterboxEducation();
+        mLayout = inflateLayout();
+        mLayout.inject(this);
+
+        mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
+                this::setDismissOnClickListener);
+
+        return mLayout;
+    }
+
+    private LetterboxEduDialogLayout inflateLayout() {
+        return (LetterboxEduDialogLayout) LayoutInflater.from(mContext).inflate(
+                R.layout.letterbox_education_dialog_layout, null);
+    }
+
+    private void setDismissOnClickListener() {
+        if (mLayout == null) {
+            return;
+        }
+        mLayout.setDismissOnClickListener(this::onDismiss);
+    }
+
+    private void onDismiss() {
+        mAnimationController.startExitAnimation(mLayout, () -> {
+            release();
+            mOnDismissCallback.run();
+        });
+    }
+
+    @Override
+    public void release() {
+        mAnimationController.cancelAnimation();
+        super.release();
+    }
+
+    @Override
+    public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+            boolean canShow) {
+        mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
+
+        return super.updateCompatInfo(taskInfo, taskListener, canShow);
+    }
+
+    @Override
+    protected void updateSurfacePosition(Rect taskBounds, Rect stableBounds) {
+        updateSurfacePosition(/* positionX= */ taskBounds.left, /* positionY= */ taskBounds.top);
+    }
+
+    @Override
+    protected WindowManager.LayoutParams getWindowLayoutParams() {
+        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
+        return getWindowLayoutParams(/* width= */ taskBounds.right - taskBounds.left,
+                /* height= */ taskBounds.bottom - taskBounds.top);
+    }
+
+    private boolean getHasSeenLetterboxEducation() {
+        return mSharedPreferences.getBoolean(getPrefKey(), /* default= */ false);
+    }
+
+    private void setSeenLetterboxEducation() {
+        mSharedPreferences.edit().putBoolean(getPrefKey(), true).apply();
+    }
+
+    private String getPrefKey() {
+        return String.valueOf(mContext.getUserId());
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index f49e80a..a244d14 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -2,3 +2,7 @@
 # includes OWNERS from parent directories
 natanieljr@google.com
 pablogamito@google.com
+
+lbill@google.com
+madym@google.com
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
index 87a61a8..23b51cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
@@ -16,15 +16,19 @@
   -->
 
 <resources>
-    <style name="CutoutDefault">
+    <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowBackground">@android:color/darker_gray</item>
+    </style>
+
+    <style name="CutoutDefault" parent="@style/DefaultTheme">
         <item name="android:windowLayoutInDisplayCutoutMode">default</item>
     </style>
 
-    <style name="CutoutShortEdges">
+    <style name="CutoutShortEdges" parent="@style/DefaultTheme">
         <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
     </style>
 
-    <style name="CutoutNever">
+    <style name="CutoutNever" parent="@style/DefaultTheme">
         <item name="android:windowLayoutInDisplayCutoutMode">never</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 185479b..169f03e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -32,6 +32,7 @@
 
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.LocusId;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -39,7 +40,6 @@
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.Log;
 import android.util.Pair;
 import android.view.WindowManager;
 
@@ -82,6 +82,7 @@
     private BubbleEntry mEntryC1;
     private BubbleEntry mEntryInterruptive;
     private BubbleEntry mEntryDismissed;
+    private BubbleEntry mEntryLocusId;
 
     private Bubble mBubbleA1;
     private Bubble mBubbleA2;
@@ -92,6 +93,7 @@
     private Bubble mBubbleC1;
     private Bubble mBubbleInterruptive;
     private Bubble mBubbleDismissed;
+    private Bubble mBubbleLocusId;
 
     private BubbleData mBubbleData;
     private TestableBubblePositioner mPositioner;
@@ -141,6 +143,10 @@
         mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null,
                 mMainExecutor);
 
+        mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null,
+                new LocusId("locusId1"));
+        mBubbleLocusId = new Bubble(mEntryLocusId, mSuppressionListener, null, mMainExecutor);
+
         mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener,
                 mMainExecutor);
         mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener,
@@ -939,6 +945,102 @@
         assertOrderChangedTo(mBubbleB3, mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2);
     }
 
+    /**
+     * There is one bubble in the stack. If a task matching the locusId becomes visible, suppress
+     * the bubble. If it is hidden, unsuppress the bubble.
+     */
+    @Test
+    public void test_onLocusVisibilityChanged_singleBubble() {
+        sendUpdatedEntryAtTime(mEntryLocusId, 1000);
+        mBubbleData.setListener(mListener);
+
+        // Suppress the bubble
+        mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), true /* visible */);
+        verifyUpdateReceived();
+        assertBubbleSuppressed(mBubbleLocusId);
+        assertOrderNotChanged();
+        assertBubbleListContains(/* empty list */);
+
+        // Unsuppress the bubble
+        mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), false /* visible */);
+        verifyUpdateReceived();
+        assertBubbleUnsuppressed(mBubbleLocusId);
+        assertOrderNotChanged();
+        assertBubbleListContains(mBubbleLocusId);
+    }
+
+    /**
+     * Bubble stack has multiple bubbles. Suppress bubble based on matching locusId. Suppressed
+     * bubble is at the top.
+     *
+     * When suppressed:
+     * - hide bubble
+     * - update order
+     * - update selection
+     *
+     * When unsuppressed:
+     * - show bubble
+     * - update order
+     * - update selection
+     */
+    @Test
+    public void test_onLocusVisibilityChanged_multipleBubbles_suppressTopBubble() {
+        sendUpdatedEntryAtTime(mEntryA1, 1000);
+        sendUpdatedEntryAtTime(mEntryA2, 2000);
+        sendUpdatedEntryAtTime(mEntryLocusId, 3000);
+        mBubbleData.setListener(mListener);
+
+        // Suppress bubble
+        mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), true /* visible */);
+        verifyUpdateReceived();
+        assertBubbleSuppressed(mBubbleLocusId);
+        assertSelectionChangedTo(mBubbleA2);
+        assertOrderChangedTo(mBubbleA2, mBubbleA1);
+
+        // Unsuppress bubble
+        mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), false /* visible */);
+        verifyUpdateReceived();
+        assertBubbleUnsuppressed(mBubbleLocusId);
+        assertSelectionChangedTo(mBubbleLocusId);
+        assertOrderChangedTo(mBubbleLocusId, mBubbleA2, mBubbleA1);
+    }
+
+    /**
+     * Bubble stack has multiple bubbles. Suppress bubble based on matching locusId. Suppressed
+     * bubble is not at the top.
+     *
+     * When suppressed:
+     * - hide suppressed bubble
+     * - do not update order
+     * - do not update selection
+     *
+     * When unsuppressed:
+     * - show bubble
+     * - do not update order
+     * - do not update selection
+     */
+    @Test
+    public void test_onLocusVisibilityChanged_multipleBubbles_suppressStackedBubble() {
+        sendUpdatedEntryAtTime(mEntryLocusId, 1000);
+        sendUpdatedEntryAtTime(mEntryA1, 2000);
+        sendUpdatedEntryAtTime(mEntryA2, 3000);
+        mBubbleData.setListener(mListener);
+
+        // Suppress bubble
+        mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), true /* visible */);
+        verifyUpdateReceived();
+        assertBubbleSuppressed(mBubbleLocusId);
+        assertSelectionNotChanged();
+        assertBubbleListContains(mBubbleA2, mBubbleA1);
+
+        // Unsuppress bubble
+        mBubbleData.onLocusVisibilityChanged(100, mEntryLocusId.getLocusId(), false /* visible */);
+        verifyUpdateReceived();
+        assertBubbleUnsuppressed(mBubbleLocusId);
+        assertSelectionNotChanged();
+        assertBubbleListContains(mBubbleA2, mBubbleA1, mBubbleLocusId);
+    }
+
     private void verifyUpdateReceived() {
         verify(mListener).applyUpdate(mUpdateCaptor.capture());
         reset(mListener);
@@ -995,9 +1097,29 @@
         assertThat(update.overflowBubbles).isEqualTo(bubbles);
     }
 
+    private void assertBubbleListContains(Bubble... bubbles) {
+        BubbleData.Update update = mUpdateCaptor.getValue();
+        assertWithMessage("bubbleList").that(update.bubbles).containsExactlyElementsIn(bubbles);
+    }
+
+    private void assertBubbleSuppressed(Bubble expected) {
+        BubbleData.Update update = mUpdateCaptor.getValue();
+        assertWithMessage("suppressedBubble").that(update.suppressedBubble).isEqualTo(expected);
+    }
+
+    private void assertBubbleUnsuppressed(Bubble expected) {
+        BubbleData.Update update = mUpdateCaptor.getValue();
+        assertWithMessage("unsuppressedBubble").that(update.unsuppressedBubble).isEqualTo(expected);
+    }
+
     private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
             NotificationListenerService.Ranking ranking) {
-        return createBubbleEntry(userId, notifKey, packageName, ranking, 1000);
+        return createBubbleEntry(userId, notifKey, packageName, ranking, 1000, null);
+    }
+
+    private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
+            NotificationListenerService.Ranking ranking, LocusId locusId) {
+        return createBubbleEntry(userId, notifKey, packageName, ranking, 1000, locusId);
     }
 
     private void setPostTime(BubbleEntry entry, long postTime) {
@@ -1010,15 +1132,18 @@
      * as a convenience to create a Notification w/BubbleMetadata.
      */
     private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
-            NotificationListenerService.Ranking ranking, long postTime) {
+            NotificationListenerService.Ranking ranking, long postTime,
+            LocusId locusId) {
         // BubbleMetadata
         Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder(
                 mExpandIntent, Icon.createWithResource("", 0))
                 .setDeleteIntent(mDeleteIntent)
+                .setSuppressableBubble(true)
                 .build();
         // Notification -> BubbleMetadata
         Notification notification = mock(Notification.class);
-        notification.setBubbleMetadata(bubbleMetadata);
+        when(notification.getBubbleMetadata()).thenReturn(bubbleMetadata);
+        when(notification.getLocusId()).thenReturn(locusId);
 
         // Notification -> extras
         notification.extras = new Bundle();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 741da3f..c5be485 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -16,15 +16,14 @@
 
 package com.android.wm.shell.compatui;
 
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 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.clearInvocations;
@@ -94,10 +93,12 @@
         doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
         doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
         doReturn(TASK_ID).when(mMockLayout).getTaskId();
+        doReturn(true).when(mMockLayout).createLayout(anyBoolean());
+        doReturn(true).when(mMockLayout).updateCompatInfo(any(), any(), anyBoolean());
         mController = new CompatUIController(mContext, mMockDisplayController,
                 mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
             @Override
-            CompatUIWindowManager createLayout(Context context, TaskInfo taskInfo,
+            CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
                 return mMockLayout;
             }
@@ -113,46 +114,79 @@
 
     @Test
     public void testOnCompatInfoChanged() {
-        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */,
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_HIDDEN);
 
-        // Verify that the compat controls are added with non-null size compat info.
+        // Verify that the compat controls are added with non-null task listener.
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mController).createLayout(any(), eq(taskInfo), eq(mMockTaskListener));
+        verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
 
-        // Verify that the compat controls are updated with non-null new size compat info.
-        taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */,
+        // Verify that the compat controls are updated with new size compat info.
+        clearInvocations(mMockLayout);
+        clearInvocations(mController);
+        taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, true /* canShow */);
+        verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ true);
 
-        // Verify that compat controls are removed with null compat info.
-        mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
-                null /* taskListener */);
-
-        verify(mMockLayout).release();
-
+        // Verify that compat controls are removed with null task listener.
         clearInvocations(mMockLayout);
         clearInvocations(mController);
-        // Verify that compat controls are removed with no size compat and dismissed camera state.
-        taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
-
-        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
-
-        verify(mController).createLayout(any(), eq(taskInfo), eq(mMockTaskListener));
-
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_DISMISSED),
-                null /* taskListener */);
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
+                /* taskListener= */ null);
 
         verify(mMockLayout).release();
     }
 
     @Test
+    public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
+        doReturn(false).when(mMockLayout).createLayout(anyBoolean());
+
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+
+        // Verify that the layout is created again.
+        clearInvocations(mMockLayout);
+        clearInvocations(mController);
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mMockLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+        verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+    }
+
+    @Test
+    public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
+        doReturn(false).when(mMockLayout).updateCompatInfo(any(), any(), anyBoolean());
+
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+
+        clearInvocations(mMockLayout);
+        clearInvocations(mController);
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ true);
+
+        // Verify that the layout is created again.
+        clearInvocations(mMockLayout);
+        clearInvocations(mController);
+        mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+        verify(mMockLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+        verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
+    }
+
+
+    @Test
     public void testOnDisplayAdded() {
         mController.onDisplayAdded(DISPLAY_ID);
         mController.onDisplayAdded(DISPLAY_ID + 1);
@@ -165,7 +199,7 @@
     public void testOnDisplayRemoved() {
         mController.onDisplayAdded(DISPLAY_ID);
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN),
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
                 mMockTaskListener);
 
         mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -183,7 +217,7 @@
     @Test
     public void testOnDisplayConfigurationChanged() {
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
 
@@ -198,7 +232,7 @@
     public void testInsetsChanged() {
         mController.onDisplayAdded(DISPLAY_ID);
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
         InsetsState insetsState = new InsetsState();
         InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
         insetsSource.setFrame(0, 0, 1000, 1000);
@@ -219,22 +253,22 @@
     @Test
     public void testChangeButtonVisibilityOnImeShowHide() {
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after IME is showing.
-        mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+        mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
 
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while IME is showing.
-        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */,
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_HIDDEN);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, false /* canShow */);
+        verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ false);
 
         // Verify button is shown after IME is hidden.
-        mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+        mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
 
         verify(mMockLayout).updateVisibility(true);
     }
@@ -242,7 +276,7 @@
     @Test
     public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
         // Verify that the restart button is hidden after keyguard becomes occluded.
         mController.onKeyguardOccludedChanged(true);
@@ -250,11 +284,11 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while keyguard is occluded.
-        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, true /* hasSizeCompat */,
+        TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_HIDDEN);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, false /* canShow */);
+        verify(mMockLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ false);
 
         // Verify button is shown after keyguard becomes not occluded.
         mController.onKeyguardOccludedChanged(false);
@@ -265,9 +299,9 @@
     @Test
     public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+        mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
         mController.onKeyguardOccludedChanged(true);
 
         verify(mMockLayout, times(2)).updateVisibility(false);
@@ -280,7 +314,7 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button is shown after IME is not showing.
-        mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+        mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
 
         verify(mMockLayout).updateVisibility(true);
     }
@@ -288,9 +322,9 @@
     @Test
     public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
-                true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+                /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
 
-        mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+        mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
         mController.onKeyguardOccludedChanged(true);
 
         verify(mMockLayout, times(2)).updateVisibility(false);
@@ -298,7 +332,7 @@
         clearInvocations(mMockLayout);
 
         // Verify button remains hidden after IME is hidden since keyguard is occluded.
-        mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+        mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
 
         verify(mMockLayout).updateVisibility(false);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 2117817..352805b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -30,7 +30,6 @@
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.app.TaskInfo.CameraCompatControlState;
-import android.content.res.Configuration;
 import android.testing.AndroidTestingRunner;
 import android.view.LayoutInflater;
 import android.view.SurfaceControlViewHost;
@@ -69,29 +68,31 @@
     @Mock private SurfaceControlViewHost mViewHost;
 
     private CompatUIWindowManager mWindowManager;
-    private CompatUILayout mCompatUILayout;
+    private CompatUILayout mLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
-                mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
-                false /* hasShownSizeCompatHint */, false /* hasShownCameraCompatHint */);
+        mWindowManager = new CompatUIWindowManager(mContext,
+                createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
+                mSyncTransactionQueue, mCallback, mTaskListener,
+                new DisplayLayout(), /* hasShownSizeCompatHint= */ false,
+                /* hasShownCameraCompatHint= */ false);
 
-        mCompatUILayout = (CompatUILayout)
+        mLayout = (CompatUILayout)
                 LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
-        mCompatUILayout.inject(mWindowManager);
+        mLayout.inject(mWindowManager);
 
         spyOn(mWindowManager);
-        spyOn(mCompatUILayout);
+        spyOn(mLayout);
         doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
-        doReturn(mCompatUILayout).when(mWindowManager).inflateLayout();
+        doReturn(mLayout).when(mWindowManager).inflateLayout();
     }
 
     @Test
     public void testOnClickForRestartButton() {
-        final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
+        final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
         button.performClick();
 
         verify(mWindowManager).onRestartButtonClicked();
@@ -102,7 +103,7 @@
     public void testOnLongClickForRestartButton() {
         doNothing().when(mWindowManager).onRestartButtonLongClicked();
 
-        final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
+        final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
         button.performLongClick();
 
         verify(mWindowManager).onRestartButtonLongClicked();
@@ -110,20 +111,20 @@
 
     @Test
     public void testOnClickForSizeCompatHint() {
-        mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_HIDDEN));
-        final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint);
+        mWindowManager.mHasSizeCompat = true;
+        mWindowManager.createLayout(/* canShow= */ true);
+        final LinearLayout sizeCompatHint = mLayout.findViewById(R.id.size_compat_hint);
         sizeCompatHint.performClick();
 
-        verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false);
+        verify(mLayout).setSizeCompatHintVisibility(/* show= */ false);
     }
 
     @Test
     public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
-        mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED));
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
+        mWindowManager.createLayout(/* canShow= */ true);
         final ImageButton button =
-                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+                mLayout.findViewById(R.id.camera_compat_treatment_button);
         button.performClick();
 
         verify(mWindowManager).onCameraTreatmentButtonClicked();
@@ -138,10 +139,10 @@
 
     @Test
     public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
-        mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        mWindowManager.createLayout(/* canShow= */ true);
         final ImageButton button =
-                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+                mLayout.findViewById(R.id.camera_compat_treatment_button);
         button.performClick();
 
         verify(mWindowManager).onCameraTreatmentButtonClicked();
@@ -156,24 +157,24 @@
 
     @Test
     public void testOnCameraDismissButtonClicked() {
-        mWindowManager.createLayout(true /* show */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        mWindowManager.createLayout(/* canShow= */ true);
         final ImageButton button =
-                mCompatUILayout.findViewById(R.id.camera_compat_dismiss_button);
+                mLayout.findViewById(R.id.camera_compat_dismiss_button);
         button.performClick();
 
         verify(mWindowManager).onCameraDismissButtonClicked();
         verify(mCallback).onCameraControlStateUpdated(
                 TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
-        verify(mCompatUILayout).setCameraControlVisibility(/* show */ false);
+        verify(mLayout).setCameraControlVisibility(/* show */ false);
     }
 
     @Test
-    public void testOnLongClickForCameraTreatementButton() {
+    public void testOnLongClickForCameraTreatmentButton() {
         doNothing().when(mWindowManager).onCameraButtonLongClicked();
 
         final ImageButton button =
-                mCompatUILayout.findViewById(R.id.camera_compat_treatment_button);
+                mLayout.findViewById(R.id.camera_compat_treatment_button);
         button.performLongClick();
 
         verify(mWindowManager).onCameraButtonLongClicked();
@@ -183,7 +184,7 @@
     public void testOnLongClickForCameraDismissButton() {
         doNothing().when(mWindowManager).onCameraButtonLongClicked();
 
-        final ImageButton button = mCompatUILayout.findViewById(R.id.camera_compat_dismiss_button);
+        final ImageButton button = mLayout.findViewById(R.id.camera_compat_dismiss_button);
         button.performLongClick();
 
         verify(mWindowManager).onCameraButtonLongClicked();
@@ -191,17 +192,18 @@
 
     @Test
     public void testOnClickForCameraCompatHint() {
-        mWindowManager.createLayout(true /* show */, createTaskInfo(false /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
-        final LinearLayout hint = mCompatUILayout.findViewById(R.id.camera_compat_hint);
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        mWindowManager.createLayout(/* canShow= */ true);
+        final LinearLayout hint = mLayout.findViewById(R.id.camera_compat_hint);
         hint.performClick();
 
-        verify(mCompatUILayout).setCameraCompatHintVisibility(/* show= */ false);
+        verify(mLayout).setCameraCompatHintVisibility(/* show= */ false);
     }
 
     private static TaskInfo createTaskInfo(boolean hasSizeCompat,
             @CameraCompatControlState int cameraCompatControlState) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.taskId = TASK_ID;
         taskInfo.topActivityInSizeCompat = hasSizeCompat;
         taskInfo.cameraCompatControlState = cameraCompatControlState;
         return taskInfo;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index de882ea..f9cfd12 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -26,18 +26,16 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 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 android.app.ActivityManager;
 import android.app.TaskInfo;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.view.DisplayInfo;
@@ -75,7 +73,7 @@
     @Mock private SyncTransactionQueue mSyncTransactionQueue;
     @Mock private CompatUIController.CompatUICallback mCallback;
     @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
-    @Mock private CompatUILayout mCompatUILayout;
+    @Mock private CompatUILayout mLayout;
     @Mock private SurfaceControlViewHost mViewHost;
 
     private CompatUIWindowManager mWindowManager;
@@ -84,47 +82,97 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
-                mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
-                false /* hasShownSizeCompatHint */, false /* hasShownSizeCompatHint */);
+        mWindowManager = new CompatUIWindowManager(mContext,
+                createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
+                mSyncTransactionQueue, mCallback, mTaskListener,
+                new DisplayLayout(), /* hasShownSizeCompatHint= */ false,
+                /* hasShownCameraCompatHint= */ false);
 
         spyOn(mWindowManager);
-        doReturn(mCompatUILayout).when(mWindowManager).inflateLayout();
+        doReturn(mLayout).when(mWindowManager).inflateLayout();
         doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
     }
 
     @Test
     public void testCreateSizeCompatButton() {
-        // Not create layout if show is false.
-        mWindowManager.createLayout(false /* canShow */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_HIDDEN));
+        // Doesn't create layout if show is false.
+        mWindowManager.mHasSizeCompat = true;
+        assertTrue(mWindowManager.createLayout(/* canShow= */ false));
 
         verify(mWindowManager, never()).inflateLayout();
 
-        // Not create hint popup.
+        // Doesn't create hint popup.
         mWindowManager.mShouldShowSizeCompatHint = false;
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_HIDDEN));
+        assertTrue(mWindowManager.createLayout(/* canShow= */ true));
 
         verify(mWindowManager).inflateLayout();
-        verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */);
+        verify(mLayout).setRestartButtonVisibility(/* show= */ true);
+        verify(mLayout, never()).setSizeCompatHintVisibility(/* show= */ true);
 
-        // Create hint popup.
+        // Creates hint popup.
+        clearInvocations(mWindowManager);
+        clearInvocations(mLayout);
         mWindowManager.release();
         mWindowManager.mShouldShowSizeCompatHint = true;
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_HIDDEN));
+        assertTrue(mWindowManager.createLayout(/* canShow= */ true));
 
-        verify(mWindowManager, times(2)).inflateLayout();
-        assertNotNull(mCompatUILayout);
-        verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
+        verify(mWindowManager).inflateLayout();
+        assertNotNull(mLayout);
+        verify(mLayout).setRestartButtonVisibility(/* show= */ true);
+        verify(mLayout).setSizeCompatHintVisibility(/* show= */ true);
         assertFalse(mWindowManager.mShouldShowSizeCompatHint);
+
+        // Returns false and doesn't create layout if has Size Compat is false.
+        clearInvocations(mWindowManager);
+        mWindowManager.release();
+        mWindowManager.mHasSizeCompat = false;
+        assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
+        verify(mWindowManager, never()).inflateLayout();
+    }
+
+    @Test
+    public void testCreateCameraCompatControl() {
+        // Doesn't create layout if show is false.
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        assertTrue(mWindowManager.createLayout(/* canShow= */ false));
+
+        verify(mWindowManager, never()).inflateLayout();
+
+        // Doesn't create hint popup.
+        mWindowManager.mShouldShowCameraCompatHint = false;
+        assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+        verify(mWindowManager).inflateLayout();
+        verify(mLayout).setCameraControlVisibility(/* show= */ true);
+        verify(mLayout, never()).setCameraCompatHintVisibility(/* show= */ true);
+
+        // Creates hint popup.
+        clearInvocations(mWindowManager);
+        clearInvocations(mLayout);
+        mWindowManager.release();
+        mWindowManager.mShouldShowCameraCompatHint = true;
+        assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+        verify(mWindowManager).inflateLayout();
+        assertNotNull(mLayout);
+        verify(mLayout).setCameraControlVisibility(/* show= */ true);
+        verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
+        assertFalse(mWindowManager.mShouldShowCameraCompatHint);
+
+        // Returns false and doesn't create layout if Camera Compat state is hidden
+        clearInvocations(mWindowManager);
+        mWindowManager.release();
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
+        assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
+        verify(mWindowManager, never()).inflateLayout();
     }
 
     @Test
     public void testRelease() {
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_HIDDEN));
+        mWindowManager.mHasSizeCompat = true;
+        mWindowManager.createLayout(/* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
 
@@ -135,12 +183,13 @@
 
     @Test
     public void testUpdateCompatInfo() {
-        TaskInfo taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
-        mWindowManager.createLayout(true /* canShow */, taskInfo);
+        mWindowManager.mHasSizeCompat = true;
+        mWindowManager.createLayout(/* canShow= */ true);
 
         // No diff
         clearInvocations(mWindowManager);
-        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, true /* canShow */);
+        TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
 
         verify(mWindowManager, never()).updateSurfacePosition();
         verify(mWindowManager, never()).release();
@@ -150,84 +199,98 @@
         clearInvocations(mWindowManager);
         final ShellTaskOrganizer.TaskListener newTaskListener = mock(
                 ShellTaskOrganizer.TaskListener.class);
-        mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
 
         verify(mWindowManager).release();
-        verify(mWindowManager).createLayout(true);
-
-        // Change in Size Compat to false, hides restart button.
-        clearInvocations(mWindowManager);
-        taskInfo = createTaskInfo(false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
-        mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */);
-
-        verify(mCompatUILayout).setRestartButtonVisibility(/* show */ false);
-
-        // Change in Size Compat to true, shows restart button.
-        clearInvocations(mWindowManager);
-        clearInvocations(mCompatUILayout);
-        taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
-        mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */);
-
-        verify(mCompatUILayout).setRestartButtonVisibility(/* show */ true);
+        verify(mWindowManager).createLayout(/* canShow= */ true);
 
         // Change Camera Compat state, show a control.
         clearInvocations(mWindowManager);
-        clearInvocations(mCompatUILayout);
-        taskInfo = createTaskInfo(true /* hasSizeCompat */,
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
-        mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
 
-        verify(mCompatUILayout).setCameraControlVisibility(/* show */ true);
-        verify(mCompatUILayout).updateCameraTreatmentButton(
+        verify(mLayout).setCameraControlVisibility(/* show= */ true);
+        verify(mLayout).updateCameraTreatmentButton(
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
 
         // Change Camera Compat state, update a control.
         clearInvocations(mWindowManager);
-        clearInvocations(mCompatUILayout);
-        taskInfo = createTaskInfo(true /* hasSizeCompat */,
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
-        mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
 
-        verify(mCompatUILayout).setCameraControlVisibility(/* show */ true);
-        verify(mCompatUILayout).updateCameraTreatmentButton(
+        verify(mLayout).setCameraControlVisibility(/* show= */ true);
+        verify(mLayout).updateCameraTreatmentButton(
                 CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
 
-        // Change Camera Compat state to hidden, hide a control.
+        // Change has Size Compat to false, hides restart button.
         clearInvocations(mWindowManager);
-        clearInvocations(mCompatUILayout);
-        taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
-        mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */);
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ false,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
 
-        verify(mCompatUILayout).setCameraControlVisibility(/* show */ false);
+        verify(mLayout).setRestartButtonVisibility(/* show= */ false);
+
+        // Change has Size Compat to true, shows restart button.
+        clearInvocations(mWindowManager);
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
+                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+        verify(mLayout).setRestartButtonVisibility(/* show= */ true);
+
+        // Change Camera Compat state to dismissed, hide a control.
+        clearInvocations(mWindowManager);
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_DISMISSED);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+        verify(mLayout).setCameraControlVisibility(/* show= */ false);
 
         // Change task bounds, update position.
         clearInvocations(mWindowManager);
-        taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
         taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
-        mWindowManager.updateCompatInfo(taskInfo, newTaskListener, true /* canShow */);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
 
         verify(mWindowManager).updateSurfacePosition();
+
+        // Change has Size Compat to false, release layout.
+        clearInvocations(mWindowManager);
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+        assertFalse(
+                mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+        verify(mWindowManager).release();
     }
 
     @Test
     public void testUpdateCompatInfoLayoutNotInflatedYet() {
-        TaskInfo taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
-        mWindowManager.createLayout(false /* canShow */, taskInfo);
+        mWindowManager.mHasSizeCompat = true;
+        mWindowManager.createLayout(/* canShow= */ false);
 
         verify(mWindowManager, never()).inflateLayout();
 
         // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be
         // inflated
         clearInvocations(mWindowManager);
-        taskInfo = createTaskInfo(false /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
-        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, true /* canShow */);
+        TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ false,
+                CAMERA_COMPAT_CONTROL_HIDDEN);
+        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager, never()).inflateLayout();
 
         // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
         clearInvocations(mWindowManager);
-        taskInfo = createTaskInfo(true /* hasSizeCompat */, CAMERA_COMPAT_CONTROL_HIDDEN);
-        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, true /* canShow */);
+        taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
     }
@@ -278,24 +341,24 @@
         // Create button if it is not created.
         mWindowManager.mLayout = null;
         mWindowManager.mHasSizeCompat = true;
-        mWindowManager.updateVisibility(true /* canShow */);
+        mWindowManager.updateVisibility(/* canShow= */ true);
 
-        verify(mWindowManager).createLayout(true /* canShow */);
+        verify(mWindowManager).createLayout(/* canShow= */ true);
 
         // Hide button.
         clearInvocations(mWindowManager);
-        doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility();
-        mWindowManager.updateVisibility(false /* canShow */);
+        doReturn(View.VISIBLE).when(mLayout).getVisibility();
+        mWindowManager.updateVisibility(/* canShow= */ false);
 
-        verify(mWindowManager, never()).createLayout(anyBoolean(), any());
-        verify(mCompatUILayout).setVisibility(View.GONE);
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mLayout).setVisibility(View.GONE);
 
         // Show button.
-        doReturn(View.GONE).when(mCompatUILayout).getVisibility();
-        mWindowManager.updateVisibility(true /* canShow */);
+        doReturn(View.GONE).when(mLayout).getVisibility();
+        mWindowManager.updateVisibility(/* canShow= */ true);
 
-        verify(mWindowManager, never()).createLayout(anyBoolean(), any());
-        verify(mCompatUILayout).setVisibility(View.VISIBLE);
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mLayout).setVisibility(View.VISIBLE);
     }
 
     @Test
@@ -308,32 +371,32 @@
 
     @Test
     public void testOnCameraDismissButtonClicked() {
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
-        clearInvocations(mCompatUILayout);
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        mWindowManager.createLayout(/* canShow= */ true);
+        clearInvocations(mLayout);
         mWindowManager.onCameraDismissButtonClicked();
 
         verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
-        verify(mCompatUILayout).setCameraControlVisibility(/* show= */ false);
+        verify(mLayout).setCameraControlVisibility(/* show= */ false);
     }
 
     @Test
     public void testOnCameraTreatmentButtonClicked() {
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
-        clearInvocations(mCompatUILayout);
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+        mWindowManager.createLayout(/* canShow= */ true);
+        clearInvocations(mLayout);
         mWindowManager.onCameraTreatmentButtonClicked();
 
         verify(mCallback).onCameraControlStateUpdated(
                 TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
-        verify(mCompatUILayout).updateCameraTreatmentButton(
+        verify(mLayout).updateCameraTreatmentButton(
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
 
         mWindowManager.onCameraTreatmentButtonClicked();
 
         verify(mCallback).onCameraControlStateUpdated(
                 TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
-        verify(mCompatUILayout).updateCameraTreatmentButton(
+        verify(mLayout).updateCameraTreatmentButton(
                 CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
     }
 
@@ -347,65 +410,37 @@
     @Test
     public void testOnRestartButtonLongClicked_showHint() {
        // Not create hint popup.
+        mWindowManager.mHasSizeCompat = true;
         mWindowManager.mShouldShowSizeCompatHint = false;
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(true /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_HIDDEN));
+        mWindowManager.createLayout(/* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
-        verify(mCompatUILayout, never()).setSizeCompatHintVisibility(true /* show */);
+        verify(mLayout, never()).setSizeCompatHintVisibility(/* show= */ true);
 
         mWindowManager.onRestartButtonLongClicked();
 
-        verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
+        verify(mLayout).setSizeCompatHintVisibility(/* show= */ true);
     }
 
     @Test
     public void testOnCamerControlLongClicked_showHint() {
        // Not create hint popup.
+        mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
         mWindowManager.mShouldShowCameraCompatHint = false;
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(false /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
+        mWindowManager.createLayout(/* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
-        verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */);
+        verify(mLayout, never()).setCameraCompatHintVisibility(/* show= */ true);
 
         mWindowManager.onCameraButtonLongClicked();
 
-        verify(mCompatUILayout).setCameraCompatHintVisibility(true /* show */);
-    }
-
-    @Test
-    public void testCreateCameraCompatControl() {
-        // Not create layout if show is false.
-        mWindowManager.createLayout(false /* canShow */, createTaskInfo(false /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
-
-        verify(mWindowManager, never()).inflateLayout();
-
-        // Not create hint popup.
-        mWindowManager.mShouldShowCameraCompatHint = false;
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(false /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
-
-        verify(mWindowManager).inflateLayout();
-        verify(mCompatUILayout, never()).setCameraCompatHintVisibility(true /* show */);
-        verify(mCompatUILayout).setCameraControlVisibility(true /* show */);
-
-        // Create hint popup.
-        mWindowManager.release();
-        mWindowManager.mShouldShowCameraCompatHint = true;
-        mWindowManager.createLayout(true /* canShow */, createTaskInfo(false /* hasSizeCompat */,
-                CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED));
-
-        verify(mWindowManager, times(2)).inflateLayout();
-        assertNotNull(mCompatUILayout);
-        verify(mCompatUILayout, times(2)).setCameraControlVisibility(true /* show */);
-        assertFalse(mWindowManager.mShouldShowCameraCompatHint);
+        verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
     }
 
     private static TaskInfo createTaskInfo(boolean hasSizeCompat,
             @TaskInfo.CameraCompatControlState int cameraCompatControlState) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.taskId = TASK_ID;
         taskInfo.topActivityInSizeCompat = hasSizeCompat;
         taskInfo.cameraCompatControlState = cameraCompatControlState;
         return taskInfo;
diff --git a/libs/hwui/effects/StretchEffect.cpp b/libs/hwui/effects/StretchEffect.cpp
index 8cb4515..2757c39 100644
--- a/libs/hwui/effects/StretchEffect.cpp
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -227,7 +227,7 @@
     mBuilder->uniform("viewportWidth").set(&width, 1);
     mBuilder->uniform("viewportHeight").set(&height, 1);
 
-    auto result = mBuilder->makeShader(nullptr, false);
+    auto result = mBuilder->makeShader();
     mBuilder->child(CONTENT_TEXTURE) = nullptr;
     return result;
 }
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 899c7d4..0bbd8a8 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -264,7 +264,7 @@
 static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderBuilder, jlong matrixPtr) {
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
     const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
-    sk_sp<SkShader> shader = builder->makeShader(matrix, false);
+    sk_sp<SkShader> shader = builder->makeShader(matrix);
     ThrowIAE_IfNull(env, shader);
     return reinterpret_cast<jlong>(shader.release());
 }
diff --git a/libs/hwui/pipeline/skia/AnimatedDrawables.h b/libs/hwui/pipeline/skia/AnimatedDrawables.h
index d173782..9cf93e6 100644
--- a/libs/hwui/pipeline/skia/AnimatedDrawables.h
+++ b/libs/hwui/pipeline/skia/AnimatedDrawables.h
@@ -110,7 +110,7 @@
         const float rotation3 = turbulencePhase * PI_ROTATE_RIGHT + 2.75 * PI;
         setUniform2f(effectBuilder, "in_tRotation3", cos(rotation3), sin(rotation3));
 
-        params.paint->value.setShader(effectBuilder.makeShader(nullptr, false));
+        params.paint->value.setShader(effectBuilder.makeShader());
         canvas->drawCircle(params.x->value, params.y->value, params.radius->value,
                            params.paint->value);
     }
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 7c57bd5..2fba13c 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -98,7 +98,7 @@
         effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
     }
 
-    return effectBuilder.makeShader(nullptr, false);
+    return effectBuilder.makeShader();
 }
 
 static bool isHdrDataspace(ui::Dataspace dataspace) {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 0caf76a..951ee21 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -279,8 +279,8 @@
         nsecs_t queueDuration;
     };
 
-    // Need at least 4 because we do quad buffer. Add a 5th for good measure.
-    RingBuffer<SwapHistory, 5> mSwapHistory;
+    // Need at least 4 because we do quad buffer. Add a few more for good measure.
+    RingBuffer<SwapHistory, 7> mSwapHistory;
     // Frame numbers start at 1, 0 means uninitialized
     uint64_t mFrameNumber = 0;
     int64_t mDamageId = 0;
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index afe002e..809ee23 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -151,7 +151,7 @@
         }
 
         /**
-         * Sets the unique id of the provider info.
+         * Sets the package name and unique id of the provider info.
          * <p>
          * The unique id is automatically set by
          * {@link com.android.server.media.MediaRouterService} and used to identify providers.
@@ -160,7 +160,7 @@
          * @hide
          */
         @NonNull
-        public Builder setUniqueId(@Nullable String uniqueId) {
+        public Builder setUniqueId(@Nullable String packageName, @Nullable String uniqueId) {
             if (TextUtils.equals(mUniqueId, uniqueId)) {
                 return this;
             }
@@ -169,6 +169,7 @@
             final ArrayMap<String, MediaRoute2Info> newRoutes = new ArrayMap<>();
             for (Map.Entry<String, MediaRoute2Info> entry : mRoutes.entrySet()) {
                 MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
+                        .setPackageName(packageName)
                         .setProviderId(mUniqueId)
                         .build();
                 newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId);
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 13c1498..7d57734 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -60,21 +60,21 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
- * MediaRouter allows applications to control the routing of media channels
+ * This API is not recommended for new applications. Use the
+ * <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/mediarouter/media/package-summary.html">Media Router
+ * Library</a> for consistent behavior across all devices.
+ *
+ * <p>MediaRouter allows applications to control the routing of media channels
  * and streams from the current device to external speakers and destination devices.
  *
  * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
  * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
  * Context.MEDIA_ROUTER_SERVICE}.
  *
- * <p>The media router API is not thread-safe; all interactions with it must be
- * done from the main thread of the process.</p>
- *
- * <p>
- * We recommend using {@link android.media.MediaRouter2} APIs for new applications.
- * </p>
+ * <p>This API is not thread-safe; all interactions with it must be done from the main thread of the
+ * process.
  */
-//TODO: Link androidx.media2.MediaRouter when we are ready.
 @SystemService(Context.MEDIA_ROUTER_SERVICE)
 public class MediaRouter {
     private static final String TAG = "MediaRouter";
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b485eb5..311476c 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -52,13 +52,13 @@
 import java.util.stream.Collectors;
 
 /**
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
-  <a href="{@docRoot}reference/androidx/mediarouter/media/package-summary.html">Media Router
+ * This API is not generally intended for third party application developers. Use the
+ * <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
+ * <a href="{@docRoot}reference/androidx/mediarouter/media/package-summary.html">Media Router
  * Library</a> for consistent behavior across all devices.
  *
- * Media Router 2 allows applications to control the routing of media channels
- * and streams from the current device to remote speakers and devices.
+ * <p>MediaRouter2 allows applications to control the routing of media channels and streams from
+ * the current device to remote speakers and devices.
  */
 // TODO(b/157873330): Add method names at the beginning of log messages. (e.g. selectRoute)
 //       Not only MediaRouter2, but also to service / manager / provider.
@@ -478,13 +478,8 @@
             if (mShouldUpdateRoutes) {
                 mShouldUpdateRoutes = false;
 
-                List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
-                for (MediaRoute2Info route : mRoutes.values()) {
-                    if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
-                        filteredRoutes.add(route);
-                    }
-                }
-                mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
+                mFilteredRoutes = Collections.unmodifiableList(
+                        filterRoutes(List.copyOf(mRoutes.values()), mDiscoveryPreference));
             }
         }
         return mFilteredRoutes;
@@ -1087,16 +1082,17 @@
 
         List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
         for (MediaRoute2Info route : getSortedRoutes(routes, discoveryPreference)) {
-            if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
-                    || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+            if (!route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
                 continue;
             }
             if (!discoveryPreference.getAllowedPackages().isEmpty()
-                    && !discoveryPreference.getAllowedPackages().contains(route.getPackageName())) {
+                    && (route.getPackageName() == null
+                    || !discoveryPreference.getAllowedPackages()
+                            .contains(route.getPackageName()))) {
                 continue;
             }
             if (discoveryPreference.shouldRemoveDuplicates()) {
-                if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
+                if (!Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
                     continue;
                 }
                 deduplicationIdSet.addAll(route.getDeduplicationIds());
@@ -2087,19 +2083,17 @@
         }
 
         @Override
-        public void onPreferredFeaturesChanged(@NonNull String packageName,
-                @NonNull List<String> preferredFeatures) {
+        public void onDiscoveryPreferenceChanged(@NonNull String packageName,
+                @NonNull RouteDiscoveryPreference preference) {
             if (!TextUtils.equals(mClientPackageName, packageName)) {
                 return;
             }
 
             synchronized (mLock) {
-                mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
-                        preferredFeatures, true).build();
+                mDiscoveryPreference = preference;
             }
-
             updateAllRoutesFromManager();
-            notifyPreferredFeaturesChanged(preferredFeatures);
+            notifyPreferredFeaturesChanged(preference.getPreferredFeatures());
         }
 
         @Override
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 8635c0e..071667a 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -254,7 +254,7 @@
     @NonNull
     public List<MediaRoute2Info> getAvailableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
         return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/true,
-                null);
+                /*additionalFilter=*/null);
     }
 
     /**
@@ -315,20 +315,20 @@
                 routes.add(route);
                 continue;
             }
-            if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures())
-                    || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
+            if (!route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) {
                 continue;
             }
             if (!discoveryPreference.getAllowedPackages().isEmpty()
-                    && !discoveryPreference.getAllowedPackages()
-                    .contains(route.getPackageName())) {
+                    && (route.getPackageName() == null
+                    || !discoveryPreference.getAllowedPackages()
+                    .contains(route.getPackageName()))) {
                 continue;
             }
             if (additionalFilter != null && !additionalFilter.test(route)) {
                 continue;
             }
             if (discoveryPreference.shouldRemoveDuplicates()) {
-                if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
+                if (!Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) {
                     continue;
                 }
                 deduplicationIdSet.addAll(route.getDeduplicationIds());
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index e609226..0ba36fe 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -24,7 +24,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -65,8 +64,6 @@
     @NonNull
     private final List<String> mPreferredFeatures;
     @NonNull
-    private final List<String> mRequiredFeatures;
-    @NonNull
     private final List<String> mPackageOrder;
     @NonNull
     private final List<String> mAllowedPackages;
@@ -85,7 +82,6 @@
 
     RouteDiscoveryPreference(@NonNull Builder builder) {
         mPreferredFeatures = builder.mPreferredFeatures;
-        mRequiredFeatures = builder.mRequiredFeatures;
         mPackageOrder = builder.mPackageOrder;
         mAllowedPackages = builder.mAllowedPackages;
         mShouldPerformActiveScan = builder.mActiveScan;
@@ -94,7 +90,6 @@
 
     RouteDiscoveryPreference(@NonNull Parcel in) {
         mPreferredFeatures = in.createStringArrayList();
-        mRequiredFeatures = in.createStringArrayList();
         mPackageOrder = in.createStringArrayList();
         mAllowedPackages = in.createStringArrayList();
         mShouldPerformActiveScan = in.readBoolean();
@@ -109,8 +104,6 @@
      * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
      * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
      * </p>
-     *
-     * @see #getRequiredFeatures()
      */
     @NonNull
     public List<String> getPreferredFeatures() {
@@ -118,21 +111,6 @@
     }
 
     /**
-     * Gets the required features of routes that media router would like to discover.
-     * <p>
-     * Routes that have all the required features will be discovered.
-     * They may include predefined features such as
-     * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO},
-     * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider.
-     *
-     * @see #getPreferredFeatures()
-     */
-    @NonNull
-    public List<String> getRequiredFeatures() {
-        return mRequiredFeatures;
-    }
-
-    /**
      * Gets the ordered list of package names used to remove duplicate routes.
      * <p>
      * Duplicate route removal is enabled if the returned list is non-empty. Routes are deduplicated
@@ -193,7 +171,6 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStringList(mPreferredFeatures);
-        dest.writeStringList(mRequiredFeatures);
         dest.writeStringList(mPackageOrder);
         dest.writeStringList(mAllowedPackages);
         dest.writeBoolean(mShouldPerformActiveScan);
@@ -224,7 +201,6 @@
         }
         RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
         return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
-                && Objects.equals(mRequiredFeatures, other.mRequiredFeatures)
                 && Objects.equals(mPackageOrder, other.mPackageOrder)
                 && Objects.equals(mAllowedPackages, other.mAllowedPackages)
                 && mShouldPerformActiveScan == other.mShouldPerformActiveScan;
@@ -232,7 +208,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPreferredFeatures, mRequiredFeatures, mPackageOrder, mAllowedPackages,
+        return Objects.hash(mPreferredFeatures, mPackageOrder, mAllowedPackages,
                 mShouldPerformActiveScan);
     }
 
@@ -241,7 +217,6 @@
      */
     public static final class Builder {
         List<String> mPreferredFeatures;
-        List<String> mRequiredFeatures;
         List<String> mPackageOrder;
         List<String> mAllowedPackages;
 
@@ -253,7 +228,6 @@
             Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
             mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
                     .collect(Collectors.toList());
-            mRequiredFeatures = List.of();
             mPackageOrder = List.of();
             mAllowedPackages = List.of();
             mActiveScan = activeScan;
@@ -263,7 +237,6 @@
             Objects.requireNonNull(preference, "preference must not be null");
 
             mPreferredFeatures = preference.getPreferredFeatures();
-            mRequiredFeatures = preference.getRequiredFeatures();
             mPackageOrder = preference.getDeduplicationPackageOrder();
             mAllowedPackages = preference.getAllowedPackages();
             mActiveScan = preference.shouldPerformActiveScan();
@@ -271,14 +244,8 @@
         }
 
         /**
-         * A constructor to combine multiple preferences into a single preference. The combined
-         * preference will discover a superset of the union of the routes discoverable by each of
-         * the individual preferences.
-         * <p>
-         * When routes need to be discovered for multiple preferences, the combined preference can
-         * be used to query route providers once and obtain all routes of interest. The obtained
-         * routes can then be filtered for each of the individual preferences. This is typically
-         * more efficient than querying route providers with each of the individual preferences.
+         * A constructor to combine multiple preferences into a single preference.
+         * It ignores extras of preferences.
          *
          * @hide
          */
@@ -286,15 +253,21 @@
             Objects.requireNonNull(preferences, "preferences must not be null");
 
             Set<String> preferredFeatures = new HashSet<>();
+            Set<String> allowedPackages = new HashSet<>();
+            mPackageOrder = List.of();
             boolean activeScan = false;
             for (RouteDiscoveryPreference preference : preferences) {
                 preferredFeatures.addAll(preference.mPreferredFeatures);
+
+                allowedPackages.addAll(preference.mAllowedPackages);
                 activeScan |= preference.mShouldPerformActiveScan;
+                // Choose one of either
+                if (mPackageOrder.isEmpty() && !preference.mPackageOrder.isEmpty()) {
+                    mPackageOrder = List.copyOf(preference.mPackageOrder);
+                }
             }
-            mPreferredFeatures = new ArrayList<>(preferredFeatures);
-            mRequiredFeatures = List.of();
-            mPackageOrder = List.of();
-            mAllowedPackages = List.of();
+            mPreferredFeatures = List.copyOf(preferredFeatures);
+            mAllowedPackages = List.copyOf(allowedPackages);
             mActiveScan = activeScan;
         }
 
@@ -316,17 +289,6 @@
         }
 
         /**
-         * Sets the required route features to discover.
-         */
-        @NonNull
-        public Builder setRequiredFeatures(@NonNull List<String> requiredFeatures) {
-            Objects.requireNonNull(requiredFeatures, "preferredFeatures must not be null");
-            mRequiredFeatures = requiredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
-                    .collect(Collectors.toList());
-            return this;
-        }
-
-        /**
          * Sets the list of package names of providers that media router would like to discover.
          * <p>
          * If it's non-empty, media router only discovers route from the provider in the list.
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 4d6739e..31e1817 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -623,7 +623,7 @@
     Image_setBufferItem(env, image, buffer);
     env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp,
             static_cast<jlong>(buffer->mTimestamp));
-    env->SetLongField(image, gSurfaceImageClassInfo.mDataSpace,
+    env->SetIntField(image, gSurfaceImageClassInfo.mDataSpace,
             static_cast<jint>(buffer->mDataSpace));
     auto transform = buffer->mTransform;
     if (buffer->mTransformToDisplayInverse) {
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 0f88afb..6c6fccb 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -901,7 +901,7 @@
 
     env->SetIntField(thiz, gSurfaceImageClassInfo.mNativeFenceFd, reinterpret_cast<jint>(fenceFd));
 
-    env->SetLongField(thiz, gSurfaceImageClassInfo.mDataSpace, dataSpace);
+    env->SetIntField(thiz, gSurfaceImageClassInfo.mDataSpace, dataSpace);
 }
 
 static void Image_unlockIfLocked(JNIEnv* env, jobject thiz) {
diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp
index 84a8d51..193a5d4 100644
--- a/media/jni/audioeffect/Visualizer.cpp
+++ b/media/jni/audioeffect/Visualizer.cpp
@@ -49,7 +49,7 @@
 }
 
 status_t Visualizer::set(int32_t priority,
-                         effect_callback_t cbf,
+                         legacy_callback_t cbf,
                          void* user,
                          audio_session_t sessionId,
                          audio_io_handle_t io,
diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h
index aa07ce8..c402c10 100644
--- a/media/jni/audioeffect/Visualizer.h
+++ b/media/jni/audioeffect/Visualizer.h
@@ -75,8 +75,8 @@
      * See AudioEffect 'set' function for details on parameters.
      */
     status_t    set(int32_t priority = 0,
-                    effect_callback_t cbf = NULL,
-                    void* user = NULL,
+                    legacy_callback_t cbf = nullptr,
+                    void* user = nullptr,
                     audio_session_t sessionId = AUDIO_SESSION_OUTPUT_MIX,
                     audio_io_handle_t io = AUDIO_IO_HANDLE_NONE,
                     const AudioDeviceTypeAddr& device = {},
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index d7e9ae9..b4aad9d 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -71,6 +71,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -276,6 +277,42 @@
         assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
+    @Test
+    public void testNoAllowedPackages_returnsZeroRoutes() throws Exception {
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true)
+                        .setAllowedPackages(List.of("random package name"))
+                        .build();
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(preference);
+
+        int remoteRouteCount = 0;
+        for (MediaRoute2Info route : routes.values()) {
+            if (!route.isSystemRoute()) {
+                remoteRouteCount++;
+            }
+        }
+
+        assertEquals(0, remoteRouteCount);
+    }
+
+    @Test
+    public void testAllowedPackages() throws Exception {
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true)
+                        .setAllowedPackages(List.of("com.android.mediaroutertest"))
+                        .build();
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(preference);
+
+        int remoteRouteCount = 0;
+        for (MediaRoute2Info route : routes.values()) {
+            if (!route.isSystemRoute()) {
+                remoteRouteCount++;
+            }
+        }
+
+        assertTrue(remoteRouteCount > 0);
+    }
+
     /**
      * Tests if MR2.SessionCallback.onSessionCreated is called
      * when a route is selected from MR2Manager.
@@ -818,8 +855,14 @@
 
     Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
             throws Exception {
+        return waitAndGetRoutesWithManager(
+                new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
+    }
+
+    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(RouteDiscoveryPreference preference)
+            throws Exception {
         CountDownLatch addedLatch = new CountDownLatch(1);
-        CountDownLatch featuresLatch = new CountDownLatch(1);
+        CountDownLatch preferenceLatch = new CountDownLatch(1);
 
         // A dummy callback is required to send route feature info.
         RouteCallback routeCallback = new RouteCallback() {};
@@ -828,7 +871,8 @@
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
                 for (MediaRoute2Info route : routes) {
                     if (!route.isSystemRoute()
-                            && hasMatchingFeature(route.getFeatures(), routeFeatures)) {
+                            && hasMatchingFeature(route.getFeatures(), preference
+                            .getPreferredFeatures())) {
                         addedLatch.countDown();
                         break;
                     }
@@ -836,20 +880,19 @@
             }
 
             @Override
-            public void onPreferredFeaturesChanged(String packageName,
-                    List<String> preferredFeatures) {
+            public void onDiscoveryPreferenceChanged(String packageName,
+                    RouteDiscoveryPreference discoveryPreference) {
                 if (TextUtils.equals(mPackageName, packageName)
-                        && preferredFeatures.size() == routeFeatures.size()
-                        && preferredFeatures.containsAll(routeFeatures)) {
-                    featuresLatch.countDown();
+                        && Objects.equals(preference, discoveryPreference)) {
+                    preferenceLatch.countDown();
                 }
             }
         };
         mManager.registerCallback(mExecutor, managerCallback);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, preference);
+
         try {
-            featuresLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
+            preferenceLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
             if (mManager.getAvailableRoutes(mPackageName).isEmpty()) {
                 addedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
             }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b7beb6e..3009a36 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -205,6 +205,7 @@
     ASensorManager_destroyEventQueue;
     ASensorManager_getDefaultSensor;
     ASensorManager_getDefaultSensorEx; # introduced=21
+    ASensorManager_getDynamicSensorList; # introduced=33
     ASensorManager_getInstance;
     ASensorManager_getInstanceForPackage; # introduced=26
     ASensorManager_getSensorList;
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index 63082fd..968de34 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -83,6 +83,16 @@
     return c;
 }
 
+ssize_t ASensorManager_getDynamicSensorList(ASensorManager* manager, ASensorList* list) {
+    RETURN_IF_MANAGER_IS_NULL(android::BAD_VALUE);
+    Sensor const* const* l;
+    ssize_t c = static_cast<SensorManager*>(manager)->getDynamicSensorList(&l);
+    if (list) {
+        *list = reinterpret_cast<ASensorList>(l);
+    }
+    return c;
+}
+
 ASensor const* ASensorManager_getDefaultSensor(ASensorManager* manager, int type) {
     RETURN_IF_MANAGER_IS_NULL(nullptr);
     return static_cast<SensorManager*>(manager)->getDefaultSensor(type);
diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 0414bb7..221a848 100644
--- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -740,8 +740,9 @@
      * {@link #unregisterUsageCallback} is called.
      *
      * @param template Template used to match networks. See {@link NetworkTemplate}.
-     * @param thresholdBytes Threshold in bytes to be notified on. The provided value that lower
-     *                       than 2MiB will be clamped for non-privileged callers.
+     * @param thresholdBytes Threshold in bytes to be notified on. Provided values lower than 2MiB
+     *                       will be clamped for callers except callers with the NETWORK_STACK
+     *                       permission.
      * @param executor The executor on which callback will be invoked. The provided {@link Executor}
      *                 must run callback sequentially, otherwise the order of callbacks cannot be
      *                 guaranteed.
@@ -750,6 +751,9 @@
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK}, conditional = true)
     public void registerUsageCallback(@NonNull NetworkTemplate template, long thresholdBytes,
             @NonNull @CallbackExecutor Executor executor, @NonNull UsageCallback callback) {
         Objects.requireNonNull(template, "NetworkTemplate cannot be null");
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
index 925d12b..e4d6e24 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -29,9 +28,7 @@
  * A {@link NetworkSpecifier} used to identify ethernet interfaces.
  *
  * @see EthernetManager
- * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class EthernetNetworkSpecifier extends NetworkSpecifier implements Parcelable {
 
     /**
@@ -61,6 +58,7 @@
         return mInterfaceName;
     }
 
+    /** @hide */
     @Override
     public boolean canBeSatisfiedBy(@Nullable NetworkSpecifier other) {
         return equals(other);
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
index a423783..9cb0947 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
@@ -61,7 +61,7 @@
  *     Internet Protocol</a>
  */
 @SystemService(Context.IPSEC_SERVICE)
-public final class IpSecManager {
+public class IpSecManager {
     private static final String TAG = "IpSecManager";
 
     /**
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java
index 061f323..42c0044 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/InterfaceMapValue.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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.
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index 1953624..fdfc893 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
@@ -19,8 +19,11 @@
 import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
 
 import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.DataUsageRequest;
 import android.net.NetworkIdentitySet;
+import android.net.NetworkStack;
 import android.net.NetworkStats;
 import android.net.NetworkStatsAccess;
 import android.net.NetworkStatsCollection;
@@ -74,9 +77,9 @@
      *
      * @return the normalized request wrapped within {@link RequestInfo}.
      */
-    public DataUsageRequest register(DataUsageRequest inputRequest, IUsageCallback callback,
-            int callingUid, @NetworkStatsAccess.Level int accessLevel) {
-        DataUsageRequest request = buildRequest(inputRequest, callingUid);
+    public DataUsageRequest register(Context context, DataUsageRequest inputRequest,
+            IUsageCallback callback, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+        DataUsageRequest request = buildRequest(context, inputRequest, callingUid);
         RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
                 accessLevel);
 
@@ -194,10 +197,13 @@
         }
     }
 
-    private DataUsageRequest buildRequest(DataUsageRequest request, int callingUid) {
-        // For non-system uid, cap the minimum threshold to a safe default to avoid too
-        // many callbacks.
-        long thresholdInBytes = (callingUid == Process.SYSTEM_UID ? request.thresholdInBytes
+    private DataUsageRequest buildRequest(Context context, DataUsageRequest request,
+                int callingUid) {
+        // For non-NETWORK_STACK permission uid, cap the minimum threshold to a safe default to
+        // avoid too many callbacks.
+        final long thresholdInBytes = (context.checkPermission(
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, Process.myPid(), callingUid)
+                == PackageManager.PERMISSION_GRANTED ? request.thresholdInBytes
                 : Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes));
         if (thresholdInBytes > request.thresholdInBytes) {
             Log.w(TAG, "Threshold was too low for " + request
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index 7a5ba09..e366ca1 100644
--- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -1229,7 +1229,7 @@
         DataUsageRequest normalizedRequest;
         final long token = Binder.clearCallingIdentity();
         try {
-            normalizedRequest = mStatsObservers.register(
+            normalizedRequest = mStatsObservers.register(mContext,
                     request, callback, callingUid, accessLevel);
         } finally {
             Binder.restoreCallingIdentity(token);
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index e1bd9f7..25d6c555 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -110,4 +110,10 @@
 
     <dimen name="avatar_picker_icon_inset">25dp</dimen>
 
+    <!-- Minimum increment between density scales. -->
+    <fraction name="display_density_min_scale_interval">9%</fraction>
+    <!-- Maximum density scale. The actual scale used depends on the device. -->
+    <fraction name="display_density_max_scale">150%</fraction>
+    <!-- Minimum density scale. This is available on all devices. -->
+    <fraction name="display_density_min_scale">85%</fraction>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index 2e7cfcb..9a29f22 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -51,6 +51,9 @@
     private static final int[] MICROPHONE_OPS = new int[]{
             AppOpsManager.OP_RECORD_AUDIO,
     };
+    private static final int[] CAMERA_OPS = new int[]{
+            AppOpsManager.OP_CAMERA,
+    };
 
 
     private static final String TAG = RecentAppOpsAccess.class.getSimpleName();
@@ -99,6 +102,13 @@
     }
 
     /**
+     * Creates an instance of {@link RecentAppOpsAccess} for camera access.
+     */
+    public static RecentAppOpsAccess createForCamera(Context context) {
+        return new RecentAppOpsAccess(context, CAMERA_OPS);
+    }
+
+    /**
      * Fills a list of applications which queried for access recently within specified time.
      * Apps are sorted by recency. Apps with more recent accesses are in the front.
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index a38091d..8b34cf3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -38,15 +38,6 @@
 public class DisplayDensityUtils {
     private static final String LOG_TAG = "DisplayDensityUtils";
 
-    /** Minimum increment between density scales. */
-    private static final float MIN_SCALE_INTERVAL = 0.09f;
-
-    /** Minimum density scale. This is available on all devices. */
-    private static final float MIN_SCALE = 0.85f;
-
-    /** Maximum density scale. The actual scale used depends on the device. */
-    private static final float MAX_SCALE = 1.50f;
-
     /** Summary used for "default" scale. */
     public static final int SUMMARY_DEFAULT = R.string.screen_zoom_summary_default;
 
@@ -105,11 +96,16 @@
         // Compute number of "larger" and "smaller" scales for this display.
         final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels);
         final int maxDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
-        final float maxScale = Math.min(MAX_SCALE, maxDensity / (float) defaultDensity);
-        final float minScale = MIN_SCALE;
-        final int numLarger = (int) MathUtils.constrain((maxScale - 1) / MIN_SCALE_INTERVAL,
+        final float maxScaleDimen = context.getResources().getFraction(
+                R.fraction.display_density_max_scale, 1, 1);
+        final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
+        final float minScale = context.getResources().getFraction(
+                R.fraction.display_density_min_scale, 1, 1);
+        final float minScaleInterval = context.getResources().getFraction(
+                R.fraction.display_density_min_scale_interval, 1, 1);
+        final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
                 0, SUMMARIES_LARGER.length);
-        final int numSmaller = (int) MathUtils.constrain((1 - minScale) / MIN_SCALE_INTERVAL,
+        final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
                 0, SUMMARIES_SMALLER.length);
 
         String[] entries = new String[1 + numSmaller + numLarger];
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index dee6894..6a1cee3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.notification;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AlertDialog;
@@ -42,8 +43,6 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.R;
 
@@ -72,6 +71,9 @@
     private static final int SECONDS_MS = 1000;
     private static final int MINUTES_MS = 60 * SECONDS_MS;
 
+    @Nullable
+    private final ZenModeDialogMetricsLogger mMetricsLogger;
+
     @VisibleForTesting
     protected Uri mForeverId;
     private int mBucketIndex = -1;
@@ -102,13 +104,16 @@
     }
 
     public EnableZenModeDialog(Context context, int themeResId) {
-        this(context, themeResId, false /* cancelIsNeutral */);
+        this(context, themeResId, false /* cancelIsNeutral */,
+                new ZenModeDialogMetricsLogger(context));
     }
 
-    public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral) {
+    public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral,
+            ZenModeDialogMetricsLogger metricsLogger) {
         mContext = context;
         mThemeResId = themeResId;
         mCancelIsNeutral = cancelIsNeutral;
+        mMetricsLogger = metricsLogger;
     }
 
     public AlertDialog createDialog() {
@@ -129,17 +134,11 @@
                                 ConditionTag tag = getConditionTagAt(checkedId);
 
                                 if (isForever(tag.condition)) {
-                                    MetricsLogger.action(mContext,
-                                            MetricsProto.MetricsEvent.
-                                                    NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER);
+                                    mMetricsLogger.logOnEnableZenModeForever();
                                 } else if (isAlarm(tag.condition)) {
-                                    MetricsLogger.action(mContext,
-                                            MetricsProto.MetricsEvent.
-                                                    NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM);
+                                    mMetricsLogger.logOnEnableZenModeUntilAlarm();
                                 } else if (isCountdown(tag.condition)) {
-                                    MetricsLogger.action(mContext,
-                                            MetricsProto.MetricsEvent.
-                                                    NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN);
+                                    mMetricsLogger.logOnEnableZenModeUntilCountdown();
                                 } else {
                                     Slog.d(TAG, "Invalid manual condition: " + tag.condition);
                                 }
@@ -222,8 +221,7 @@
                 if (isChecked) {
                     tag.rb.setChecked(true);
                     if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId);
-                    MetricsLogger.action(mContext,
-                            MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
+                    mMetricsLogger.logOnConditionSelected();
                     updateAlarmWarningText(tag.condition);
                 }
             }
@@ -435,7 +433,7 @@
     }
 
     private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
-        MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_TIME, up);
+        mMetricsLogger.logOnClickTimeButton(up);
         Condition newCondition = null;
         final int N = MINUTE_BUCKETS.length;
         if (mBucketIndex == -1) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenModeDialogMetricsLogger.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenModeDialogMetricsLogger.java
new file mode 100644
index 0000000..088a37d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenModeDialogMetricsLogger.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.settingslib.notification;
+
+import android.content.Context;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
+/**
+ * Logs ui events for {@link EnableZenModeDialog}.
+ */
+public class ZenModeDialogMetricsLogger {
+    private final Context mContext;
+
+    public ZenModeDialogMetricsLogger(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * User enabled DND from the QS DND dialog to last until manually turned off
+     */
+    public void logOnEnableZenModeForever() {
+        MetricsLogger.action(
+                mContext,
+                MetricsProto.MetricsEvent.NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER);
+    }
+
+    /**
+     * User enabled DND from the QS DND dialog to last until the next alarm goes off
+     */
+    public void logOnEnableZenModeUntilAlarm() {
+        MetricsLogger.action(
+                mContext,
+                MetricsProto.MetricsEvent.NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM);
+    }
+
+    /**
+     * User enabled DND from the QS DND dialog to last until countdown is done
+     */
+    public void logOnEnableZenModeUntilCountdown() {
+        MetricsLogger.action(
+                mContext,
+                MetricsProto.MetricsEvent.NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN);
+    }
+
+    /**
+     * User selected an option on the DND dialog
+     */
+    public void logOnConditionSelected() {
+        MetricsLogger.action(
+                mContext,
+                MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
+    }
+
+    /**
+     * User increased or decreased countdown duration of DND from the DND dialog
+     */
+    public void logOnClickTimeButton(boolean up) {
+        MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_TIME, up);
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index a6bfc408b..716ee84 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -43,6 +43,7 @@
         Settings.System.FONT_SCALE,
         Settings.System.DIM_SCREEN,
         Settings.System.SCREEN_OFF_TIMEOUT,
+        Settings.System.SCREEN_OFF_TIMEOUT_DOCKED,
         Settings.System.SCREEN_BRIGHTNESS_MODE,
         Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
         Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 06712cc..d430296 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -111,6 +111,7 @@
                 });
         VALIDATORS.put(System.DISPLAY_COLOR_MODE_VENDOR_HINT, ANY_STRING_VALIDATOR);
         VALIDATORS.put(System.SCREEN_OFF_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(System.SCREEN_OFF_TIMEOUT_DOCKED, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(System.SCREEN_BRIGHTNESS_FOR_VR, new InclusiveIntegerRangeValidator(0, 255));
         VALIDATORS.put(System.SCREEN_BRIGHTNESS_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 51870e2..077337c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3624,7 +3624,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 209;
+            private static final int SETTINGS_VERSION = 210;
 
             private final int mUserId;
 
@@ -5498,17 +5498,21 @@
                 }
 
                 if (currentVersion == 208) {
-                    // Version 208: Enable enforcement of
+                    // Unused
+                    currentVersion = 209;
+                }
+                if (currentVersion == 209) {
+                    // Version 209: Enable enforcement of
                     // android.Manifest.permission#POST_NOTIFICATIONS in order for applications
                     // to post notifications.
                     final SettingsState secureSettings = getSecureSettingsLocked(userId);
                     secureSettings.insertSettingLocked(
                             Secure.NOTIFICATION_PERMISSION_ENABLED,
-                            /* enabled= */" 1",
+                            /* enabled= */ "1",
                             /* tag= */ null,
                             /* makeDefault= */ false,
                             SettingsState.SYSTEM_PACKAGE_NAME);
-                    currentVersion = 209;
+                    currentVersion = 210;
                 }
 
                 // vXXX: Add new settings above this point.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c9bd371..6b6aa71 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -845,6 +845,18 @@
                   android:visibleToInstantApps="true">
         </activity>
 
+        <activity android:name=".user.UserSwitcherActivity"
+                  android:label="@string/accessibility_multi_user_switch_switcher"
+                  android:theme="@style/Theme.UserSwitcherActivity"
+                  android:excludeFromRecents="true"
+                  android:showWhenLocked="true"
+                  android:showForAllUsers="true"
+                  android:finishOnTaskLaunch="true"
+                  android:launchMode="singleInstance"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
+                  android:visibleToInstantApps="true">
+        </activity>
+
         <receiver android:name=".controls.management.ControlsRequestReceiver"
             android:exported="true">
             <intent-filter>
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
new file mode 100644
index 0000000..7ef0901
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2022 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.animation
+
+import android.annotation.SuppressLint
+import android.app.WindowConfiguration
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.ArrayMap
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransition
+import android.window.TransitionInfo
+
+class RemoteTransitionAdapter {
+    companion object {
+        /**
+         * Almost a copy of Transitions#setupStartState.
+         * TODO: remove when there is proper cross-process transaction sync.
+         */
+        @SuppressLint("NewApi")
+        private fun setupLeash(
+            leash: SurfaceControl,
+            change: TransitionInfo.Change,
+            layer: Int,
+            info: TransitionInfo,
+            t: SurfaceControl.Transaction
+        ) {
+            val isOpening = info.type == WindowManager.TRANSIT_OPEN ||
+                    info.type == WindowManager.TRANSIT_TO_FRONT
+            // Put animating stuff above this line and put static stuff below it.
+            val zSplitLine = info.changes.size
+            // changes should be ordered top-to-bottom in z
+            val mode = change.mode
+
+            // Launcher animates leaf tasks directly, so always reparent all task leashes to root.
+            t.reparent(leash, info.rootLeash)
+            t.setPosition(leash, (change.startAbsBounds.left - info.rootOffset.x).toFloat(), (
+                    change.startAbsBounds.top - info.rootOffset.y).toFloat())
+            t.show(leash)
+            // Put all the OPEN/SHOW on top
+            if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) {
+                if (isOpening) {
+                    t.setLayer(leash, zSplitLine + info.changes.size - layer)
+                    if (change.flags
+                            and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0) {
+                        // if transferred, it should be left visible.
+                        t.setAlpha(leash, 0f)
+                    }
+                } else {
+                    // put on bottom and leave it visible
+                    t.setLayer(leash, zSplitLine - layer)
+                }
+            } else if (mode == WindowManager.TRANSIT_CLOSE ||
+                    mode == WindowManager.TRANSIT_TO_BACK) {
+                if (isOpening) {
+                    // put on bottom and leave visible
+                    t.setLayer(leash, zSplitLine - layer)
+                } else {
+                    // put on top
+                    t.setLayer(leash, zSplitLine + info.changes.size - layer)
+                }
+            } else { // CHANGE
+                t.setLayer(leash, zSplitLine + info.changes.size - layer)
+            }
+        }
+
+        @SuppressLint("NewApi")
+        private fun createLeash(
+            info: TransitionInfo,
+            change: TransitionInfo.Change,
+            order: Int,
+            t: SurfaceControl.Transaction
+        ): SurfaceControl {
+            // TODO: once we can properly sync transactions across process, then get rid of this.
+            if (change.parent != null && change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
+                // Special case for wallpaper atm. Normally these are left alone; but, a quirk of
+                // making leashes means we have to handle them specially.
+                return change.leash
+            }
+            val leashSurface = SurfaceControl.Builder()
+                    .setName(change.leash.toString() + "_transition-leash")
+                    .setContainerLayer().setParent(if (change.parent == null)
+                            info.rootLeash else info.getChange(change.parent!!)!!.leash).build()
+            // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
+            setupLeash(leashSurface, change, info.changes.size - order, info, t)
+            t.reparent(change.leash, leashSurface)
+            t.setAlpha(change.leash, 1.0f)
+            t.show(change.leash)
+            t.setPosition(change.leash, 0f, 0f)
+            t.setLayer(change.leash, 0)
+            return leashSurface
+        }
+
+        private fun newModeToLegacyMode(newMode: Int): Int {
+            return when (newMode) {
+                WindowManager.TRANSIT_OPEN, WindowManager.TRANSIT_TO_FRONT
+                        -> RemoteAnimationTarget.MODE_OPENING
+                WindowManager.TRANSIT_CLOSE, WindowManager.TRANSIT_TO_BACK
+                        -> RemoteAnimationTarget.MODE_CLOSING
+                else -> RemoteAnimationTarget.MODE_CHANGING
+            }
+        }
+
+        private fun rectOffsetTo(rect: Rect, offset: Point): Rect {
+            val out = Rect(rect)
+            out.offsetTo(offset.x, offset.y)
+            return out
+        }
+
+        fun createTarget(
+            change: TransitionInfo.Change,
+            order: Int,
+            info: TransitionInfo,
+            t: SurfaceControl.Transaction
+        ): RemoteAnimationTarget {
+            return RemoteAnimationTarget(
+                    /* taskId */ if (change.taskInfo != null) change.taskInfo!!.taskId else -1,
+                    /* mode */ newModeToLegacyMode(change.mode),
+                    /* leash */ createLeash(info, change, order, t),
+                    /* isTranslucent */ (change.flags and TransitionInfo.FLAG_TRANSLUCENT != 0 ||
+                    change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0),
+                    /* clipRect */ null,
+                    /* contentInsets */ Rect(0, 0, 0, 0),
+                    /* prefixOrderIndex */ order,
+                    /* position */ null,
+                    /* localBounds */ rectOffsetTo(change.endAbsBounds, change.endRelOffset),
+                    /* screenSpaceBounds */ Rect(change.endAbsBounds),
+                    /* windowConfig */ if (change.taskInfo != null)
+                            change.taskInfo!!.configuration.windowConfiguration else
+                                    WindowConfiguration(),
+                    /* isNotInRecents */ if (change.taskInfo != null)
+                            !change.taskInfo!!.isRunning else true,
+                    /* startLeash */ null,
+                    /* startBounds */ Rect(change.startAbsBounds),
+                    /* taskInfo */ change.taskInfo,
+                    /* allowEnterPip */ change.allowEnterPip,
+                    /* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE)
+        }
+
+        /**
+         * Represents a TransitionInfo object as an array of old-style targets
+         *
+         * @param wallpapers If true, this will return wallpaper targets; otherwise it returns
+         * non-wallpaper targets.
+         * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should
+         * be populated by this function. If null, it is ignored.
+         */
+        fun wrapTargets(
+            info: TransitionInfo,
+            wallpapers: Boolean,
+            t: SurfaceControl.Transaction,
+            leashMap: ArrayMap<SurfaceControl, SurfaceControl>?
+        ): Array<RemoteAnimationTarget> {
+            val out = ArrayList<RemoteAnimationTarget>()
+            for (i in info.changes.indices) {
+                val change = info.changes[i]
+                val changeIsWallpaper = change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0
+                if (wallpapers != changeIsWallpaper) continue
+                out.add(createTarget(change, info.changes.size - i, info, t))
+                if (leashMap != null) {
+                    leashMap[change.leash] = out[out.size - 1].leash
+                }
+            }
+            return out.toTypedArray()
+        }
+
+        @JvmStatic
+        fun adaptRemoteRunner(
+            runner: IRemoteAnimationRunner
+        ): IRemoteTransition.Stub {
+            return object : IRemoteTransition.Stub() {
+                override fun startAnimation(
+                    token: IBinder,
+                    info: TransitionInfo,
+                    t: SurfaceControl.Transaction,
+                    finishCallback: IRemoteTransitionFinishedCallback
+                ) {
+                    val leashMap = ArrayMap<SurfaceControl, SurfaceControl>()
+                    val appsCompat = wrapTargets(info, false /* wallpapers */, t, leashMap)
+                    val wallpapersCompat = wrapTargets(info, true /* wallpapers */, t, leashMap)
+                    // TODO(bc-unlock): Build wrapped object for non-apps target.
+                    val nonAppsCompat = arrayOfNulls<RemoteAnimationTarget>(0)
+
+                    // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
+                    var isReturnToHome = false
+                    var launcherTask: TransitionInfo.Change? = null
+                    var wallpaper: TransitionInfo.Change? = null
+                    var launcherLayer = 0
+                    var rotateDelta = 0
+                    var displayW = 0f
+                    var displayH = 0f
+                    for (i in info.changes.indices.reversed()) {
+                        val change = info.changes[i]
+                        if (change.taskInfo != null &&
+                                change.taskInfo!!.activityType
+                                        == WindowConfiguration.ACTIVITY_TYPE_HOME) {
+                            isReturnToHome = (change.mode == WindowManager.TRANSIT_OPEN ||
+                                    change.mode == WindowManager.TRANSIT_TO_FRONT)
+                            launcherTask = change
+                            launcherLayer = info.changes.size - i
+                        } else if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
+                            wallpaper = change
+                        }
+                        if (change.parent == null && change.endRotation >= 0 &&
+                                change.endRotation != change.startRotation) {
+                            rotateDelta = change.endRotation - change.startRotation
+                            displayW = change.endAbsBounds.width().toFloat()
+                            displayH = change.endAbsBounds.height().toFloat()
+                        }
+                    }
+
+                    // Prepare for rotation if there is one
+                    val counterLauncher = CounterRotator()
+                    val counterWallpaper = CounterRotator()
+                    if (launcherTask != null && rotateDelta != 0 && launcherTask.parent != null) {
+                        counterLauncher.setup(t, info.getChange(launcherTask.parent!!)!!.leash,
+                                rotateDelta, displayW, displayH)
+                        if (counterLauncher.surface != null) {
+                            t.setLayer(counterLauncher.surface!!, launcherLayer)
+                        }
+                    }
+                    if (isReturnToHome) {
+                        if (counterLauncher.surface != null) {
+                            t.setLayer(counterLauncher.surface!!, info.changes.size * 3)
+                        }
+                        // Need to "boost" the closing things since that's what launcher expects.
+                        for (i in info.changes.indices.reversed()) {
+                            val change = info.changes[i]
+                            val leash = leashMap[change.leash]
+                            val mode = info.changes[i].mode
+                            // Only deal with independent layers
+                            if (!TransitionInfo.isIndependent(change, info)) continue
+                            if (mode == WindowManager.TRANSIT_CLOSE ||
+                                    mode == WindowManager.TRANSIT_TO_BACK) {
+                                t.setLayer(leash!!, info.changes.size * 3 - i)
+                                counterLauncher.addChild(t, leash)
+                            }
+                        }
+                        // Make wallpaper visible immediately since sysui apparently won't do this.
+                        for (i in wallpapersCompat.indices.reversed()) {
+                            t.show(wallpapersCompat[i].leash)
+                            t.setAlpha(wallpapersCompat[i].leash, 1f)
+                        }
+                    } else {
+                        if (launcherTask != null) {
+                            counterLauncher.addChild(t, leashMap[launcherTask.leash])
+                        }
+                        if (wallpaper != null && rotateDelta != 0 && wallpaper.parent != null) {
+                            counterWallpaper.setup(t, info.getChange(wallpaper.parent!!)!!.leash,
+                                    rotateDelta, displayW, displayH)
+                            if (counterWallpaper.surface != null) {
+                                t.setLayer(counterWallpaper.surface!!, -1)
+                                counterWallpaper.addChild(t, leashMap[wallpaper.leash])
+                            }
+                        }
+                    }
+                    t.apply()
+                    val animationFinishedCallback = object : IRemoteAnimationFinishedCallback {
+                        override fun onAnimationFinished() {
+                            val finishTransaction = SurfaceControl.Transaction()
+                            counterLauncher.cleanUp(finishTransaction)
+                            counterWallpaper.cleanUp(finishTransaction)
+                            // Release surface references now. This is apparently to free GPU memory
+                            // while doing quick operations (eg. during CTS).
+                            for (i in info.changes.indices.reversed()) {
+                                info.changes[i].leash.release()
+                            }
+                            for (i in leashMap.size - 1 downTo 0) {
+                                leashMap.valueAt(i).release()
+                            }
+                            try {
+                                finishCallback.onTransitionFinished(null /* wct */,
+                                        finishTransaction)
+                            } catch (e: RemoteException) {
+                                Log.e("ActivityOptionsCompat", "Failed to call app controlled" +
+                                        " animation finished callback", e)
+                            }
+                        }
+
+                        override fun asBinder(): IBinder? {
+                            return null
+                        }
+                    }
+                    // TODO(bc-unlcok): Pass correct transit type.
+                    runner.onAnimationStart(
+                            WindowManager.TRANSIT_OLD_NONE,
+                            appsCompat, wallpapersCompat, nonAppsCompat,
+                            animationFinishedCallback)
+                }
+
+                override fun mergeAnimation(
+                    token: IBinder,
+                    info: TransitionInfo,
+                    t: SurfaceControl.Transaction,
+                    mergeTarget: IBinder,
+                    finishCallback: IRemoteTransitionFinishedCallback
+                ) {
+                    // TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
+                    //       ignore any incoming merges.
+                }
+            }
+        }
+
+        @JvmStatic
+        fun adaptRemoteAnimation(
+            adapter: RemoteAnimationAdapter
+        ): RemoteTransition {
+            return RemoteTransition(adaptRemoteRunner(adapter.runner), adapter.callingApplication)
+        }
+    }
+
+    /**
+     * Utility class that takes care of counter-rotating surfaces during a transition animation.
+     */
+    class CounterRotator {
+        /** Gets the surface with the counter-rotation.  */
+        var surface: SurfaceControl? = null
+            private set
+
+        /**
+         * Sets up this rotator.
+         *
+         * @param rotateDelta is the forward rotation change (the rotation the display is making).
+         * @param displayW (and H) Is the size of the rotating display.
+         */
+        fun setup(
+            t: SurfaceControl.Transaction,
+            parent: SurfaceControl,
+            rotateDelta: Int,
+            displayW: Float,
+            displayH: Float
+        ) {
+            var rotateDelta = rotateDelta
+            if (rotateDelta == 0) return
+            // We want to counter-rotate, so subtract from 4
+            rotateDelta = 4 - (rotateDelta + 4) % 4
+            surface = SurfaceControl.Builder()
+                    .setName("Transition Unrotate")
+                    .setContainerLayer()
+                    .setParent(parent)
+                    .build()
+            // column-major
+            when (rotateDelta) {
+                1 -> {
+                    t.setMatrix(surface, 0f, 1f, -1f, 0f)
+                    t.setPosition(surface!!, displayW, 0f)
+                }
+                2 -> {
+                    t.setMatrix(surface, -1f, 0f, 0f, -1f)
+                    t.setPosition(surface!!, displayW, displayH)
+                }
+                3 -> {
+                    t.setMatrix(surface, 0f, -1f, 1f, 0f)
+                    t.setPosition(surface!!, 0f, displayH)
+                }
+            }
+            t.show(surface)
+        }
+
+        /**
+         * Adds a surface that needs to be counter-rotate.
+         */
+        fun addChild(t: SurfaceControl.Transaction, child: SurfaceControl?) {
+            if (surface == null) return
+            t.reparent(child!!, surface)
+        }
+
+        /**
+         * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the
+         * counter rotation surface.
+         */
+        fun cleanUp(finishTransaction: SurfaceControl.Transaction) {
+            if (surface == null) return
+            finishTransaction.remove(surface!!)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
new file mode 100644
index 0000000..dcf66b9
--- /dev/null
+++ b/packages/SystemUI/docs/user-switching.md
@@ -0,0 +1,45 @@
+# User Switching
+
+Multiple users and the ability to switch between them is controlled by Settings -> System -> Multiple Users.
+
+## Entry Points
+
+### Quick Settings
+
+In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherActivity][5]).
+
+### Bouncer
+
+May allow changing or adding new users directly from they bouncer. See [KeyguardBouncer][1]
+
+### Keyguard affordance
+
+[KeyguardQsUserSwitchController][4]
+
+## Components
+
+All visual implementations should derive their logic and use the adapter specified in:
+
+### [UserSwitcherController][3]
+
+* Contains the current list of all system users
+* Listens for relevant events and broadcasts to make sure this list stays up to date
+* Manages user switching and dialogs for exiting from guest users
+* Is settings aware regarding adding users from the lockscreen
+
+## Visual Components
+
+### [UserSwitcherActivity][5]
+
+A fullscreen user switching activity, supporting add guest/user actions if configured.
+
+### [UserSwitchDialogController][6]
+
+Renders user switching as a dialog over the current surface, and supports add guest user/actions if configured.
+
+[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
+[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+[6]: /frameworks/base/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
new file mode 100644
index 0000000..5343411
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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="0dp"
+    android:layout_height="@dimen/qs_security_footer_single_line_height"
+    android:layout_weight="1"
+    android:gravity="center"
+    android:clickable="true"
+    android:visibility="gone">
+
+    <LinearLayout
+        android:id="@+id/fgs_text_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="@dimen/new_qs_footer_action_inset"
+        android:background="@drawable/qs_security_footer_background"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:paddingHorizontal="@dimen/qs_footer_padding"
+        >
+
+        <ImageView
+            android:id="@+id/primary_footer_icon"
+            android:layout_width="@dimen/qs_footer_icon_size"
+            android:layout_height="@dimen/qs_footer_icon_size"
+            android:gravity="start"
+            android:layout_marginEnd="12dp"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_info_outline"
+            android:tint="?android:attr/textColorSecondary" />
+
+        <TextView
+            android:id="@+id/footer_text"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:maxLines="1"
+            android:ellipsize="end"
+            android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
+            android:textColor="?android:attr/textColorSecondary"/>
+
+        <ImageView
+            android:id="@+id/footer_icon"
+            android:layout_width="@dimen/qs_footer_icon_size"
+            android:layout_height="@dimen/qs_footer_icon_size"
+            android:layout_marginStart="8dp"
+            android:contentDescription="@null"
+            android:src="@*android:drawable/ic_chevron_end"
+            android:autoMirrored="true"
+            android:tint="?android:attr/textColorSecondary" />
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/fgs_number_container"
+        android:layout_width="@dimen/qs_footer_action_button_size"
+        android:layout_height="@dimen/qs_footer_action_button_size"
+        android:background="@drawable/qs_footer_action_circle"
+        android:focusable="true"
+        android:visibility="gone">
+
+        <TextView
+            android:id="@+id/fgs_number"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
+            android:layout_gravity="center"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="18sp"/>
+        <ImageView
+            android:id="@+id/fgs_new"
+            android:layout_width="12dp"
+            android:layout_height="12dp"
+            android:scaleType="fitCenter"
+            android:layout_gravity="bottom|end"
+            android:src="@drawable/new_fgs_dot"
+            android:contentDescription="@string/fgs_dot_content_description"
+            />
+    </FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
index 95bdd89..4884df7 100644
--- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
@@ -20,34 +20,41 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_footer_height"
+    android:layout_height="@dimen/new_footer_height"
+    android:elevation="@dimen/qs_panel_elevation"
+    android:paddingTop="8dp"
+    android:paddingBottom="4dp"
+    android:background="@drawable/qs_footer_actions_background"
     android:gravity="center_vertical"
     android:layout_gravity="bottom"
 >
 
-    <View
-        android:layout_height="1dp"
+    <LinearLayout
+        android:id="@+id/security_footers_container"
+        android:orientation="horizontal"
+        android:layout_height="@dimen/qs_footer_action_button_size"
         android:layout_width="0dp"
         android:layout_weight="1"
-        />
+    />
 
+    <!-- Negative margin equal to -->
     <LinearLayout
         android:layout_height="match_parent"
         android:layout_width="wrap_content"
+        android:layout_marginEnd="@dimen/new_qs_footer_action_inset_negative"
         >
 
         <com.android.systemui.statusbar.phone.MultiUserSwitch
             android:id="@+id/multi_user_switch"
             android:layout_width="@dimen/qs_footer_action_button_size"
             android:layout_height="@dimen/qs_footer_action_button_size"
-            android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
             android:background="@drawable/qs_footer_action_circle"
             android:focusable="true">
 
             <ImageView
                 android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/multi_user_avatar_expanded_size"
-                android:layout_height="@dimen/multi_user_avatar_expanded_size"
+                android:layout_width="@dimen/qs_footer_icon_size"
+                android:layout_height="@dimen/qs_footer_icon_size"
                 android:layout_gravity="center"
                 android:scaleType="centerInside" />
         </com.android.systemui.statusbar.phone.MultiUserSwitch>
@@ -56,19 +63,17 @@
             android:id="@+id/settings_button_container"
             android:layout_width="@dimen/qs_footer_action_button_size"
             android:layout_height="@dimen/qs_footer_action_button_size"
-            android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
             android:background="@drawable/qs_footer_action_circle"
             android:clipChildren="false"
             android:clipToPadding="false">
 
             <com.android.systemui.statusbar.phone.SettingsButton
                 android:id="@+id/settings_button"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/qs_footer_action_button_size"
+                android:layout_width="@dimen/qs_footer_icon_size"
+                android:layout_height="@dimen/qs_footer_icon_size"
                 android:layout_gravity="center"
                 android:background="@android:color/transparent"
                 android:contentDescription="@string/accessibility_quick_settings_settings"
-                android:padding="@dimen/qs_footer_icon_padding"
                 android:scaleType="centerInside"
                 android:src="@drawable/ic_settings"
                 android:tint="?android:attr/textColorPrimary" />
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index cbf4f83..dad4c19 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -113,9 +113,17 @@
     <dimen name="bouncer_user_switcher_item_icon_size">28dp</dimen>
     <dimen name="bouncer_user_switcher_item_icon_padding">12dp</dimen>
     <dimen name="bouncer_user_switcher_width">248dp</dimen>
-    <dimen name="bouncer_user_switcher_icon_size">190dp</dimen>
     <dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen>
     <dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen>
     <dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
     <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
+
+    <!-- 2 * the margin + size should equal the plus_margin -->
+    <dimen name="user_switcher_icon_large_margin">16dp</dimen>
+    <dimen name="bouncer_user_switcher_icon_size">190dp</dimen>
+    <dimen name="bouncer_user_switcher_icon_size_plus_margin">222dp</dimen>
+
+    <dimen name="user_switcher_icon_selected_width">8dp</dimen>
+    <dimen name="user_switcher_fullscreen_button_text_size">14sp</dimen>
+    <dimen name="user_switcher_fullscreen_button_padding">12dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/drawable/new_fgs_dot.xml b/packages/SystemUI/res/drawable/new_fgs_dot.xml
new file mode 100644
index 0000000..759ddaf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/new_fgs_dot.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 2022, 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval"
+    android:width="12dp"
+    android:height="12dp">
+    <solid android:color="@*android:color/red" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index f54c30f..d057f5f 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -15,10 +15,7 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_footer_action_inset"
-    android:insetBottom="@dimen/qs_footer_action_inset"
-    android:insetLeft="@dimen/qs_footer_action_inset"
-    android:insetRight="@dimen/qs_footer_action_inset">
+       android:inset="@dimen/new_qs_footer_action_inset">
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 1a323bc..944061c 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -15,10 +15,7 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_footer_action_inset"
-    android:insetBottom="@dimen/qs_footer_action_inset"
-    android:insetLeft="@dimen/qs_footer_action_inset"
-    android:insetRight="@dimen/qs_footer_action_inset">
+    android:inset="@dimen/new_qs_footer_action_inset">
     <ripple
         android:color="?android:attr/colorControlHighlight">
         <item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
new file mode 100644
index 0000000..c9517cd9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+    <shape>
+        <solid android:color="?attr/underSurfaceColor"/>
+        <corners android:topLeftRadius="@dimen/qs_corner_radius"
+                 android:topRightRadius="@dimen/qs_corner_radius"/>
+    </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
index 860d23b..381af50 100644
--- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml
+++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
@@ -15,8 +15,8 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-    android:insetTop="@dimen/qs_security_footer_background_inset"
-    android:insetBottom="@dimen/qs_security_footer_background_inset"
+    android:insetTop="@dimen/qs_footer_action_inset"
+    android:insetBottom="@dimen/qs_footer_action_inset"
     >
     <ripple
         android:color="?android:attr/colorControlHighlight">
diff --git a/packages/SystemUI/res/drawable/user_switcher_icon_large.xml b/packages/SystemUI/res/drawable/user_switcher_icon_large.xml
new file mode 100644
index 0000000..b78b221
--- /dev/null
+++ b/packages/SystemUI/res/drawable/user_switcher_icon_large.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2022, 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.
+*/
+-->
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/bouncer_user_switcher_icon_size_plus_margin"
+    android:height="@dimen/bouncer_user_switcher_icon_size_plus_margin">
+  <!-- The final layer is inset, so it needs this background -->
+  <item>
+    <shape android:shape="oval">
+       <solid android:color="@color/user_switcher_fullscreen_bg" />
+    </shape>
+  </item>
+  <!-- When an item is selected, this layer will show a ring around the icon -->
+  <item>
+    <shape android:shape="oval">
+       <stroke
+           android:width="@dimen/user_switcher_icon_selected_width"
+           android:color="@android:color/transparent" />
+    </shape>
+  </item>
+  <!-- Where the user drawable/bitmap will be placed -->
+  <item
+      android:drawable="@drawable/kg_bg_avatar"
+      android:width="@dimen/bouncer_user_switcher_icon_size"
+      android:height="@dimen/bouncer_user_switcher_icon_size"
+      android:top="@dimen/user_switcher_icon_large_margin"
+      android:left="@dimen/user_switcher_icon_large_margin"
+      android:right="@dimen/user_switcher_icon_large_margin"
+      android:bottom="@dimen/user_switcher_icon_large_margin" />
+</layer-list>
diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
index 335e0a4..43b1661 100644
--- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
@@ -20,8 +20,6 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical|start"
-    android:paddingTop="1dp"
-    android:paddingBottom="1dp"
     android:paddingEnd="12dp">
 
     <FrameLayout
@@ -69,4 +67,4 @@
         android:singleLine="true"
         style="?attr/hybridNotificationTextStyle"
     />
-</com.android.systemui.statusbar.notification.row.HybridConversationNotificationView>
\ No newline at end of file
+</com.android.systemui.statusbar.notification.row.HybridConversationNotificationView>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 22abd0c..85b33cc 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -25,7 +25,7 @@
         android:id="@+id/expanded_qs_scroll_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:elevation="4dp"
+        android:elevation="@dimen/qs_panel_elevation"
         android:importantForAccessibility="no"
         android:scrollbars="none"
         android:clipChildren="false"
@@ -55,7 +55,7 @@
         android:id="@+id/container_stub"
         android:inflatedId="@+id/qs_footer_actions"
         android:layout="@layout/new_footer_actions"
-        android:layout_height="@dimen/qs_footer_height"
+        android:layout_height="@dimen/new_footer_height"
         android:layout_width="match_parent"
         android:layout_gravity="bottom"
         />
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
new file mode 100644
index 0000000..7b95cf3c
--- /dev/null
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 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:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:id="@+id/user_switcher_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginEnd="60dp"
+    android:layout_marginStart="60dp">
+
+  <androidx.constraintlayout.helper.widget.Flow
+      android:id="@+id/flow"
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      app:layout_constraintBottom_toBottomOf="parent"
+      app:layout_constraintTop_toTopOf="parent"
+      app:layout_constraintStart_toStartOf="parent"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:flow_horizontalBias="0.5"
+      app:flow_verticalAlign="center"
+      app:flow_wrapMode="chain"
+      app:flow_horizontalGap="64dp"
+      app:flow_verticalGap="44dp"
+      app:flow_horizontalStyle="packed"/>
+
+  <TextView
+      android:id="@+id/cancel"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center"
+      android:gravity="center"
+      app:layout_constraintHeight_min="48dp"
+      app:layout_constraintEnd_toStartOf="@+id/add"
+      app:layout_constraintBottom_toBottomOf="parent"
+      android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+      android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+      android:textColor="?androidprv:attr/colorAccentPrimary"
+      android:text="@string/cancel" />
+
+  <TextView
+      android:id="@+id/add"
+      android:visibility="gone"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center"
+      android:gravity="center"
+      app:layout_constraintHeight_min="48dp"
+      app:layout_constraintEnd_toEndOf="parent"
+      app:layout_constraintBottom_toBottomOf="parent"
+      android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+      android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+      android:textColor="?androidprv:attr/colorAccentPrimary"
+      android:text="@string/add" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml
new file mode 100644
index 0000000..3319442
--- /dev/null
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ Copyright (C) 2022 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="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+  <ImageView
+      android:id="@+id/user_switcher_icon"
+      android:layout_gravity="center"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content" />
+  <TextView
+      style="@style/Bouncer.UserSwitcher.Spinner.Item"
+      android:id="@+id/user_switcher_text"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:textColor="@*android:color/text_color_primary_device_default_dark"
+      android:layout_gravity="center" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
new file mode 100644
index 0000000..8d02429
--- /dev/null
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright (C) 2022 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:paddingVertical="18dp"
+    android:paddingStart="18dp"
+    android:paddingEnd="65dp">
+
+  <LinearLayout
+      android:orientation="horizontal"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:gravity="start">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_gravity="center"
+        android:layout_width="20dp"
+        android:layout_height="20dp"
+        android:contentDescription="@null"
+        android:layout_marginEnd="10dp" />
+
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textColor="@*android:color/text_color_primary_device_default_dark"
+        android:textSize="14sp"
+        android:layout_gravity="start" />
+  </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index ae557c4..e897f75 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -20,4 +20,6 @@
     <dimen name="controls_padding_horizontal">205dp</dimen>
     <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
     <dimen name="notification_panel_margin_bottom">56dp</dimen>
+
+    <dimen name="keyguard_split_shade_top_margin">72dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3ab569a..1514778 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -87,6 +87,7 @@
     <color name="notification_section_clear_all_btn_color">@color/GM2_grey_700</color>
 
     <color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
+    <color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color>
 
     <!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
     <color name="navigation_bar_icon_color">#E5FFFFFF</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7eb25db..fe79f27 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -329,6 +329,9 @@
          etc. -->
     <dimen name="qs_footer_height">48dp</dimen>
 
+    <!-- 40dp (circles) + 8dp (circle padding) + 8dp (top) + 4dp (bottom) -->
+    <dimen name="new_footer_height">60dp</dimen>
+
     <!-- The size of each of the icon buttons in the QS footer -->
     <dimen name="qs_footer_action_button_size">48dp</dimen>
 
@@ -336,6 +339,9 @@
 
     <!-- (48dp - 44dp) / 2 -->
     <dimen name="qs_footer_action_inset">2dp</dimen>
+    <!-- (48dp - 40dp) / 2 -->
+    <dimen name="new_qs_footer_action_inset">4dp</dimen>
+    <dimen name="new_qs_footer_action_inset_negative">-4dp</dimen>
 
     <!-- Margins on each side of QS Footer -->
     <dimen name="qs_footer_margin">2dp</dimen>
@@ -491,7 +497,8 @@
     <dimen name="qs_tile_text_size">14sp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
     <dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
-    <dimen name="qs_panel_padding_bottom">@dimen/qs_footer_height</dimen>
+    <dimen name="qs_panel_elevation">4dp</dimen>
+    <dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen>
     <dimen name="qs_panel_padding_top">48dp</dimen>
     <dimen name="qs_detail_header_padding">0dp</dimen>
     <dimen name="qs_detail_image_width">56dp</dimen>
@@ -574,15 +581,13 @@
     <dimen name="notification_shade_content_margin_horizontal">16dp</dimen>
 
     <!-- The top margin for the notification children container in its non-expanded form. -->
-    <dimen name="notification_children_container_margin_top">
-        @*android:dimen/notification_content_margin_top
-    </dimen>
+    <dimen name="notification_children_container_margin_top">48dp</dimen>
 
     <!-- The height of the gap between adjacent notification sections. -->
     <dimen name="notification_section_divider_height">@dimen/notification_side_paddings</dimen>
 
     <!-- Size of the face pile shown on one-line (children of a group) conversation notifications -->
-    <dimen name="conversation_single_line_face_pile_size">25dp</dimen>
+    <dimen name="conversation_single_line_face_pile_size">24dp</dimen>
 
     <!-- Size of the avatars within a face pile shown on one-line (children of a group) conversation notifications -->
     <dimen name="conversation_single_line_face_pile_avatar_size">17dp</dimen>
@@ -651,11 +656,14 @@
     <dimen name="panel_overshoot_amount">16dp</dimen>
 
     <!-- The padding between notification children when collapsed -->
-    <dimen name="notification_children_padding">4dp</dimen>
+    <dimen name="notification_children_padding">8dp</dimen>
 
     <!-- The padding on top of the first notification to the children container -->
     <dimen name="notification_children_container_top_padding">8dp</dimen>
 
+    <!-- The padding on the bottom of the last group hybrid notification when collapsed -->
+    <dimen name="notification_children_collapsed_bottom_padding">16dp</dimen>
+
     <!-- end margin for system icons if multi user switch is hidden -->
     <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 793647d..d39e295 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2375,6 +2375,9 @@
         <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> active app</item>
         <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> active apps</item>
     </plurals>
+    <!-- Content description for a dot indicator in the running application indicating that there
+    is new information [CHAR LIMIT=NONE] -->
+    <string name="fgs_dot_content_description">New information</string>
     <!-- Title for dialog listing applications currently running [CHAR LIMIT=NONE]-->
     <string name="fgs_manager_dialog_title">Active apps</string>
     <!-- Label of the button to stop an app from running [CHAR LIMIT=12]-->
@@ -2396,4 +2399,7 @@
     <string name="clipboard_edit_image_description">Edit copied image</string>
     <!-- Label for button to send copied content to a nearby device [CHAR LIMIT=NONE] -->
     <string name="clipboard_send_nearby_description">Send to nearby device</string>
+
+    <!-- Generic "add" string [CHAR LIMIT=NONE] -->
+    <string name="add">Add</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 590cc9b..9448d3f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -771,6 +771,14 @@
         <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
     </style>
 
+    <style name="Theme.UserSwitcherActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+        <item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
+        <item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
+        <item name="android:navigationBarColor">@color/user_switcher_fullscreen_bg</item>
+        <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
+    </style>
+
     <style name="Theme.CreateUser" parent="@style/Theme.SystemUI">
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">#33000000</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 4d0c443..c9a659a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shared.system;
 
 import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -31,6 +32,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.ArrayMap;
+import android.util.SparseArray;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -55,7 +57,7 @@
     public static final int ACTIVITY_TYPE_ASSISTANT = WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
     public final int activityType;
 
-    public final int taskId;
+    public int taskId;
     public final SurfaceControl leash;
     public final boolean isTranslucent;
     public final Rect clipRect;
@@ -67,7 +69,7 @@
     public final Rect startScreenSpaceBounds;
     public final boolean isNotInRecents;
     public final Rect contentInsets;
-    public final ActivityManager.RunningTaskInfo taskInfo;
+    public ActivityManager.RunningTaskInfo taskInfo;
     public final boolean allowEnterPip;
     public final int rotationChange;
     public final int windowType;
@@ -139,12 +141,21 @@
         // changes should be ordered top-to-bottom in z
         final int mode = change.getMode();
 
-        // Launcher animates leaf tasks directly, so always reparent all task leashes to root leash.
-        t.reparent(leash, info.getRootLeash());
-        t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
-                change.getStartAbsBounds().top - info.getRootOffset().y);
+        // Don't move anything that isn't independent within its parents
+        if (!TransitionInfo.isIndependent(change, info)) {
+            if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
+                t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
+            }
+            return;
+        }
 
-        t.show(leash);
+        final boolean hasParent = change.getParent() != null;
+
+        if (!hasParent) {
+            t.reparent(leash, info.getRootLeash());
+            t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
+                    change.getStartAbsBounds().top - info.getRootOffset().y);
+        }
         // Put all the OPEN/SHOW on top
         if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
             if (isOpening) {
@@ -181,8 +192,12 @@
         }
         SurfaceControl leashSurface = new SurfaceControl.Builder()
                 .setName(change.getLeash().toString() + "_transition-leash")
-                .setContainerLayer().setParent(change.getParent() == null ? info.getRootLeash()
-                        : info.getChange(change.getParent()).getLeash()).build();
+                .setContainerLayer()
+                // Initial the surface visible to respect the visibility of the original surface.
+                .setHidden(false)
+                .setParent(change.getParent() == null ? info.getRootLeash()
+                        : info.getChange(change.getParent()).getLeash())
+                .build();
         // Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
         setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
         t.reparent(change.getLeash(), leashSurface);
@@ -253,17 +268,42 @@
     public static RemoteAnimationTargetCompat[] wrap(TransitionInfo info, boolean wallpapers,
             SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
         final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
+        final SparseArray<RemoteAnimationTargetCompat> childTaskTargets = new SparseArray<>();
         for (int i = 0; i < info.getChanges().size(); i++) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             final boolean changeIsWallpaper =
                     (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
             if (wallpapers != changeIsWallpaper) continue;
 
-            out.add(new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t));
+            final RemoteAnimationTargetCompat targetCompat =
+                    new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
             if (leashMap != null) {
-                leashMap.put(change.getLeash(), out.get(out.size() - 1).leash);
+                leashMap.put(change.getLeash(), targetCompat.leash);
             }
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo != null) {
+                if (taskInfo.parentTaskId != -1) {
+                    // Cache child task targets to override its parent target later and exclude
+                    // child task while wrapping up animate targets. Otherwise the child task might
+                    // get transformed twice with the flow like RecentsView#redrawLiveTile.
+                    childTaskTargets.put(taskInfo.parentTaskId, targetCompat);
+                    continue;
+                }
+
+                final RemoteAnimationTargetCompat childTaskTarget =
+                        childTaskTargets.get(taskInfo.taskId);
+                if (childTaskTarget != null) {
+                    // Launcher monitors leaf tasks to perform animation, hence override the parent
+                    // task target with child task info so Launcher can locate and animate root
+                    // surface directly with leaf task information.
+                    targetCompat.taskInfo = childTaskTarget.taskInfo;
+                    targetCompat.taskId = childTaskTarget.taskId;
+                }
+            }
+
+            out.add(targetCompat);
         }
+
         return out.toArray(new RemoteAnimationTargetCompat[out.size()]);
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c387260..7bc343e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -856,10 +856,9 @@
         }
 
         private void setupUserSwitcher() {
-            String currentUserName = mUserSwitcherController.getCurrentUserName();
-            mUserSwitcher.setText(currentUserName);
+            final UserRecord currentUser = mUserSwitcherController.getCurrentUserRecord();
+            mUserSwitcher.setText(mUserSwitcherController.getCurrentUserName());
 
-            final UserRecord currentUser = getCurrentUser();
             ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor);
             BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
                 @Override
@@ -961,16 +960,6 @@
             });
         }
 
-        private UserRecord getCurrentUser() {
-            for (int i = 0; i < mUserSwitcherController.getUsers().size(); ++i) {
-                UserRecord userRecord = mUserSwitcherController.getUsers().get(i);
-                if (userRecord.isCurrent) {
-                    return userRecord;
-                }
-            }
-            return null;
-        }
-
         /**
          * Each view will get half the width. Yes, it would be easier to use something other than
          * FrameLayout but it was too disruptive to downstream projects to change.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index daca918..5bdee2a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -272,7 +272,7 @@
         mServicesStarted = true;
     }
 
-    // TODO(b/149254050): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
+    // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
     @Override
     public boolean addDumpable(Dumpable dumpable) {
         String name = dumpable.getDumpableName();
@@ -288,13 +288,21 @@
         if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable);
         mDumpables.put(name, dumpable);
 
-        // TODO(b/149254050): replace com.android.systemui.dump.Dumpable by
+        // TODO(b/217567642): replace com.android.systemui.dump.Dumpable by
         // com.android.util.Dumpable and get rid of the intermediate lambda
         mDumpManager.registerDumpable(dumpable.getDumpableName(),
                 (fd, pw, args) -> dumpable.dump(pw, args));
         return true;
     }
 
+    // TODO(b/217567642): implement
+    @Override
+    public boolean removeDumpable(Dumpable dumpable) {
+        Log.w(TAG, "removeDumpable(" + dumpable + "): not implemented");
+
+        return false;
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 54664f2..72b40d4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -48,7 +48,7 @@
     @Override
     public void start() {
         if (DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) {
+                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, false)) {
             mClipboardManager = requireNonNull(mContext.getSystemService(ClipboardManager.class));
             mClipboardManager.addPrimaryClipChangedListener(this);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
index 09221b4..240389a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -66,6 +66,7 @@
     }
 
     private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+        private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
         private final LockscreenSmartspaceController mSmartSpaceController;
         private final Context mContext;
 
@@ -92,7 +93,7 @@
             return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT,
                     ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START,
                     ComplicationLayoutParams.DIRECTION_DOWN,
-                    0, true);
+                    SMARTSPACE_COMPLICATION_WEIGHT, true);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 1af9e41..51101da 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -110,6 +110,8 @@
     public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, false);
 
     public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
+    public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
+            new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
 
     /***************************************/
     // 600- status bar
@@ -137,7 +139,7 @@
 
     /***************************************/
     // 900 - media
-    public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+    public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
     public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
     public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false);
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
index 3372899..3408d97 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java
@@ -90,9 +90,9 @@
         static ComplicationLayoutParams provideLayoutParams() {
             return new ComplicationLayoutParams(0,
                     ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ComplicationLayoutParams.POSITION_BOTTOM
-                            | ComplicationLayoutParams.POSITION_END,
-                    ComplicationLayoutParams.DIRECTION_UP,
+                    ComplicationLayoutParams.POSITION_TOP
+                            | ComplicationLayoutParams.POSITION_START,
+                    ComplicationLayoutParams.DIRECTION_DOWN,
                     0,
                     true);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index adae07b..2ed2f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -47,7 +47,7 @@
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
         type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
         flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-        title = "Media Tap-To-Transfer Chip View"
+        title = WINDOW_TITLE
         format = PixelFormat.TRANSLUCENT
         setTrustedOverlay()
     }
@@ -106,3 +106,7 @@
         }
     }
 }
+
+// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
+// UpdateMediaTapToTransferReceiverDisplayTest
+private const val WINDOW_TITLE = "Media Transfer Chip View"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 58ebe89..bbe0a99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -40,6 +40,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
 import com.android.systemui.R
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -50,6 +51,7 @@
 import javax.inject.Inject
 import kotlin.math.max
 
+@SysUISingleton
 class FgsManagerController @Inject constructor(
     private val context: Context,
     @Main private val mainExecutor: Executor,
@@ -65,6 +67,9 @@
         private val LOG_TAG = FgsManagerController::class.java.simpleName
     }
 
+    var changesSinceDialog = false
+        private set
+
     private var isAvailable = false
 
     private val lock = Any()
@@ -137,6 +142,7 @@
             val numPackagesAfter = getNumRunningPackagesLocked()
 
             if (numPackagesAfter != numPackagesBefore) {
+                changesSinceDialog = true
                 onNumberOfPackagesChangedListeners.forEach {
                     backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) }
                 }
@@ -210,6 +216,7 @@
                 this.dialog = dialog
 
                 dialog.setOnDismissListener {
+                    changesSinceDialog = false
                     synchronized(lock) {
                         this.dialog = null
                         updateAppItemsLocked()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 4aedbc9..77feb90 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -22,6 +22,8 @@
 import android.provider.Settings
 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
 import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
@@ -29,7 +31,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.logging.nano.MetricsProto
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.flags.FeatureFlags
@@ -46,6 +47,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
 import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.DualHeightHorizontalLinearLayout
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
@@ -57,7 +59,7 @@
  * determined by [buttonsVisibleState]
  */
 @QSScope
-class FooterActionsController @Inject constructor(
+internal class FooterActionsController @Inject constructor(
     view: FooterActionsView,
     multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
     private val activityStarter: ActivityStarter,
@@ -65,6 +67,8 @@
     private val userTracker: UserTracker,
     private val userInfoController: UserInfoController,
     private val deviceProvisionedController: DeviceProvisionedController,
+    private val securityFooterController: QSSecurityFooter,
+    private val fgsManagerFooterController: QSFgsManagerFooter,
     private val falsingManager: FalsingManager,
     private val metricsLogger: MetricsLogger,
     private val tunerService: TunerService,
@@ -90,15 +94,15 @@
             updateVisibility()
         }
 
-    init {
-        view.elevation = resources.displayMetrics.density * 4f
-        view.setBackgroundColor(Utils.getColorAttrDefaultColor(context, R.attr.underSurfaceColor))
-    }
-
     private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
     private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
+    private val securityFootersContainer: ViewGroup? =
+        view.findViewById(R.id.security_footers_container)
     private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
     private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
+    private val securityFootersSeparator = View(context).apply {
+        visibility = View.GONE
+    }
 
     private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
         val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
@@ -151,6 +155,7 @@
 
     override fun onInit() {
         multiUserSwitchController.init()
+        fgsManagerFooterController.init()
     }
 
     private fun updateVisibility() {
@@ -178,9 +183,46 @@
             powerMenuLite.visibility = View.GONE
         }
         settingsButton.setOnClickListener(onClickListener)
+        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            val securityFooter = securityFooterController.view as DualHeightHorizontalLinearLayout
+            securityFootersContainer?.addView(securityFooter)
+            val separatorWidth = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
+            securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
+            reformatForNewFooter(securityFooter)
+            val fgsFooter = fgsManagerFooterController.view
+            securityFootersContainer?.addView(fgsFooter)
+
+            val visibilityListener =
+                VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
+                    if (visibility == View.GONE) {
+                        securityFootersSeparator.visibility = View.GONE
+                    } else if (securityFooter.visibility == View.VISIBLE &&
+                        fgsFooter.visibility == View.VISIBLE) {
+                        securityFootersSeparator.visibility = View.VISIBLE
+                    } else {
+                        securityFootersSeparator.visibility = View.GONE
+                    }
+                    fgsManagerFooterController
+                        .setCollapsed(securityFooter.visibility == View.VISIBLE)
+                }
+            securityFooterController.setOnVisibilityChangedListener(visibilityListener)
+            fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
+        }
         updateView()
     }
 
+    private fun reformatForNewFooter(view: DualHeightHorizontalLinearLayout) {
+        // This is only necessary while things are flagged as the view could be attached in two
+        // different locations.
+        (view.layoutParams as LinearLayout.LayoutParams).apply {
+            bottomMargin = 0
+            width = 0
+            weight = 1f
+            marginEnd = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
+        }
+        view.alwaysSingleLine = true
+    }
+
     private fun updateView() {
         mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
     }
@@ -201,6 +243,10 @@
         } else {
             userInfoController.removeCallback(onUserInfoChangedListener)
         }
+        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            fgsManagerFooterController.setListening(listening)
+            securityFooterController.setListening(listening)
+        }
     }
 
     fun disable(state2: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7800027..707313f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -211,9 +211,13 @@
                 // Some views are always full width or have dependent padding
                 continue;
             }
-            LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            lp.rightMargin = mSideMargins;
-            lp.leftMargin = mSideMargins;
+            if (!(view instanceof FooterActionsView)) {
+                // Only padding for FooterActionsView, no margin. That way, the background goes
+                // all the way to the edge.
+                LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                lp.rightMargin = mSideMargins;
+                lp.leftMargin = mSideMargins;
+            }
             if (view == mQSPanelContainer) {
                 // QS panel lays out some of its content full width
                 qsPanelController.setContentMargins(mContentPadding, mContentPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
index cc5a771..26adf46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -94,15 +94,28 @@
     override fun getId() = _id
 }
 
+/**
+ * Events from the QS DND tile dialog. {@see QSZenModeDialogMetricsLogger}
+ * Other names for DND (Do Not Disturb) include "Zen" and "Priority mode".
+ */
 enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
-    @UiEvent(doc = "TODO(beverlyt)")
+    @UiEvent(doc = "User selected an option on the DND dialog")
     QS_DND_CONDITION_SELECT(420),
 
-    @UiEvent(doc = "TODO(beverlyt)")
+    @UiEvent(doc = "User increased countdown duration of DND from the DND dialog")
     QS_DND_TIME_UP(422),
 
-    @UiEvent(doc = "TODO(beverlyt)")
-    QS_DND_TIME_DOWN(423);
+    @UiEvent(doc = "User decreased countdown duration of DND from the DND dialog")
+    QS_DND_TIME_DOWN(423),
+
+    @UiEvent(doc = "User enabled DND from the QS DND dialog to last until manually turned off")
+    QS_DND_DIALOG_ENABLE_FOREVER(946),
+
+    @UiEvent(doc = "User enabled DND from the QS DND dialog to last until the next alarm goes off")
+    QS_DND_DIALOG_ENABLE_UNTIL_ALARM(947),
+
+    @UiEvent(doc = "User enabled DND from the QS DND dialog to last until countdown is done")
+    QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN(948);
 
     override fun getId() = _id
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index 55d4a53..0fe9095 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -20,12 +20,17 @@
 
 import android.content.Context;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.qs.dagger.QSScope;
 
 import java.util.concurrent.Executor;
 
@@ -35,9 +40,11 @@
 /**
  * Footer entry point for the foreground service manager
  */
+@QSScope
 public class QSFgsManagerFooter implements View.OnClickListener,
         FgsManagerController.OnDialogDismissedListener,
-        FgsManagerController.OnNumberOfPackagesChangedListener {
+        FgsManagerController.OnNumberOfPackagesChangedListener,
+        VisibilityChangedDispatcher {
 
     private final View mRootView;
     private final TextView mFooterText;
@@ -50,20 +57,43 @@
     private boolean mIsInitialized = false;
     private int mNumPackages;
 
+    private final View mTextContainer;
+    private final View mNumberContainer;
+    private final TextView mNumberView;
+    private final ImageView mDotView;
+
+    @Nullable
+    private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
+
     @Inject
     QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView,
             @Main Executor mainExecutor, @Background Executor executor,
             FgsManagerController fgsManagerController) {
         mRootView = rootView;
         mFooterText = mRootView.findViewById(R.id.footer_text);
-        ImageView icon = mRootView.findViewById(R.id.primary_footer_icon);
-        icon.setImageResource(R.drawable.ic_info_outline);
+        mTextContainer = mRootView.findViewById(R.id.fgs_text_container);
+        mNumberContainer = mRootView.findViewById(R.id.fgs_number_container);
+        mNumberView = mRootView.findViewById(R.id.fgs_number);
+        mDotView = mRootView.findViewById(R.id.fgs_new);
         mContext = rootView.getContext();
         mMainExecutor = mainExecutor;
         mExecutor = executor;
         mFgsManagerController = fgsManagerController;
     }
 
+    /**
+     * Whether to show the footer in collapsed mode (just a number) or not (text).
+     * @param collapsed
+     */
+    public void setCollapsed(boolean collapsed) {
+        mTextContainer.setVisibility(collapsed ? View.GONE : View.VISIBLE);
+        mNumberContainer.setVisibility(collapsed ? View.VISIBLE : View.GONE);
+        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mRootView.getLayoutParams();
+        lp.width = collapsed ? ViewGroup.LayoutParams.WRAP_CONTENT : 0;
+        lp.weight = collapsed ? 0f : 1f;
+        mRootView.setLayoutParams(lp);
+    }
+
     public void init() {
         if (mIsInitialized) {
             return;
@@ -89,6 +119,12 @@
     }
 
     @Override
+    public void setOnVisibilityChangedListener(
+            @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
+        mVisibilityChangedListener = onVisibilityChangedListener;
+    }
+
+    @Override
     public void onClick(View view) {
         mFgsManagerController.showDialog(mRootView);
     }
@@ -103,11 +139,19 @@
 
     public void handleRefreshState() {
         mMainExecutor.execute(() -> {
-            mFooterText.setText(mContext.getResources().getQuantityString(
-                    R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages));
+            CharSequence text = mContext.getResources().getQuantityString(
+                    R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages);
+            mFooterText.setText(text);
+            mNumberView.setText(Integer.toString(mNumPackages));
+            mNumberView.setContentDescription(text);
             if (mFgsManagerController.shouldUpdateFooterVisibility()) {
                 mRootView.setVisibility(mNumPackages > 0
                         && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE);
+                mDotView.setVisibility(
+                        mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE);
+                if (mVisibilityChangedListener != null) {
+                    mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility());
+                }
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 6b515c8..7c04cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -110,6 +110,7 @@
     private float mSquishinessFraction = 1f;
     private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
     private final Rect mClippingRect = new Rect();
+    private boolean mUseNewFooter = false;
 
     public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -151,6 +152,10 @@
         }
     }
 
+    void setUseNewFooter(boolean useNewFooter) {
+        mUseNewFooter = useNewFooter;
+    }
+
     protected void setHorizontalContentContainerClipping() {
         mHorizontalContentContainer.setClipChildren(true);
         mHorizontalContentContainer.setClipToPadding(false);
@@ -368,11 +373,12 @@
 
     protected void updatePadding() {
         final Resources res = mContext.getResources();
-        int padding = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+        int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
+        // Bottom padding only when there's a new footer with its height.
         setPaddingRelative(getPaddingStart(),
-                padding,
+                paddingTop,
                 getPaddingEnd(),
-                res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
+                mUseNewFooter ? res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom) : 0);
     }
 
     void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 8f268b5..418c4ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -31,6 +31,8 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
@@ -67,6 +69,7 @@
     private final BrightnessController mBrightnessController;
     private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
+    private final FeatureFlags mFeatureFlags;
 
     private boolean mGridContentVisible = true;
 
@@ -104,7 +107,7 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSliderController.Factory brightnessSliderFactory,
-            FalsingManager falsingManager, CommandQueue commandQueue) {
+            FalsingManager falsingManager, CommandQueue commandQueue, FeatureFlags featureFlags) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mQSFgsManagerFooter = qsFgsManagerFooter;
@@ -114,13 +117,14 @@
         mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
         mFalsingManager = falsingManager;
         mCommandQueue = commandQueue;
-        mQsSecurityFooter.setHostEnvironment(qstileHost);
 
         mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView);
         mView.setBrightnessView(mBrightnessSliderController.getRootView());
 
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
         mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
+        mFeatureFlags = featureFlags;
+        view.setUseNewFooter(featureFlags.isEnabled(Flags.NEW_FOOTER));
     }
 
     @Override
@@ -150,8 +154,10 @@
             refreshAllTiles();
         }
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
-        mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
-        mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
+        if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
+            mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
+        }
         switchTileLayout(true);
         mBrightnessMirrorHandler.onQsPanelAttached();
 
@@ -192,8 +198,10 @@
             refreshAllTiles();
         }
 
-        mQSFgsManagerFooter.setListening(listening);
-        mQsSecurityFooter.setListening(listening);
+        if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
+            mQSFgsManagerFooter.setListening(listening);
+            mQsSecurityFooter.setListening(listening);
+        }
 
         // Set the listening as soon as the QS fragment starts listening regardless of the
         //expansion, so it will update the current brightness before the slider is visible.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 9e17c12..fb55cd2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -80,6 +80,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -88,11 +89,14 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.SecurityController;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
 @QSScope
-class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener {
+class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener,
+        VisibilityChangedDispatcher {
     protected static final String TAG = "QSSecurityFooter";
     protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final boolean DEBUG_FORCE_VISIBLE = false;
@@ -107,11 +111,16 @@
     private final ActivityStarter mActivityStarter;
     private final Handler mMainHandler;
     private final UserTracker mUserTracker;
+    private final DialogLaunchAnimator mDialogLaunchAnimator;
+
+    private final AtomicBoolean mShouldUseSettingsButton = new AtomicBoolean(false);
 
     private AlertDialog mDialog;
-    private QSTileHost mHost;
     protected H mHandler;
 
+    // Does it move between footer and header? Remove this once all the flagging is removed
+    private boolean mIsMovable = true;
+
     private boolean mIsVisible;
     @Nullable
     private CharSequence mFooterTextContent = null;
@@ -119,10 +128,14 @@
     @Nullable
     private Drawable mPrimaryFooterIconDrawable;
 
+    @Nullable
+    private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
+
     @Inject
     QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView,
             UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter,
-            SecurityController securityController, @Background Looper bgLooper) {
+            SecurityController securityController, DialogLaunchAnimator dialogLaunchAnimator,
+            @Background Looper bgLooper) {
         mRootView = rootView;
         mRootView.setOnClickListener(this);
         mFooterText = mRootView.findViewById(R.id.footer_text);
@@ -135,10 +148,7 @@
         mSecurityController = securityController;
         mHandler = new H(bgLooper);
         mUserTracker = userTracker;
-    }
-
-    public void setHostEnvironment(QSTileHost host) {
-        mHost = host;
+        mDialogLaunchAnimator = dialogLaunchAnimator;
     }
 
     public void setListening(boolean listening) {
@@ -150,23 +160,31 @@
         }
     }
 
+    @Override
+    public void setOnVisibilityChangedListener(
+            @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
+        mVisibilityChangedListener = onVisibilityChangedListener;
+    }
+
     public void onConfigurationChanged() {
         FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size);
 
-        Resources r = mContext.getResources();
+        if (mIsMovable) {
+            Resources r = mContext.getResources();
 
-        mFooterText.setMaxLines(r.getInteger(R.integer.qs_security_footer_maxLines));
-        int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding);
-        mRootView.setPaddingRelative(padding, padding, padding, padding);
+            mFooterText.setMaxLines(r.getInteger(R.integer.qs_security_footer_maxLines));
+            int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding);
+            mRootView.setPaddingRelative(padding, padding, padding, padding);
 
-        int bottomMargin = r.getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
-        ViewGroup.MarginLayoutParams lp =
-                (ViewGroup.MarginLayoutParams) mRootView.getLayoutParams();
-        lp.bottomMargin = bottomMargin;
-        lp.width = r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT
-                ? MATCH_PARENT : WRAP_CONTENT;
-        mRootView.setLayoutParams(lp);
+            int bottomMargin = r.getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
+            ViewGroup.MarginLayoutParams lp =
+                    (ViewGroup.MarginLayoutParams) mRootView.getLayoutParams();
+            lp.bottomMargin = bottomMargin;
+            lp.width = r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT
+                    ? MATCH_PARENT : WRAP_CONTENT;
+            mRootView.setLayoutParams(lp);
 
+        }
         mRootView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background));
     }
 
@@ -455,23 +473,27 @@
     public void onClick(DialogInterface dialog, int which) {
         if (which == DialogInterface.BUTTON_NEGATIVE) {
             final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS);
-            mDialog.dismiss();
+            dialog.dismiss();
             // This dismisses the shade on opening the activity
             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
         }
     }
 
     private void createDialog() {
-        mDialog = new SystemUIDialog(mContext, 0); // Use mContext theme
-        mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
-        mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
-        mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
+        mShouldUseSettingsButton.set(false);
+        final View view = createDialogView();
+        mMainHandler.post(() -> {
+            mDialog = new SystemUIDialog(mContext, 0); // Use mContext theme
+            mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+            mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
+            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+                    mShouldUseSettingsButton.get() ? getSettingsButton() : getNegativeButton(),
+                    this);
 
-        mDialog.setView(createDialogView());
+            mDialog.setView(view);
 
-        mDialog.show();
-        mDialog.getWindow().setLayout(MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
+            mDialogLaunchAnimator.showFromView(mDialog, mRootView);
+        });
     }
 
     @VisibleForTesting
@@ -510,7 +532,7 @@
             TextView deviceManagementWarning =
                     (TextView) dialogView.findViewById(R.id.device_management_warning);
             deviceManagementWarning.setText(managementMessage);
-            mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this);
+            mShouldUseSettingsButton.set(true);
         }
 
         // ca certificate section
@@ -782,6 +804,9 @@
                 mFooterText.setText(mFooterTextContent);
             }
             mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE);
+            if (mVisibilityChangedListener != null) {
+                mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility());
+            }
         }
     };
 
@@ -814,7 +839,6 @@
             } catch (Throwable t) {
                 final String error = "Error in " + name;
                 Log.w(TAG, error, t);
-                mHost.warn(error, t);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt b/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt
new file mode 100644
index 0000000..73362ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.qs
+
+/**
+ * Dispatches events that set the visibility from the controller.
+ */
+interface VisibilityChangedDispatcher {
+
+    fun setOnVisibilityChangedListener(onVisibilityChangedListener: OnVisibilityChangedListener?)
+
+    fun interface OnVisibilityChangedListener {
+        fun onVisibilityChanged(visibility: Int)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index fdf9ae0..2780b16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -202,6 +202,6 @@
             @QSThemedContext LayoutInflater layoutInflater,
             QSPanel qsPanel
     ) {
-        return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false);
+        return layoutInflater.inflate(R.layout.fgs_footer, qsPanel, false);
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index a06dc8b..a33650c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -65,6 +65,7 @@
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.settings.SecureSettings;
@@ -86,6 +87,7 @@
     private final SharedPreferences mSharedPreferences;
     private final SettingObserver mSettingZenDuration;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
+    private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger;
 
     private boolean mListening;
     private boolean mShowingDetail;
@@ -119,6 +121,7 @@
                 refreshState();
             }
         };
+        mQSZenDialogMetricsLogger = new QSZenModeDialogMetricsLogger(mContext);
     }
 
     public static void setVisible(Context context, boolean visible) {
@@ -211,7 +214,8 @@
 
     private Dialog makeZenModeDialog() {
         AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
-                true /* cancelIsNeutral */).createDialog();
+                true /* cancelIsNeutral */,
+                mQSZenDialogMetricsLogger).createDialog();
         SystemUIDialog.applyFlags(dialog);
         SystemUIDialog.setShowForAllUsers(dialog, true);
         SystemUIDialog.registerDismissListener(dialog);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java
new file mode 100644
index 0000000..1b81a99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/QSZenModeDialogMetricsLogger.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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.qs.tiles.dialog;
+
+import android.content.Context;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.settingslib.notification.ZenModeDialogMetricsLogger;
+import com.android.systemui.qs.QSDndEvent;
+import com.android.systemui.qs.QSEvents;
+
+/**
+ * Logs ui events for the DND dialog that may appear from tapping the QS DND tile.
+ * To see the dialog from QS:
+ *     Settings > Notifications > Do Not Disturb > Duration for Quick Settings >  Ask every time
+ *
+ * Other names for DND (Do Not Disturb) include "Zen" and "Priority only".
+ */
+public class QSZenModeDialogMetricsLogger extends ZenModeDialogMetricsLogger {
+    private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
+
+    public QSZenModeDialogMetricsLogger(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void logOnEnableZenModeForever() {
+        super.logOnEnableZenModeForever();
+        mUiEventLogger.log(QSDndEvent.QS_DND_DIALOG_ENABLE_FOREVER);
+    }
+
+    @Override
+    public void logOnEnableZenModeUntilAlarm() {
+        super.logOnEnableZenModeUntilAlarm();
+        mUiEventLogger.log(QSDndEvent.QS_DND_DIALOG_ENABLE_UNTIL_ALARM);
+    }
+
+    @Override
+    public void logOnEnableZenModeUntilCountdown() {
+        super.logOnEnableZenModeUntilCountdown();
+        mUiEventLogger.log(QSDndEvent.QS_DND_DIALOG_ENABLE_UNTIL_COUNTDOWN);
+    }
+
+    @Override
+    public void logOnConditionSelected() {
+        super.logOnConditionSelected();
+        mUiEventLogger.log(QSDndEvent.QS_DND_CONDITION_SELECT);
+    }
+
+    @Override
+    public void logOnClickTimeButton(boolean up) {
+        super.logOnClickTimeButton(up);
+        mUiEventLogger.log(up
+                ? QSDndEvent.QS_DND_TIME_UP : QSDndEvent.QS_DND_TIME_DOWN);
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index eccf27e..0509a7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -73,6 +73,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardIndication;
@@ -132,6 +133,7 @@
     private final DevicePolicyManager mDevicePolicyManager;
     private final UserManager mUserManager;
     protected final @Main DelayableExecutor mExecutor;
+    protected final @Background DelayableExecutor mBackgroundExecutor;
     private final LockPatternUtils mLockPatternUtils;
     private final IActivityManager mIActivityManager;
     private final FalsingManager mFalsingManager;
@@ -203,6 +205,7 @@
             IBatteryStats iBatteryStats,
             UserManager userManager,
             @Main DelayableExecutor executor,
+            @Background DelayableExecutor bgExecutor,
             FalsingManager falsingManager,
             LockPatternUtils lockPatternUtils,
             ScreenLifecycle screenLifecycle,
@@ -220,6 +223,7 @@
         mBatteryInfo = iBatteryStats;
         mUserManager = userManager;
         mExecutor = executor;
+        mBackgroundExecutor = bgExecutor;
         mLockPatternUtils = lockPatternUtils;
         mIActivityManager = iActivityManager;
         mFalsingManager = falsingManager;
@@ -328,15 +332,22 @@
 
     private void updateDisclosure() {
         if (mOrganizationOwnedDevice) {
-            final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
-            final CharSequence disclosure = getDisclosureText(organizationName);
-            mRotateTextViewController.updateIndication(
-                    INDICATION_TYPE_DISCLOSURE,
-                    new KeyguardIndication.Builder()
-                            .setMessage(disclosure)
-                            .setTextColor(mInitialTextColorState)
-                            .build(),
-                    /* updateImmediately */ false);
+            mBackgroundExecutor.execute(() -> {
+                final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
+                final CharSequence disclosure = getDisclosureText(organizationName);
+
+                mExecutor.execute(() -> {
+                    if (mKeyguardStateController.isShowing()) {
+                        mRotateTextViewController.updateIndication(
+                              INDICATION_TYPE_DISCLOSURE,
+                              new KeyguardIndication.Builder()
+                                      .setMessage(disclosure)
+                                      .setTextColor(mInitialTextColorState)
+                                      .build(),
+                              /* updateImmediately */ false);
+                    }
+                });
+            });
         } else {
             mRotateTextViewController.hideIndication(INDICATION_TYPE_DISCLOSURE);
         }
@@ -364,26 +375,35 @@
     }
 
     private void updateOwnerInfo() {
-        String info = mLockPatternUtils.getDeviceOwnerInfo();
-        if (info == null) {
-            // Use the current user owner information if enabled.
-            final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
-                    KeyguardUpdateMonitor.getCurrentUser());
-            if (ownerInfoEnabled) {
-                info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+        // Check device owner info on a bg thread.
+        // It makes multiple IPCs that could block the thread it's run on.
+        mBackgroundExecutor.execute(() -> {
+            String info = mLockPatternUtils.getDeviceOwnerInfo();
+            if (info == null) {
+                // Use the current user owner information if enabled.
+                final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
+                        KeyguardUpdateMonitor.getCurrentUser());
+                if (ownerInfoEnabled) {
+                    info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
+                }
             }
-        }
-        if (!TextUtils.isEmpty(info)) {
-            mRotateTextViewController.updateIndication(
-                    INDICATION_TYPE_OWNER_INFO,
-                    new KeyguardIndication.Builder()
-                            .setMessage(info)
-                            .setTextColor(mInitialTextColorState)
-                            .build(),
-                    false);
-        } else {
-            mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
-        }
+
+            // Update the UI on the main thread.
+            final String finalInfo = info;
+            mExecutor.execute(() -> {
+                if (!TextUtils.isEmpty(finalInfo) && mKeyguardStateController.isShowing()) {
+                    mRotateTextViewController.updateIndication(
+                            INDICATION_TYPE_OWNER_INFO,
+                            new KeyguardIndication.Builder()
+                                    .setMessage(finalInfo)
+                                    .setTextColor(mInitialTextColorState)
+                                    .build(),
+                            false);
+                } else {
+                    mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
+                }
+            });
+        });
     }
 
     private void updateBattery(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index ee12cc5..7811401 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -336,6 +336,9 @@
     }
 
     private void setDozeAmountInternal(float dozeAmount) {
+        if (Float.compare(dozeAmount, mDozeAmount) == 0) {
+            return;
+        }
         mDozeAmount = dozeAmount;
         float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount);
         synchronized (mListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 06651f2..50a6207 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -46,4 +46,5 @@
         })
     }
 }
-const val TAG = "HeadsUpViewBinder"
\ No newline at end of file
+
+private const val TAG = "HeadsUpViewBinder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
new file mode 100644
index 0000000..c991376
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.interruption
+
+import android.service.notification.StatusBarNotification
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.log.dagger.NotificationLog
+import javax.inject.Inject
+
+class NotificationInterruptLogger @Inject constructor(
+    @NotificationLog val notifBuffer: LogBuffer,
+    @NotificationHeadsUpLog val hunBuffer: LogBuffer
+) {
+    fun logHeadsUpFeatureChanged(useHeadsUp: Boolean) {
+        hunBuffer.log(TAG, INFO, {
+            bool1 = useHeadsUp
+        }, {
+            "heads up is enabled=$bool1"
+        })
+    }
+
+    fun logWillDismissAll() {
+        hunBuffer.log(TAG, INFO, {
+        }, {
+            "dismissing any existing heads up notification on disable event"
+        })
+    }
+
+    fun logNoBubbleNotAllowed(sbn: StatusBarNotification) {
+        notifBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No bubble up: not allowed to bubble: $str1"
+        })
+    }
+
+    fun logNoBubbleNoMetadata(sbn: StatusBarNotification) {
+        notifBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No bubble up: notification: $str1 doesn't have valid metadata"
+        })
+    }
+
+    fun logNoHeadsUpFeatureDisabled() {
+        hunBuffer.log(TAG, DEBUG, {
+        }, {
+            "No heads up: no huns"
+        })
+    }
+
+    fun logNoHeadsUpPackageSnoozed(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No alerting: snoozed package: $str1"
+        })
+    }
+
+    fun logNoHeadsUpAlreadyBubbled(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No heads up: in unlocked shade where notification is shown as a bubble: $str1"
+        })
+    }
+
+    fun logNoHeadsUpSuppressedByDnd(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No heads up: suppressed by DND: $str1"
+        })
+    }
+
+    fun logNoHeadsUpNotImportant(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No heads up: unimportant notification: $str1"
+        })
+    }
+
+    fun logNoHeadsUpNotInUse(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No heads up: not in use: $str1"
+        })
+    }
+
+    fun logNoHeadsUpSuppressedBy(
+        sbn: StatusBarNotification,
+        suppressor: NotificationInterruptSuppressor
+    ) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+            str2 = suppressor.name
+        }, {
+            "No heads up: aborted by suppressor: $str2 sbnKey=$str1"
+        })
+    }
+
+    fun logHeadsUp(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "Heads up: $str1"
+        })
+    }
+
+    fun logNoAlertingFilteredOut(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No alerting: filtered notification: $str1"
+        })
+    }
+
+    fun logNoAlertingGroupAlertBehavior(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No alerting: suppressed due to group alert behavior: $str1"
+        })
+    }
+
+    fun logNoAlertingSuppressedBy(
+        sbn: StatusBarNotification,
+        suppressor: NotificationInterruptSuppressor,
+        awake: Boolean
+    ) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+            str2 = suppressor.name
+            bool1 = awake
+        }, {
+            "No alerting: aborted by suppressor: $str2 awake=$bool1 sbnKey=$str1"
+        })
+    }
+
+    fun logNoAlertingRecentFullscreen(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No alerting: recent fullscreen: $str1"
+        })
+    }
+
+    fun logNoPulsingSettingDisabled(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No pulsing: disabled by setting: $str1"
+        })
+    }
+
+    fun logNoPulsingBatteryDisabled(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No pulsing: disabled by battery saver: $str1"
+        })
+    }
+
+    fun logNoPulsingNoAlert(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No pulsing: notification shouldn't alert: $str1"
+        })
+    }
+
+    fun logNoPulsingNoAmbientEffect(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No pulsing: ambient effect suppressed: $str1"
+        })
+    }
+
+    fun logNoPulsingNotImportant(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "No pulsing: not important enough: $str1"
+        })
+    }
+
+    fun logPulsing(sbn: StatusBarNotification) {
+        hunBuffer.log(TAG, DEBUG, {
+            str1 = sbn.key
+        }, {
+            "Pulsing: $str1"
+        })
+    }
+}
+
+private const val TAG = "InterruptionStateProvider"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 2b4bc91..7ed2699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -40,7 +40,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.Compile;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -53,8 +52,6 @@
 @SysUISingleton
 public class NotificationInterruptStateProviderImpl implements NotificationInterruptStateProvider {
     private static final String TAG = "InterruptionStateProvider";
-    private static final boolean DEBUG = Compile.IS_DEBUG;
-    private static final boolean DEBUG_HEADS_UP = Compile.IS_DEBUG;
     private static final boolean ENABLE_HEADS_UP = true;
     private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
@@ -67,7 +64,8 @@
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final BatteryController mBatteryController;
     private final ContentObserver mHeadsUpObserver;
-    private HeadsUpManager mHeadsUpManager;
+    private final HeadsUpManager mHeadsUpManager;
+    private final NotificationInterruptLogger mLogger;
 
     @VisibleForTesting
     protected boolean mUseHeadsUp = false;
@@ -82,6 +80,7 @@
             BatteryController batteryController,
             StatusBarStateController statusBarStateController,
             HeadsUpManager headsUpManager,
+            NotificationInterruptLogger logger,
             @Main Handler mainHandler) {
         mContentResolver = contentResolver;
         mPowerManager = powerManager;
@@ -91,6 +90,7 @@
         mNotificationFilter = notificationFilter;
         mStatusBarStateController = statusBarStateController;
         mHeadsUpManager = headsUpManager;
+        mLogger = logger;
         mHeadsUpObserver = new ContentObserver(mainHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -100,11 +100,10 @@
                         mContentResolver,
                         Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                         Settings.Global.HEADS_UP_OFF);
-                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                mLogger.logHeadsUpFeatureChanged(mUseHeadsUp);
                 if (wasUsing != mUseHeadsUp) {
                     if (!mUseHeadsUp) {
-                        Log.d(TAG, "dismissing any existing heads up notification on "
-                                + "disable event");
+                        mLogger.logWillDismissAll();
                         mHeadsUpManager.releaseAllImmediately();
                     }
                 }
@@ -141,19 +140,14 @@
         }
 
         if (!entry.canBubble()) {
-            if (DEBUG) {
-                Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
-            }
+            mLogger.logNoBubbleNotAllowed(sbn);
             return false;
         }
 
         if (entry.getBubbleMetadata() == null
                 || (entry.getBubbleMetadata().getShortcutId() == null
                     && entry.getBubbleMetadata().getIntent() == null)) {
-            if (DEBUG) {
-                Log.d(TAG, "No bubble up: notification: " + sbn.getKey()
-                        + " doesn't have valid metadata");
-            }
+            mLogger.logNoBubbleNoMetadata(sbn);
             return false;
         }
 
@@ -185,9 +179,7 @@
         StatusBarNotification sbn = entry.getSbn();
 
         if (!mUseHeadsUp) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: no huns");
-            }
+            mLogger.logNoHeadsUpFeatureDisabled();
             return false;
         }
 
@@ -200,32 +192,23 @@
         }
 
         if (isSnoozedPackage(sbn)) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
-            }
+            mLogger.logNoHeadsUpPackageSnoozed(sbn);
             return false;
         }
 
         boolean inShade = mStatusBarStateController.getState() == SHADE;
         if (entry.isBubble() && inShade) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
-                        + "bubble: " + sbn.getKey());
-            }
+            mLogger.logNoHeadsUpAlreadyBubbled(sbn);
             return false;
         }
 
         if (entry.shouldSuppressPeek()) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
-            }
+            mLogger.logNoHeadsUpSuppressedByDnd(sbn);
             return false;
         }
 
         if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
-            }
+            mLogger.logNoHeadsUpNotImportant(sbn);
             return false;
         }
 
@@ -238,21 +221,17 @@
         boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
 
         if (!inUse) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
-            }
+            mLogger.logNoHeadsUpNotInUse(sbn);
             return false;
         }
 
         for (int i = 0; i < mSuppressors.size(); i++) {
             if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
-                if (DEBUG_HEADS_UP) {
-                    Log.d(TAG, "No heads up: aborted by suppressor: "
-                            + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey());
-                }
+                mLogger.logNoHeadsUpSuppressedBy(sbn, mSuppressors.get(i));
                 return false;
             }
         }
+        mLogger.logHeadsUp(sbn);
         return true;
     }
 
@@ -267,39 +246,30 @@
         StatusBarNotification sbn = entry.getSbn();
 
         if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No pulsing: disabled by setting: " + sbn.getKey());
-            }
+            mLogger.logNoPulsingSettingDisabled(sbn);
             return false;
         }
 
         if (mBatteryController.isAodPowerSave()) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No pulsing: disabled by battery saver: " + sbn.getKey());
-            }
+            mLogger.logNoPulsingBatteryDisabled(sbn);
             return false;
         }
 
         if (!canAlertCommon(entry)) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
-            }
+            mLogger.logNoPulsingNoAlert(sbn);
             return false;
         }
 
         if (entry.shouldSuppressAmbient()) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
-            }
+            mLogger.logNoPulsingNoAmbientEffect(sbn);
             return false;
         }
 
         if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
-            }
+            mLogger.logNoPulsingNotImportant(sbn);
             return false;
         }
+        mLogger.logPulsing(sbn);
         return true;
     }
 
@@ -313,34 +283,25 @@
         StatusBarNotification sbn = entry.getSbn();
 
         if (mNotificationFilter.shouldFilterOut(entry)) {
-            if (DEBUG || DEBUG_HEADS_UP) {
-                Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
-            }
+            mLogger.logNoAlertingFilteredOut(sbn);
             return false;
         }
 
         // Don't alert notifications that are suppressed due to group alert behavior
         if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
-            if (DEBUG || DEBUG_HEADS_UP) {
-                Log.d(TAG, "No alerting: suppressed due to group alert behavior");
-            }
+            mLogger.logNoAlertingGroupAlertBehavior(sbn);
             return false;
         }
 
         for (int i = 0; i < mSuppressors.size(); i++) {
             if (mSuppressors.get(i).suppressInterruptions(entry)) {
-                if (DEBUG_HEADS_UP) {
-                    Log.d(TAG, "No alerting: aborted by suppressor: "
-                            + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey());
-                }
+                mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ false);
                 return false;
             }
         }
 
         if (entry.hasJustLaunchedFullScreenIntent()) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No alerting: recent fullscreen: " + sbn.getKey());
-            }
+            mLogger.logNoAlertingRecentFullscreen(sbn);
             return false;
         }
 
@@ -358,10 +319,7 @@
 
         for (int i = 0; i < mSuppressors.size(); i++) {
             if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) {
-                if (DEBUG_HEADS_UP) {
-                    Log.d(TAG, "No alerting: aborted by suppressor: "
-                            + mSuppressors.get(i).getName() + " sbnKey=" + sbn.getKey());
-                }
+                mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ true);
                 return false;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index a3fe47c..dad06d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -81,7 +81,7 @@
     private int mNotificationHeaderMargin;
 
     private int mNotificationTopPadding;
-    private float mCollapsedBottompadding;
+    private float mCollapsedBottomPadding;
     private boolean mChildrenExpanded;
     private ExpandableNotificationRow mContainingNotification;
     private TextView mOverflowNumber;
@@ -140,17 +140,17 @@
 
     private void initDimens() {
         Resources res = getResources();
-        mChildPadding = res.getDimensionPixelSize(R.dimen.notification_children_padding);
-        mDividerHeight = res.getDimensionPixelSize(
+        mChildPadding = res.getDimensionPixelOffset(R.dimen.notification_children_padding);
+        mDividerHeight = res.getDimensionPixelOffset(
                 R.dimen.notification_children_container_divider_height);
         mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha);
-        mNotificationHeaderMargin = res.getDimensionPixelSize(
+        mNotificationHeaderMargin = res.getDimensionPixelOffset(
                 R.dimen.notification_children_container_margin_top);
-        mNotificationTopPadding = res.getDimensionPixelSize(
+        mNotificationTopPadding = res.getDimensionPixelOffset(
                 R.dimen.notification_children_container_top_padding);
         mHeaderHeight = mNotificationHeaderMargin + mNotificationTopPadding;
-        mCollapsedBottompadding = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.notification_content_margin);
+        mCollapsedBottomPadding = res.getDimensionPixelOffset(
+                R.dimen.notification_children_collapsed_bottom_padding);
         mEnableShadowOnChildNotifications =
                 res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
         mShowGroupCountInExpander =
@@ -159,7 +159,7 @@
                 res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded);
         mHideDividersDuringExpand =
                 res.getBoolean(R.bool.config_hideDividersDuringExpand);
-        mTranslationForHeader = res.getDimensionPixelSize(
+        mTranslationForHeader = res.getDimensionPixelOffset(
                 com.android.internal.R.dimen.notification_content_margin)
                 - mNotificationHeaderMargin;
         mHybridGroupManager.initDimens();
@@ -560,10 +560,10 @@
             visibleChildren++;
         }
         if (mUserLocked) {
-            intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
+            intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottomPadding, 0.0f,
                     expandFactor);
         } else if (!childrenExpanded) {
-            intrinsicHeight += mCollapsedBottompadding;
+            intrinsicHeight += mCollapsedBottomPadding;
         }
         return intrinsicHeight;
     }
@@ -1163,7 +1163,7 @@
             minExpandHeight += child.getSingleLineView().getHeight();
             visibleChildren++;
         }
-        minExpandHeight += mCollapsedBottompadding;
+        minExpandHeight += mCollapsedBottomPadding;
         return minExpandHeight;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e1116f8..d1c63e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -290,11 +290,11 @@
 
         @Override
         public void onThemeChanged() {
-            updateShowEmptyShadeView();
             mView.updateCornerRadius();
             mView.updateBgColor();
             mView.updateDecorViews();
             mView.reinflateViews();
+            updateShowEmptyShadeView();
             updateFooter();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e24cd3e..3a3f581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -376,6 +376,11 @@
 
         final float stackHeight = ambientState.getStackHeight()  - shelfHeight - scrimPadding;
         final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
+        if (stackEndHeight == 0f) {
+            // This should not happen, since even when the shade is empty we show EmptyShadeView
+            // but check just in case, so we don't return infinity or NaN.
+            return 0f;
+        }
         return stackHeight / stackEndHeight;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index eb5db29..357a12b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -18,13 +18,16 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.content.Intent;
 import android.os.UserManager;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.FooterActionsView;
@@ -32,6 +35,7 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.user.UserSwitchDialogController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.UserSwitcherActivity;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
@@ -43,6 +47,7 @@
     private final QSDetailDisplayer mQsDetailDisplayer;
     private final FalsingManager mFalsingManager;
     private final UserSwitchDialogController mUserSwitchDialogController;
+    private final ActivityStarter mActivityStarter;
     private final FeatureFlags mFeatureFlags;
 
     private UserSwitcherController.BaseUserAdapter mUserListener;
@@ -54,7 +59,14 @@
                 return;
             }
 
-            if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
+            if (mFeatureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+                Intent intent = new Intent(v.getContext(), UserSwitcherActivity.class);
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+
+                mActivityStarter.startActivity(intent, true /* dismissShade */,
+                        ActivityLaunchAnimator.Controller.fromView(v, null),
+                        true /* showOverlockscreenwhenlocked */);
+            } else if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
                 mUserSwitchDialogController.showDialog(v);
             } else {
                 View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
@@ -76,31 +88,35 @@
         private final QSDetailDisplayer mQsDetailDisplayer;
         private final FalsingManager mFalsingManager;
         private final UserSwitchDialogController mUserSwitchDialogController;
+        private final ActivityStarter mActivityStarter;
         private final FeatureFlags mFeatureFlags;
 
         @Inject
         public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
                 QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager,
-                UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags) {
+                UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags,
+                ActivityStarter activityStarter) {
             mUserManager = userManager;
             mUserSwitcherController = userSwitcherController;
             mQsDetailDisplayer = qsDetailDisplayer;
             mFalsingManager = falsingManager;
             mUserSwitchDialogController = userSwitchDialogController;
+            mActivityStarter = activityStarter;
             mFeatureFlags = featureFlags;
         }
 
         public MultiUserSwitchController create(FooterActionsView view) {
             return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
                     mUserManager, mUserSwitcherController, mQsDetailDisplayer,
-                    mFalsingManager, mUserSwitchDialogController, mFeatureFlags);
+                    mFalsingManager, mUserSwitchDialogController, mFeatureFlags,
+                    mActivityStarter);
         }
     }
 
     private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
             UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer,
             FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags, ActivityStarter activityStarter) {
         super(view);
         mUserManager = userManager;
         mUserSwitcherController = userSwitcherController;
@@ -108,6 +124,7 @@
         mFalsingManager = falsingManager;
         mUserSwitchDialogController = userSwitchDialogController;
         mFeatureFlags = featureFlags;
+        mActivityStarter = activityStarter;
     }
 
     @Override
@@ -166,5 +183,4 @@
         return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
                 getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user)));
     }
-
 }
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 f667c10..62a96ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1577,6 +1577,7 @@
 
             constraintSet.applyTo(mNotificationContainerParent);
         }
+        mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index b457ebf..7c9e597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -102,17 +102,21 @@
     private fun updateBottomSpacing() {
         val (containerPadding, notificationsMargin) = calculateBottomSpacing()
         var qsScrollPaddingBottom = 0
-        if (!(splitShadeEnabled || isQSCustomizing || isQSDetailShowing || isGestureNavigation ||
-                        taskbarVisible)) {
+        val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
+        if (!newFooter && !(splitShadeEnabled || isQSCustomizing || isQSDetailShowing ||
+                        isGestureNavigation || taskbarVisible)) {
             // no taskbar, portrait, navigation buttons enabled:
             // padding is needed so QS can scroll up over bottom insets - to reach the point when
             // the whole QS is above bottom insets
             qsScrollPaddingBottom = bottomStableInsets
+        } else if (newFooter && !(isQSCustomizing || isQSDetailShowing)) {
+            // With the new footer, we also want this padding in the bottom in these cases
+            qsScrollPaddingBottom = bottomStableInsets
         }
         mView.setPadding(0, 0, 0, containerPadding)
         mView.setNotificationsMarginBottom(notificationsMargin)
-        if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
-            mView.setQSContainerPaddingBottom(notificationsMargin)
+        if (newFooter) {
+            mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
         } else {
             mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
         }
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 4748d9c..716e8c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -40,6 +40,7 @@
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -139,6 +140,7 @@
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DelegateLaunchAnimatorController;
+import com.android.systemui.animation.RemoteTransitionAdapter;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -4087,7 +4089,12 @@
             @Nullable RemoteAnimationAdapter animationAdapter) {
         ActivityOptions options;
         if (animationAdapter != null) {
-            options = ActivityOptions.makeRemoteAnimation(animationAdapter);
+            if (ENABLE_SHELL_TRANSITIONS) {
+                options = ActivityOptions.makeRemoteTransition(
+                        RemoteTransitionAdapter.adaptRemoteAnimation(animationAdapter));
+            } else {
+                options = ActivityOptions.makeRemoteAnimation(animationAdapter);
+            }
         } else {
             options = ActivityOptions.makeBasic();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 1b73595..3ece240 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -81,7 +81,6 @@
 import com.android.systemui.qs.tiles.UserDetailView;
 import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.CreateUserActivity;
@@ -159,7 +158,7 @@
     private final AtomicBoolean mGuestIsResetting;
     private final AtomicBoolean mGuestCreationScheduled;
     private FalsingManager mFalsingManager;
-    private NotificationShadeWindowView mRootView;
+    private View mView;
 
     @Inject
     public UserSwitcherController(Context context,
@@ -458,6 +457,19 @@
         }
     }
 
+    /**
+     * @return UserRecord for the current user
+     */
+    public @Nullable UserRecord getCurrentUserRecord() {
+        for (int i = 0; i < mUsers.size(); ++i) {
+            UserRecord userRecord = mUsers.get(i);
+            if (userRecord.isCurrent) {
+                return userRecord;
+            }
+        }
+        return null;
+    }
+
     @VisibleForTesting
     void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
         int id;
@@ -504,7 +516,7 @@
     protected void switchToUserId(int id) {
         try {
             mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
-                    .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mRootView)
+                    .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
                     .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
             mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
             pauseRefreshUsers();
@@ -823,8 +835,11 @@
         return guest.id;
     }
 
-    public void init(NotificationShadeWindowView notificationShadeWindowView) {
-        mRootView = notificationShadeWindowView;
+    /**
+     * Require a view for jank detection
+     */
+    public void init(View view) {
+        mView = view;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 0ad0984..469d54f 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -16,21 +16,32 @@
 
 package com.android.systemui.user;
 
+import android.app.Activity;
+
 import com.android.settingslib.users.EditUserInfoController;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 /**
  * Dagger module for User related classes.
  */
 @Module
-public class UserModule {
+public abstract class UserModule {
 
     private static final String FILE_PROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
 
     @Provides
-    EditUserInfoController provideEditUserInfoController() {
+    public static EditUserInfoController provideEditUserInfoController() {
         return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
     }
+
+    /** Provides UserSwitcherActivity */
+    @Binds
+    @IntoMap
+    @ClassKey(UserSwitcherActivity.class)
+    public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
new file mode 100644
index 0000000..d6a8ab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2022 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.user
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.InsetDrawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Bundle
+import android.os.UserManager
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowInsets.Type
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.ImageView
+import android.widget.TextView
+
+import androidx.constraintlayout.helper.widget.Flow
+
+import com.android.internal.util.UserIcons
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord
+import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
+import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
+import com.android.systemui.util.LifecycleActivity
+
+import javax.inject.Inject
+
+private const val USER_VIEW = "user_view"
+
+/**
+ * Support a fullscreen user switcher
+ */
+class UserSwitcherActivity @Inject constructor(
+    private val userSwitcherController: UserSwitcherController,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val layoutInflater: LayoutInflater,
+    private val falsingManager: FalsingManager,
+    private val userManager: UserManager
+) : LifecycleActivity() {
+
+    private lateinit var parent: ViewGroup
+    private lateinit var broadcastReceiver: BroadcastReceiver
+    private var popupMenu: UserSwitcherPopupMenu? = null
+    private lateinit var addButton: View
+    private var addUserItem: UserRecord? = null
+    private var addGuestItem: UserRecord? = null
+
+    private val adapter = object : BaseUserAdapter(userSwitcherController) {
+        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+            val item = getItem(position)
+            var view = convertView as ViewGroup?
+            if (view == null) {
+                view = layoutInflater.inflate(
+                    R.layout.user_switcher_fullscreen_item,
+                    parent,
+                    false
+                ) as ViewGroup
+            }
+            (view.getChildAt(0) as ImageView).apply {
+                setImageDrawable(getDrawable(item))
+            }
+            (view.getChildAt(1) as TextView).apply {
+                setText(getName(getContext(), item))
+            }
+
+            view.setEnabled(item.isSwitchToEnabled)
+            view.setAlpha(
+                if (view.isEnabled()) {
+                    USER_SWITCH_ENABLED_ALPHA
+                } else {
+                    USER_SWITCH_DISABLED_ALPHA
+                }
+            )
+            view.setTag(USER_VIEW)
+            return view
+        }
+
+        fun findUserIcon(item: UserRecord): Drawable {
+            if (item.info == null) {
+                return getIconDrawable(this@UserSwitcherActivity, item)
+            }
+            val userIcon = userManager.getUserIcon(item.info.id)
+            if (userIcon != null) {
+                return BitmapDrawable(userIcon)
+            }
+            return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
+        }
+
+        private fun getDrawable(item: UserRecord): Drawable {
+            var drawable = if (item.isCurrent && item.isGuest) {
+                getDrawable(R.drawable.ic_avatar_guest_user)
+            } else {
+                findUserIcon(item)
+            }
+            drawable.mutate()
+
+            if (!item.isCurrent && !item.isSwitchToEnabled) {
+                drawable.setTint(
+                    resources.getColor(
+                        R.color.kg_user_switcher_restricted_avatar_icon_color,
+                        getTheme()
+                    )
+                )
+            }
+
+            val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate()
+                as LayerDrawable
+            if (item == userSwitcherController.getCurrentUserRecord()) {
+                (ld.getDrawable(1) as GradientDrawable).apply {
+                    val stroke = resources
+                        .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
+                    val color = Utils.getColorAttrDefaultColor(
+                        this@UserSwitcherActivity,
+                        com.android.internal.R.attr.colorAccentPrimary
+                    )
+
+                    setStroke(stroke, color)
+                }
+            }
+
+            ld.addLayer(
+                InsetDrawable(
+                    drawable,
+                    resources.getDimensionPixelSize(
+                        R.dimen.user_switcher_icon_large_margin
+                    )
+                )
+            )
+
+            return ld
+        }
+
+        override fun notifyDataSetChanged() {
+            super.notifyDataSetChanged()
+            buildUserViews()
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(R.layout.user_switcher_fullscreen)
+
+        parent = requireViewById<ViewGroup>(R.id.user_switcher_root).apply {
+            setOnApplyWindowInsetsListener {
+                v: View, insets: WindowInsets ->
+                    v.apply {
+                        val l = getPaddingLeft()
+                        val t = getPaddingTop()
+                        val r = getPaddingRight()
+                        setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
+                    }
+
+                WindowInsets.CONSUMED
+            }
+        }
+
+        requireViewById<View>(R.id.cancel).apply {
+            setOnClickListener {
+                _ -> finish()
+            }
+        }
+
+        addButton = requireViewById<View>(R.id.add).apply {
+            setOnClickListener {
+                _ -> showPopupMenu()
+            }
+        }
+
+        userSwitcherController.init(parent)
+        initBroadcastReceiver()
+        buildUserViews()
+    }
+
+    private fun showPopupMenu() {
+        val items = mutableListOf<UserRecord>()
+        addUserItem?.let { items.add(it) }
+        addGuestItem?.let { items.add(it) }
+
+        var popupMenuAdapter = ItemAdapter(
+            this,
+            R.layout.user_switcher_fullscreen_popup_item,
+            layoutInflater,
+            { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) },
+            { item: UserRecord -> adapter.findUserIcon(item) }
+        )
+        popupMenuAdapter.addAll(items)
+
+        popupMenu = UserSwitcherPopupMenu(this, falsingManager).apply {
+            setAnchorView(addButton)
+            setAdapter(popupMenuAdapter)
+            setOnItemClickListener {
+                parent: AdapterView<*>, view: View, pos: Int, id: Long ->
+                    if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) {
+                        return@setOnItemClickListener
+                    }
+                    // -1 for the header
+                    val item = popupMenuAdapter.getItem(pos - 1)
+                    adapter.onUserListItemClicked(item)
+
+                    dismiss()
+                    popupMenu = null
+            }
+
+            show()
+        }
+    }
+
+    private fun buildUserViews() {
+        var count = 0
+        var start = 0
+        for (i in 0 until parent.getChildCount()) {
+            if (parent.getChildAt(i).getTag() == USER_VIEW) {
+                if (count == 0) start = i
+                count++
+            }
+        }
+        parent.removeViews(start, count)
+
+        val flow = requireViewById<Flow>(R.id.flow)
+        for (i in 0 until adapter.getCount()) {
+            val item = adapter.getItem(i)
+            if (item.isAddUser) {
+                addUserItem = item
+            } else if (item.isGuest && item.info == null) {
+                addGuestItem = item
+            } else {
+                val userView = adapter.getView(i, null, parent)
+                userView.setId(View.generateViewId())
+                parent.addView(userView)
+
+                // Views must have an id and a parent in order for Flow to lay them out
+                flow.addView(userView)
+
+                userView.setOnClickListener { v ->
+                    if (falsingManager.isFalseTap(LOW_PENALTY) || !v.isEnabled()) {
+                        return@setOnClickListener
+                    }
+
+                    if (!item.isCurrent || item.isGuest) {
+                        adapter.onUserListItemClicked(item)
+                    }
+                }
+            }
+        }
+
+        if (addUserItem != null || addGuestItem != null) {
+            addButton.visibility = View.VISIBLE
+        } else {
+            addButton.visibility = View.GONE
+        }
+    }
+
+    override fun onBackPressed() {
+        finish()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+    }
+
+    private fun initBroadcastReceiver() {
+        broadcastReceiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                val action = intent.getAction()
+                if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                    finish()
+                }
+            }
+        }
+
+        val filter = IntentFilter()
+        filter.addAction(Intent.ACTION_SCREEN_OFF)
+        broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
+    }
+
+    private class ItemAdapter(
+        val parentContext: Context,
+        val resource: Int,
+        val layoutInflater: LayoutInflater,
+        val textGetter: (UserRecord) -> String,
+        val iconGetter: (UserRecord) -> Drawable
+    ) : ArrayAdapter<UserRecord>(parentContext, resource) {
+
+        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+            val item = getItem(position)
+            val view = convertView ?: layoutInflater.inflate(resource, parent, false)
+
+            view.requireViewById<ImageView>(R.id.icon).apply {
+                setImageDrawable(iconGetter(item))
+            }
+            view.requireViewById<TextView>(R.id.text).apply {
+                setText(textGetter(item))
+            }
+
+            return view
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
new file mode 100644
index 0000000..8963547
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 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.user
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.ShapeDrawable
+import android.view.View
+import android.view.View.MeasureSpec
+import android.widget.ListAdapter
+import android.widget.ListPopupWindow
+import android.widget.ListView
+
+import com.android.systemui.R
+import com.android.systemui.plugins.FalsingManager
+
+/**
+ * Popup menu for displaying items on the fullscreen user switcher.
+ */
+class UserSwitcherPopupMenu(
+    private val context: Context,
+    private val falsingManager: FalsingManager
+) : ListPopupWindow(context) {
+
+    private val res = context.resources
+    private var adapter: ListAdapter? = null
+
+    init {
+        setBackgroundDrawable(
+            res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
+        )
+        setModal(true)
+        setOverlapAnchor(true)
+    }
+
+    override fun setAdapter(adapter: ListAdapter?) {
+        super.setAdapter(adapter)
+        this.adapter = adapter
+    }
+
+    /**
+      * Show the dialog.
+      */
+    override fun show() {
+        // need to call show() first in order to construct the listView
+        super.show()
+        val listView = getListView()
+
+        listView.setVerticalScrollBarEnabled(false)
+        listView.setHorizontalScrollBarEnabled(false)
+
+        // Creates a transparent spacer between items
+        val shape = ShapeDrawable()
+        shape.setAlpha(0)
+        listView.setDivider(shape)
+        listView.setDividerHeight(res.getDimensionPixelSize(
+            R.dimen.bouncer_user_switcher_popup_divider_height))
+
+        val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
+        listView.addHeaderView(createSpacer(height), null, false)
+        listView.addFooterView(createSpacer(height), null, false)
+        setWidth(findMaxWidth(listView))
+
+        super.show()
+    }
+
+    private fun findMaxWidth(listView: ListView): Int {
+        var maxWidth = 0
+        adapter?.let {
+            val parentWidth = res.getDisplayMetrics().widthPixels
+            val spec = MeasureSpec.makeMeasureSpec(
+                (parentWidth * 0.25).toInt(),
+                MeasureSpec.AT_MOST
+            )
+
+            for (i in 0 until it.getCount()) {
+                val child = it.getView(i, null, listView)
+                child.measure(spec, MeasureSpec.UNSPECIFIED)
+                maxWidth = Math.max(child.getMeasuredWidth(), maxWidth)
+            }
+        }
+        return maxWidth
+    }
+
+    private fun createSpacer(height: Int): View {
+        return object : View(context) {
+            override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+                setMeasuredDimension(1, height)
+            }
+
+            override fun draw(canvas: Canvas) {
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
index 0e04871..cfceefa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
@@ -65,6 +65,17 @@
 
     private var initialPadding = mPaddingTop // All vertical padding is the same
 
+    private var originalMaxLines = 1
+    var alwaysSingleLine: Boolean = false
+        set(value) {
+            field = value
+            if (field) {
+                textView?.setSingleLine()
+            } else {
+                textView?.maxLines = originalMaxLines
+            }
+        }
+
     init {
         if (orientation != HORIZONTAL) {
             throw IllegalStateException("This view should always have horizontal orientation")
@@ -120,7 +131,7 @@
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         textView?.let { tv ->
-            if (tv.lineCount < 2) {
+            if (tv.lineCount < 2 || alwaysSingleLine) {
                 setMeasuredDimension(measuredWidth, singleLineHeightPx)
                 mPaddingBottom = 0
                 mPaddingTop = 0
@@ -133,7 +144,9 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        textView = findViewById(textViewId)
+        textView = findViewById<TextView>(textViewId)?.also {
+            originalMaxLines = it.maxLines
+        }
     }
 
     override fun onConfigurationChanged(newConfig: Configuration?) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 0cd4fb9..7bb987c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -56,12 +56,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.qs.QSDndEvent;
-import com.android.systemui.qs.QSEvents;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.io.FileDescriptor;
@@ -106,7 +103,6 @@
     private final TransitionHelper mTransitionHelper = new TransitionHelper();
     private final Uri mForeverId;
     private final ConfigurableTexts mConfigurableTexts;
-    private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
 
     private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
 
@@ -666,7 +662,6 @@
                     tag.rb.setChecked(true);
                     if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId);
                     MetricsLogger.action(mContext, MetricsEvent.QS_DND_CONDITION_SELECT);
-                    mUiEventLogger.log(QSDndEvent.QS_DND_CONDITION_SELECT);
                     select(tag.condition);
                     announceConditionSelection(tag);
                 }
@@ -772,7 +767,6 @@
 
     private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
         MetricsLogger.action(mContext, MetricsEvent.QS_DND_TIME, up);
-        mUiEventLogger.log(up ? QSDndEvent.QS_DND_TIME_UP : QSDndEvent.QS_DND_TIME_DOWN);
         Condition newCondition = null;
         final int N = MINUTE_BUCKETS.length;
         if (mBucketIndex == -1) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index f5fa0d0..91a9f9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -64,6 +64,10 @@
     private lateinit var uiEventLogger: UiEventLogger
     @Mock
     private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var securityFooterController: QSSecurityFooter
+    @Mock
+    private lateinit var fgsManagerController: QSFgsManagerFooter
 
     private lateinit var controller: FooterActionsController
 
@@ -90,7 +94,8 @@
 
         controller = FooterActionsController(view, multiUserSwitchControllerFactory,
                 activityStarter, userManager, userTracker, userInfoController,
-                deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
+                deviceProvisionedController, securityFooterController, fgsManagerController,
+                falsingManager, metricsLogger, fakeTunerService,
                 globalActionsDialog, uiEventLogger, showPMLiteButton = true, fakeSettings,
                 Handler(testableLooper.looper), featureFlags)
         controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index b5ce706..b2ca62f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTileView;
 import com.android.systemui.qs.customize.QSCustomizerController;
@@ -106,6 +107,8 @@
     Resources mResources;
     @Mock
     Configuration mConfiguration;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     private QSPanelController mController;
 
@@ -133,7 +136,7 @@
                 mTunerService, mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
-                mFalsingManager, mCommandQueue
+                mFalsingManager, mCommandQueue, mFeatureFlags
         );
 
         mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 3500c18..4ae19332 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -162,6 +162,20 @@
         assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
     }
 
+    @Test
+    fun testBottomPadding() {
+        mQsPanel.setUseNewFooter(false)
+
+        mQsPanel.updatePadding()
+        assertThat(mQsPanel.paddingBottom).isEqualTo(0)
+
+        mQsPanel.setUseNewFooter(true)
+
+        mQsPanel.updatePadding()
+        assertThat(mQsPanel.paddingBottom)
+                .isEqualTo(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
+    }
+
     private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
             context.resources.configuration.apply { orientation = newOrientation }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 770cf2c..2b7fa42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -22,12 +22,14 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AlertDialog;
 import android.content.ComponentName;
 import android.content.DialogInterface;
 import android.content.pm.UserInfo;
@@ -50,6 +52,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.SecurityController;
@@ -57,10 +60,13 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /*
  * Compile and run the whole SystemUI test suite:
    runtest --path frameworks/base/packages/SystemUI/tests
@@ -94,20 +100,24 @@
     private UserTracker mUserTracker;
     @Mock
     private ActivityStarter mActivityStarter;
+    @Mock
+    private DialogLaunchAnimator mDialogLaunchAnimator;
+
+    private TestableLooper mTestableLooper;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Looper looper = TestableLooper.get(this).getLooper();
+        mTestableLooper = TestableLooper.get(this);
+        Looper looper = mTestableLooper.getLooper();
         when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
         mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
                 .replace("ImageView", TestableImageView.class)
                 .build().inflate(R.layout.quick_settings_security_footer, null, false);
         mFooter = new QSSecurityFooter(mRootView, mUserTracker, new Handler(looper),
-                mActivityStarter, mSecurityController, looper);
+                mActivityStarter, mSecurityController, mDialogLaunchAnimator, looper);
         mFooterText = mRootView.findViewById(R.id.footer_text);
         mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
-        mFooter.setHostEnvironment(null);
 
         when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
                 .thenReturn(DEVICE_OWNER_COMPONENT);
@@ -650,8 +660,6 @@
 
     @Test
     public void testNoClickWhenGone() {
-        QSTileHost mockHost = mock(QSTileHost.class);
-        mFooter.setHostEnvironment(mockHost);
         mFooter.refreshState();
 
         TestableLooper.get(this).processAllMessages();
@@ -660,7 +668,7 @@
         mFooter.onClick(mFooter.getView());
 
         // Proxy for dialog being created
-        verify(mockHost, never()).collapsePanels();
+        verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
     }
 
     @Test
@@ -700,6 +708,16 @@
     }
 
     @Test
+    public void testDialogUsesDialogLauncher() {
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        mFooter.onClick(mRootView);
+
+        mTestableLooper.processAllMessages();
+
+        verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView));
+    }
+
+    @Test
     public void testCreateDialogViewForFinancedDevice() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName())
@@ -707,12 +725,6 @@
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
-        // Initialize AlertDialog which sets the text for the negative button, which is used when
-        // creating the dialog for a financed device.
-        mFooter.showDeviceMonitoringDialog();
-        // The above statement would display the Quick Settings dialog which requires user input,
-        // so simulate the press to continue with the unit test (otherwise, it is stuck).
-        mFooter.onClick(null, DialogInterface.BUTTON_NEGATIVE);
         View view = mFooter.createDialogView();
 
         TextView managementSubtitle = view.findViewById(R.id.device_management_subtitle);
@@ -727,6 +739,49 @@
                 mFooter.getSettingsButton());
     }
 
+    @Test
+    public void testFinancedDeviceUsesSettingsButtonText() {
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        when(mSecurityController.getDeviceOwnerOrganizationName())
+                .thenReturn(MANAGING_ORGANIZATION);
+        when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+                .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
+
+        mFooter.showDeviceMonitoringDialog();
+        ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
+
+        mTestableLooper.processAllMessages();
+        verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any());
+
+        AlertDialog dialog = dialogCaptor.getValue();
+        dialog.create();
+
+        assertEquals(mFooter.getSettingsButton(),
+                dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText());
+
+        dialog.dismiss();
+    }
+
+    @Test
+    public void testVisibilityListener() {
+        final AtomicInteger lastVisibility = new AtomicInteger(-1);
+        VisibilityChangedDispatcher.OnVisibilityChangedListener listener =
+                (VisibilityChangedDispatcher.OnVisibilityChangedListener) lastVisibility::set;
+
+        mFooter.setOnVisibilityChangedListener(listener);
+
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        mFooter.refreshState();
+        mTestableLooper.processAllMessages();
+        assertEquals(View.VISIBLE, lastVisibility.get());
+
+        when(mSecurityController.isDeviceManaged()).thenReturn(false);
+        mFooter.refreshState();
+        mTestableLooper.processAllMessages();
+        assertEquals(View.GONE, lastVisibility.get());
+    }
+
+
     private CharSequence addLink(CharSequence description) {
         final SpannableStringBuilder message = new SpannableStringBuilder();
         message.append(description);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 98e2857..5924329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -229,8 +229,8 @@
         mController = new KeyguardIndicationController(mContext, mWakeLockBuilder,
                 mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
                 mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
-                mUserManager, mExecutor, mFalsingManager, mLockPatternUtils, mScreenLifecycle,
-                mIActivityManager, mKeyguardBypassController);
+                mUserManager, mExecutor, mExecutor,  mFalsingManager, mLockPatternUtils,
+                mScreenLifecycle, mIActivityManager, mKeyguardBypassController);
         mController.init();
         mController.setIndicationArea(mIndicationArea);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -331,6 +331,7 @@
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
         when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyHideIndication(INDICATION_TYPE_DISCLOSURE);
     }
@@ -341,6 +342,7 @@
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
     }
@@ -353,6 +355,7 @@
                 new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE)));
         when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(null);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
     }
@@ -363,6 +366,7 @@
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
     }
@@ -375,6 +379,7 @@
                 new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE)));
         when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(ORGANIZATION_NAME);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
     }
@@ -387,6 +392,7 @@
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureGeneric);
         reset(mRotateTextViewController);
@@ -394,12 +400,14 @@
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
         when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mDisclosureWithOrganization);
         reset(mRotateTextViewController);
 
         when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyHideIndication(INDICATION_TYPE_DISCLOSURE);
     }
@@ -413,6 +421,7 @@
         when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
         sendUpdateDisclosureBroadcast();
+        mExecutor.runAllReady();
 
         verifyIndicationMessage(INDICATION_TYPE_DISCLOSURE, mFinancedDisclosureWithOrganization);
     }
@@ -734,6 +743,7 @@
 
         // WHEN asked to update the indication area
         mController.setVisible(true);
+        mExecutor.runAllReady();
 
         // THEN the owner info should be hidden
         verifyHideIndication(INDICATION_TYPE_OWNER_INFO);
@@ -763,6 +773,7 @@
 
         // WHEN keyguard showing changed called
         mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+        mExecutor.runAllReady();
 
         // THEN persistent messages are updated (in this case, most messages are hidden since
         // no info is provided) - verify that this happens
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index a5ea897..6abdea5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -23,14 +23,18 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
 
@@ -73,4 +77,14 @@
         assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE.id, ids[1])
         assertEquals(StatusBarStateEvent.STATUS_BAR_STATE_SHADE_LOCKED.id, ids[2])
     }
+
+    @Test
+    fun testSetDozeAmountInternal_onlySetsOnce() {
+        val listener = mock(StatusBarStateController.StateListener::class.java)
+        controller.addCallback(listener)
+
+        controller.setDozeAmount(0.5f, false /* animated */)
+        controller.setDozeAmount(0.5f, false /* animated */)
+        verify(listener).onDozeAmountChanged(eq(0.5f), anyFloat())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 60c3bc8..2e1297b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -81,6 +81,8 @@
     @Mock
     HeadsUpManager mHeadsUpManager;
     @Mock
+    NotificationInterruptLogger mLogger;
+    @Mock
     BatteryController mBatteryController;
     @Mock
     Handler mMockHandler;
@@ -101,6 +103,7 @@
                         mBatteryController,
                         mStatusBarStateController,
                         mHeadsUpManager,
+                        mLogger,
                         mMockHandler);
 
         mNotifInterruptionStateProvider.mUseHeadsUp = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index bbb2346..00af446 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -114,13 +114,15 @@
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = true,
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = STABLE_INSET_BOTTOM,
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -150,13 +152,15 @@
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
+        then(expectedContainerPadding = 0,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -190,7 +194,8 @@
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withCutout().withStableBottom())
         then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
+                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -217,12 +222,14 @@
         given(taskbarVisible = true,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
+        then(expectedContainerPadding = 0,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = true,
                 navigationMode = BUTTONS_NAVIGATION,
                 insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM)
+        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
+                expectedQsPadding = STABLE_INSET_BOTTOM)
     }
 
     @Test
@@ -259,7 +266,7 @@
         given(taskbarVisible = false,
                 navigationMode = GESTURES_NAVIGATION,
                 insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = CUTOUT_HEIGHT)
+        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
 
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
@@ -350,6 +357,7 @@
     @Test
     fun testDetailShowingInSplitShade() {
         notificationsQSContainerController.splitShadeEnabled = true
+        notificationsQSContainerController.setDetailShowing(true)
         useNewFooter(false)
 
         given(taskbarVisible = false,
@@ -357,7 +365,6 @@
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0)
 
-        notificationsQSContainerController.setDetailShowing(true)
         // should not influence spacing
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
@@ -368,6 +375,7 @@
     @Test
     fun testDetailShowingInSplitShade_newFooter() {
         notificationsQSContainerController.splitShadeEnabled = true
+        notificationsQSContainerController.setDetailShowing(true)
         useNewFooter(true)
 
         given(taskbarVisible = false,
@@ -375,7 +383,6 @@
                 insets = windowInsets().withStableBottom())
         then(expectedContainerPadding = 0)
 
-        notificationsQSContainerController.setDetailShowing(true)
         // should not influence spacing
         given(taskbarVisible = false,
                 navigationMode = BUTTONS_NAVIGATION,
@@ -417,7 +424,7 @@
         val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
         if (newFooter) {
             verify(notificationsQSContainer)
-                    .setQSContainerPaddingBottom(expectedNotificationsMargin)
+                    .setQSContainerPaddingBottom(expectedQsPadding)
         } else {
             verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 90b93e7..a7809c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -130,6 +130,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
@@ -309,6 +310,7 @@
                         mPowerManager,
                         mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
                         mStatusBarStateController, mBatteryController, mHeadsUpManager,
+                        mock(NotificationInterruptLogger.class),
                         new Handler(TestableLooper.get(this).getLooper()));
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
@@ -994,9 +996,10 @@
                 StatusBarStateController controller,
                 BatteryController batteryController,
                 HeadsUpManager headsUpManager,
+                NotificationInterruptLogger logger,
                 Handler mainHandler) {
             super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter,
-                    batteryController, controller, headsUpManager, mainHandler);
+                    batteryController, controller, headsUpManager, logger, mainHandler);
             mUseHeadsUp = true;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ca37a40..4bc4e6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -96,6 +96,7 @@
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -334,6 +335,7 @@
                         mock(StatusBarStateController.class),
                         mock(BatteryController.class),
                         mock(HeadsUpManager.class),
+                        mock(NotificationInterruptLogger.class),
                         mock(Handler.class)
                 );
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index d82671d..75d8453 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -82,6 +82,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -300,6 +301,7 @@
                         mock(StatusBarStateController.class),
                         mock(BatteryController.class),
                         mock(HeadsUpManager.class),
+                        mock(NotificationInterruptLogger.class),
                         mock(Handler.class)
                 );
         when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 7847c57..e698f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -24,6 +24,7 @@
 
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -40,6 +41,7 @@
             StatusBarStateController statusBarStateController,
             BatteryController batteryController,
             HeadsUpManager headsUpManager,
+            NotificationInterruptLogger logger,
             Handler mainHandler) {
         super(contentResolver,
                 powerManager,
@@ -49,6 +51,7 @@
                 batteryController,
                 statusBarStateController,
                 headsUpManager,
+                logger,
                 mainHandler);
         mUseHeadsUp = true;
     }
diff --git a/services/Android.bp b/services/Android.bp
index 0189303..e010469 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -23,6 +23,7 @@
             "-Xep:FormatString:ERROR",
             "-Xep:ArrayHashCode:ERROR",
             "-Xep:SelfAssignment:ERROR",
+            "-Xep:ArrayEquals:ERROR",
             // NOTE: only enable to generate local patchfiles
             // "-XepPatchChecks:refaster:frameworks/base/errorprone/refaster/EfficientXml.java.refaster",
             // "-XepPatchLocation:/tmp/refaster/",
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 8b62a64..7f10314 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -25,6 +25,7 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK;
 import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK;
@@ -66,6 +67,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -80,15 +82,21 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionWithIdCallback;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -180,6 +188,8 @@
 
     boolean mLastAccessibilityButtonCallbackState;
 
+    boolean mRequestImeApis;
+
     int mFetchFlags;
 
     long mNotificationTimeout;
@@ -271,6 +281,9 @@
 
         void onDoubleTapAndHold(int displayId);
 
+        void requestImeLocked(AccessibilityServiceConnection connection);
+
+        void unbindImeLocked(AccessibilityServiceConnection connection);
     }
 
     public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
@@ -374,6 +387,9 @@
                 & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
         mRequestAccessibilityButton = (info.flags
                 & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+        // TODO(b/218193835): request ime when ime flag is set and clean up when ime flag is unset
+        mRequestImeApis = (info.flags
+                & AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) != 0;
     }
 
     protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
@@ -1610,6 +1626,27 @@
         mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available);
     }
 
+    public void createImeSessionLocked() {
+        mInvocationHandler.createImeSessionLocked();
+    }
+
+    public void setImeSessionEnabledLocked(IInputMethodSession session, boolean enabled) {
+        mInvocationHandler.setImeSessionEnabledLocked(session, enabled);
+    }
+
+    public void bindInputLocked(InputBinding binding) {
+        mInvocationHandler.bindInputLocked(binding);
+    }
+
+    public  void unbindInputLocked() {
+        mInvocationHandler.unbindInputLocked();
+    }
+
+    public void startInputLocked(IBinder startInputToken, IInputContext inputContext,
+            EditorInfo editorInfo, boolean restarting) {
+        mInvocationHandler.startInputLocked(startInputToken, inputContext, editorInfo, restarting);
+    }
+
     /**
      * Called by the invocation handler to notify the service that the
      * state of magnification has changed.
@@ -1732,6 +1769,84 @@
         }
     }
 
+    private void createImeSessionInternal() {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("createImeSession", "");
+                }
+                AccessibilityCallback callback = new AccessibilityCallback();
+                listener.createImeSession(callback);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error requesting IME session from " + mService, re);
+            }
+        }
+    }
+
+    private void setImeSessionEnabledInternal(IInputMethodSession session, boolean enabled) {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null && session != null) {
+            try {
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("createImeSession", "");
+                }
+                listener.setImeSessionEnabled(session, enabled);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error requesting IME session from " + mService, re);
+            }
+        }
+    }
+
+    private void bindInputInternal(InputBinding binding) {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("bindInput", binding.toString());
+                }
+                listener.bindInput(binding);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error binding input to " + mService, re);
+            }
+        }
+    }
+
+    private void unbindInputInternal() {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("unbindInput", "");
+                }
+                listener.unbindInput();
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error unbinding input to " + mService, re);
+            }
+        }
+    }
+
+    private void startInputInternal(IBinder startInputToken, IInputContext inputContext,
+            EditorInfo editorInfo, boolean restarting) {
+        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+        if (listener != null) {
+            try {
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("startInput", startInputToken + " "
+                            + inputContext + " " + editorInfo + restarting);
+                }
+                listener.startInput(startInputToken, inputContext, editorInfo, restarting);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG,
+                        "Error starting input to " + mService, re);
+            }
+        }
+    }
+
     protected IAccessibilityServiceClient getServiceInterfaceSafely() {
         synchronized (mLock) {
             return mServiceInterface;
@@ -1925,6 +2040,11 @@
         private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
         private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
         private static final int MSG_ON_SYSTEM_ACTIONS_CHANGED = 9;
+        private static final int MSG_CREATE_IME_SESSION = 10;
+        private static final int MSG_SET_IME_SESSION_ENABLED = 11;
+        private static final int MSG_BIND_INPUT = 12;
+        private static final int MSG_UNBIND_INPUT = 13;
+        private static final int MSG_START_INPUT = 14;
 
         /** List of magnification callback states, mapping from displayId -> Boolean */
         @GuardedBy("mlock")
@@ -1974,6 +2094,29 @@
                     notifySystemActionsChangedInternal();
                     break;
                 }
+                case MSG_CREATE_IME_SESSION:
+                    createImeSessionInternal();
+                    break;
+                case MSG_SET_IME_SESSION_ENABLED:
+                    final boolean enabled = (message.arg1 != 0);
+                    final IInputMethodSession session = (IInputMethodSession) message.obj;
+                    setImeSessionEnabledInternal(session, enabled);
+                    break;
+                case MSG_BIND_INPUT:
+                    final InputBinding binding = (InputBinding) message.obj;
+                    bindInputInternal(binding);
+                    break;
+                case MSG_UNBIND_INPUT:
+                    unbindInputInternal();
+                    break;
+                case MSG_START_INPUT:
+                    final boolean restarting = (message.arg1 != 0);
+                    final SomeArgs args = (SomeArgs) message.obj;
+                    final IBinder startInputToken = (IBinder) args.arg1;
+                    final IInputContext inputContext = (IInputContext) args.arg2;
+                    final EditorInfo editorInfo = (EditorInfo) args.arg3;
+                    startInputInternal(startInputToken, inputContext, editorInfo, restarting);
+                    break;
                 default: {
                     throw new IllegalArgumentException("Unknown message: " + type);
                 }
@@ -2036,6 +2179,37 @@
                     (available ? 1 : 0), 0);
             msg.sendToTarget();
         }
+
+        public void createImeSessionLocked() {
+            final Message msg = obtainMessage(MSG_CREATE_IME_SESSION);
+            msg.sendToTarget();
+        }
+
+        public void setImeSessionEnabledLocked(IInputMethodSession session, boolean enabled) {
+            final Message msg = obtainMessage(MSG_SET_IME_SESSION_ENABLED, (enabled ? 1 : 0),
+                    0, session);
+            msg.sendToTarget();
+        }
+
+        public void bindInputLocked(InputBinding binding) {
+            final Message msg = obtainMessage(MSG_BIND_INPUT, binding);
+            msg.sendToTarget();
+        }
+
+        public void unbindInputLocked() {
+            final Message msg = obtainMessage(MSG_UNBIND_INPUT);
+            msg.sendToTarget();
+        }
+
+        public void startInputLocked(IBinder startInputToken, IInputContext inputContext,
+                EditorInfo editorInfo, boolean restarting) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = startInputToken;
+            args.arg2 = inputContext;
+            args.arg3 = editorInfo;
+            final Message msg = obtainMessage(MSG_START_INPUT, restarting ? 1 : 0, 0, args);
+            msg.sendToTarget();
+        }
     }
 
     public boolean isServiceHandlesDoubleTapEnabled() {
@@ -2185,4 +2359,18 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    private static final class AccessibilityCallback extends IInputSessionWithIdCallback.Stub {
+        @Override
+        public void sessionCreated(IInputMethodSession session, int id) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.sessionCreated");
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                InputMethodManagerInternal.get().onSessionForAccessibilityCreated(id, session);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
 }
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 5b580d9..62da981 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -115,6 +115,8 @@
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
@@ -128,12 +130,16 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IntPair;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodSession;
+import com.android.server.AccessibilityManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
 import com.android.server.accessibility.magnification.MagnificationScaleProvider;
 import com.android.server.accessibility.magnification.WindowMagnificationManager;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -231,7 +237,7 @@
 
     private final MainHandler mMainHandler;
 
-    // Lazily initialized - access through getSystemActionPerfomer()
+    // Lazily initialized - access through getSystemActionPerformer()
     private SystemActionPerformer mSystemActionPerformer;
 
     private InteractionBridge mInteractionBridge;
@@ -273,6 +279,13 @@
     private Point mTempPoint = new Point();
     private boolean mIsAccessibilityButtonShown;
 
+    private InputBinding mInputBinding;
+    IBinder mStartInputToken;
+    IInputContext mInputContext;
+    EditorInfo mEditorInfo;
+    boolean mRestarting;
+    boolean mInputSessionRequested;
+
     private AccessibilityUserState getCurrentUserStateLocked() {
         return getUserStateLocked(mCurrentUserId);
     }
@@ -298,6 +311,42 @@
         }
     }
 
+    private static final class LocalServiceImpl extends AccessibilityManagerInternal {
+        @NonNull
+        private final AccessibilityManagerService mService;
+
+        LocalServiceImpl(@NonNull AccessibilityManagerService service) {
+            mService = service;
+        }
+
+        @Override
+        public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions,
+                boolean enabled) {
+            mService.setImeSessionEnabled(sessions, enabled);
+        }
+
+        @Override
+        public void unbindInput() {
+            mService.unbindInput();
+        }
+
+        @Override
+        public void bindInput(InputBinding binding) {
+            mService.bindInput(binding);
+        }
+
+        @Override
+        public void createImeSession(ArraySet<Integer> ignoreSet) {
+            mService.createImeSession(ignoreSet);
+        }
+
+        @Override
+        public void startInput(IBinder startInputToken, IInputContext inputContext,
+                EditorInfo editorInfo, boolean restarting) {
+            mService.startInput(startInputToken, inputContext, editorInfo, restarting);
+        }
+    }
+
     public static final class Lifecycle extends SystemService {
         private final AccessibilityManagerService mService;
 
@@ -308,6 +357,8 @@
 
         @Override
         public void onStart() {
+            LocalServices.addService(AccessibilityManagerInternal.class,
+                    new LocalServiceImpl(mService));
             publishBinderService(Context.ACCESSIBILITY_SERVICE, mService);
         }
 
@@ -3463,27 +3514,27 @@
 
     @Override
     @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
-    public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) {
+    public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) {
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
-                "setSystemAudioCaptioningRequested");
+                "setSystemAudioCaptioningEnabled");
 
-        mCaptioningManagerImpl.setSystemAudioCaptioningRequested(isEnabled, userId);
+        mCaptioningManagerImpl.setSystemAudioCaptioningEnabled(isEnabled, userId);
     }
 
     @Override
-    public boolean isSystemAudioCaptioningUiRequested(int userId) {
-        return mCaptioningManagerImpl.isSystemAudioCaptioningUiRequested(userId);
+    public boolean isSystemAudioCaptioningUiEnabled(int userId) {
+        return mCaptioningManagerImpl.isSystemAudioCaptioningUiEnabled(userId);
     }
 
     @Override
     @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
-    public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) {
+    public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) {
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
-                "setSystemAudioCaptioningUiRequested");
+                "setSystemAudioCaptioningUiEnabled");
 
-        mCaptioningManagerImpl.setSystemAudioCaptioningUiRequested(isEnabled, userId);
+        mCaptioningManagerImpl.setSystemAudioCaptioningUiEnabled(isEnabled, userId);
     }
 
     @Override
@@ -4240,6 +4291,45 @@
                         this, displayId));
     }
 
+    @Override
+    public void requestImeLocked(AccessibilityServiceConnection connection) {
+        mMainHandler.sendMessage(obtainMessage(
+                AccessibilityManagerService::createSessionForConnection, this, connection));
+        mMainHandler.sendMessage(obtainMessage(
+                AccessibilityManagerService::bindAndStartInputForConnection, this, connection));
+    }
+
+    @Override
+    public void unbindImeLocked(AccessibilityServiceConnection connection) {
+        mMainHandler.sendMessage(obtainMessage(
+                AccessibilityManagerService::unbindInputForConnection, this, connection));
+    }
+
+    private void createSessionForConnection(AccessibilityServiceConnection connection) {
+        synchronized (mLock) {
+            if (mInputSessionRequested) {
+                connection.createImeSessionLocked();
+            }
+        }
+    }
+
+    private void bindAndStartInputForConnection(AccessibilityServiceConnection connection) {
+        synchronized (mLock) {
+            if (mInputBinding != null) {
+                connection.bindInputLocked(mInputBinding);
+                connection.startInputLocked(mStartInputToken, mInputContext, mEditorInfo,
+                        mRestarting);
+            }
+        }
+    }
+
+    private void unbindInputForConnection(AccessibilityServiceConnection connection) {
+        InputMethodManagerInternal.get().unbindAccessibilityFromCurrentClient(connection.mId);
+        synchronized (mLock) {
+            connection.unbindInputLocked();
+        }
+    }
+
     private void onDoubleTapAndHoldInternal(int displayId) {
         synchronized (mLock) {
             if (mHasInputFilter && mInputFilter != null) {
@@ -4268,4 +4358,100 @@
     public AccessibilityTraceManager getTraceManager() {
         return mTraceManager;
     }
+
+    /**
+     * Bind input for accessibility services which request ime capabilities.
+     *
+     * @param binding Information given to an accessibility service about a client connecting to it.
+     */
+    public void bindInput(InputBinding binding) {
+        AccessibilityUserState userState;
+        synchronized (mLock) {
+            // Keep records of these in case new Accessibility Services are enabled.
+            mInputBinding = binding;
+            userState = getCurrentUserStateLocked();
+            for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+                final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+                if (service.requestImeApis()) {
+                    service.bindInputLocked(binding);
+                }
+            }
+        }
+    }
+
+    /**
+     * Unbind input for accessibility services which request ime capabilities.
+     */
+    public void unbindInput() {
+        AccessibilityUserState userState;
+        // TODO(b/218182733): Resolve the Imf lock and mLock possible deadlock
+        synchronized (mLock) {
+            userState = getCurrentUserStateLocked();
+            for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+                final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+                if (service.requestImeApis()) {
+                    service.unbindInputLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Start input for accessibility services which request ime capabilities.
+     */
+    public void startInput(IBinder startInputToken, IInputContext inputContext,
+            EditorInfo editorInfo, boolean restarting) {
+        AccessibilityUserState userState;
+        synchronized (mLock) {
+            // Keep records of these in case new Accessibility Services are enabled.
+            mStartInputToken = startInputToken;
+            mInputContext = inputContext;
+            mEditorInfo = editorInfo;
+            mRestarting = restarting;
+            userState = getCurrentUserStateLocked();
+            for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+                final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+                if (service.requestImeApis()) {
+                    service.startInputLocked(startInputToken, inputContext, editorInfo, restarting);
+                }
+            }
+        }
+    }
+
+    /**
+     * Request input sessions from all accessibility services which request ime capabilities and
+     * whose id is not in the ignoreSet
+     */
+    public void createImeSession(ArraySet<Integer> ignoreSet) {
+        AccessibilityUserState userState;
+        synchronized (mLock) {
+            mInputSessionRequested = true;
+            userState = getCurrentUserStateLocked();
+            for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+                final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+                if ((!ignoreSet.contains(service.mId)) && service.requestImeApis()) {
+                    service.createImeSessionLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Enable or disable the sessions.
+     *
+     * @param sessions Sessions to enable or disable.
+     * @param enabled True if enable the sessions or false if disable the sessions.
+     */
+    public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions, boolean enabled) {
+        AccessibilityUserState userState;
+        synchronized (mLock) {
+            userState = getCurrentUserStateLocked();
+            for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+                final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+                if (sessions.contains(service.mId) && service.requestImeApis()) {
+                    service.setImeSessionEnabledLocked(sessions.get(service.mId), enabled);
+                }
+            }
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 8f7260f..0631028 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -127,6 +127,9 @@
     }
 
     public void unbindLocked() {
+        if (requestImeApis()) {
+            mSystemSupport.unbindImeLocked(this);
+        }
         mContext.unbindService(this);
         AccessibilityUserState userState = mUserStateWeakReference.get();
         if (userState == null) return;
@@ -188,6 +191,9 @@
             // the new configuration (for example, initializing the input filter).
             mMainHandler.sendMessage(obtainMessage(
                     AccessibilityServiceConnection::initializeService, this));
+            if (requestImeApis()) {
+                mSystemSupport.requestImeLocked(this);
+            }
         }
     }
 
@@ -371,6 +377,9 @@
             if (!isConnectedLocked()) {
                 return;
             }
+            if (requestImeApis()) {
+                mSystemSupport.unbindImeLocked(this);
+            }
             mAccessibilityServiceInfo.crashed = true;
             AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState != null) {
@@ -512,6 +521,10 @@
         mMainHandler.sendMessage(msg);
     }
 
+    public boolean requestImeApis() {
+        return mRequestImeApis;
+    }
+
     private void notifyMotionEventInternal(MotionEvent event) {
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
diff --git a/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java
index 39780d2..0fc6c8d 100644
--- a/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java
+++ b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java
@@ -40,7 +40,7 @@
      * @param userId The user Id.
      */
     @Override
-    public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) {
+    public void setSystemAudioCaptioningEnabled(boolean isEnabled, int userId) {
         final long identity = Binder.clearCallingIdentity();
         try {
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
@@ -57,7 +57,7 @@
      * @return the system audio caption UI enabled state.
      */
     @Override
-    public boolean isSystemAudioCaptioningUiRequested(int userId) {
+    public boolean isSystemAudioCaptioningUiEnabled(int userId) {
         final long identity = Binder.clearCallingIdentity();
         try {
             return Settings.Secure.getIntForUser(mContext.getContentResolver(),
@@ -75,7 +75,7 @@
      * @param userId The user Id.
      */
     @Override
-    public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) {
+    public void setSystemAudioCaptioningUiEnabled(boolean isEnabled, int userId) {
         final long identity = Binder.clearCallingIdentity();
         try {
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
diff --git a/services/api/current.txt b/services/api/current.txt
index dcf7e64..e46c972 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -38,8 +38,8 @@
 package com.android.server.am {
 
   public interface ActivityManagerLocal {
+    method public boolean bindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, int) throws android.os.RemoteException;
     method public boolean canStartForegroundService(int, int, @NonNull String);
-    method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException;
   }
 
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5f3a030..075d96d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -215,6 +215,10 @@
 
     @Override // Binder call
     public void close() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to close the virtual device");
+
         synchronized (mVirtualDeviceLock) {
             if (!mPerDisplayWakelocks.isEmpty()) {
                 mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
@@ -227,7 +231,13 @@
         }
         mListener.onClose(mAssociationInfo.getId());
         mAppToken.unlinkToDeath(this, 0);
-        mInputController.close();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mInputController.close();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/AccessibilityManagerInternal.java b/services/core/java/com/android/server/AccessibilityManagerInternal.java
new file mode 100644
index 0000000..28f6db1
--- /dev/null
+++ b/services/core/java/com/android/server/AccessibilityManagerInternal.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodSession;
+
+/**
+ * Accessibility manager local system service interface.
+ */
+public abstract class AccessibilityManagerInternal {
+    /** Enable or disable the sessions. */
+    public abstract void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions,
+            boolean enabled);
+
+    /** Unbind input for all accessibility services which require ime capabilities. */
+    public abstract void unbindInput();
+
+    /** Bind input for all accessibility services which require ime capabilities. */
+    public abstract void bindInput(InputBinding binding);
+
+    /**
+     * Request input session from all accessibility services which require ime capabilities and
+     * whose id is not in the ignoreSet.
+     */
+    public abstract void createImeSession(ArraySet<Integer> ignoreSet);
+
+    /** Start input for all accessibility services which require ime capabilities. */
+    public abstract void startInput(IBinder startInputToken, IInputContext inputContext,
+            EditorInfo editorInfo, boolean restarting);
+
+    private static final AccessibilityManagerInternal NOP = new AccessibilityManagerInternal() {
+        @Override
+        public void setImeSessionEnabled(SparseArray<IInputMethodSession> sessions,
+                boolean enabled) {
+        }
+
+        @Override
+        public void unbindInput() {
+        }
+
+        @Override
+        public void bindInput(InputBinding binding) {
+        }
+
+        @Override
+        public void createImeSession(ArraySet<Integer> ignoreSet) {
+        }
+
+        @Override
+        public void startInput(IBinder startInputToken, IInputContext inputContext,
+                EditorInfo editorInfo, boolean restarting) {
+        }
+    };
+
+    /**
+     * @return Global instance if exists. Otherwise, a fallback no-op instance.
+     */
+    @NonNull
+    public static AccessibilityManagerInternal get() {
+        final AccessibilityManagerInternal instance =
+                LocalServices.getService(AccessibilityManagerInternal.class);
+        return instance != null ? instance : NOP;
+    }
+}
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index e29e894..e924012 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -16,8 +16,9 @@
 
 package com.android.server;
 
+import android.annotation.EnforcePermission;
+import android.annotation.RequiresNoPermission;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.gsi.AvbPublicKey;
 import android.gsi.GsiProgress;
 import android.gsi.IGsiService;
@@ -53,20 +54,12 @@
     }
 
     private IGsiService getGsiService() {
-        checkPermission();
         if (mGsiService != null) {
             return mGsiService;
         }
         return IGsiService.Stub.asInterface(ServiceManager.waitForService("gsiservice"));
     }
 
-    private void checkPermission() {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires MANAGE_DYNAMIC_SYSTEM permission");
-        }
-    }
-
     class GsiServiceCallback extends IGsiServiceCallback.Stub {
         // 0 for success
         private int mResult = -1;
@@ -82,6 +75,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean startInstallation(String dsuSlot) throws RemoteException {
         IGsiService service = getGsiService();
         mGsiService = service;
@@ -124,6 +118,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean createPartition(String name, long size, boolean readOnly)
             throws RemoteException {
         IGsiService service = getGsiService();
@@ -135,6 +130,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean closePartition() throws RemoteException {
         IGsiService service = getGsiService();
         if (service.closePartition() != 0) {
@@ -145,6 +141,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean finishInstallation() throws RemoteException {
         IGsiService service = getGsiService();
         if (service.closeInstall() != 0) {
@@ -155,21 +152,25 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public GsiProgress getInstallationProgress() throws RemoteException {
         return getGsiService().getInstallProgress();
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean abort() throws RemoteException {
         return getGsiService().cancelGsiInstall();
     }
 
     @Override
+    @RequiresNoPermission
     public boolean isInUse() {
         return SystemProperties.getBoolean("ro.gsid.image_running", false);
     }
 
     @Override
+    @RequiresNoPermission
     public boolean isInstalled() {
         boolean installed = SystemProperties.getBoolean("gsid.image_installed", false);
         Slog.i(TAG, "isInstalled(): " + installed);
@@ -177,11 +178,13 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean isEnabled() throws RemoteException {
         return getGsiService().isGsiEnabled();
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean remove() throws RemoteException {
         try {
             GsiServiceCallback callback = new GsiServiceCallback();
@@ -197,6 +200,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException {
         IGsiService gsiService = getGsiService();
         if (enable) {
@@ -220,6 +224,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
         try {
             return getGsiService().setGsiAshmem(ashmem, size);
@@ -229,6 +234,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean submitFromAshmem(long size) {
         try {
             return getGsiService().commitGsiChunkFromAshmem(size);
@@ -238,6 +244,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public boolean getAvbPublicKey(AvbPublicKey dst) {
         try {
             return getGsiService().getAvbPublicKey(dst) == 0;
@@ -247,6 +254,7 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
     public long suggestScratchSize() throws RemoteException {
         return getGsiService().suggestScratchSize();
     }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 32a6558..178b666 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4071,7 +4071,7 @@
                             || (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia));
                 } else {
                     match = vol.isVisibleForUser(userId)
-                            || (includeInvisible && vol.getPath() != null)
+                            || (!vol.isVisible() && includeInvisible && vol.getPath() != null)
                             || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia));
                 }
                 if (!match) continue;
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index b48e21e..7b8cce5 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -340,17 +340,18 @@
      * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed
      * exclusively by the Settings app, and passed into the platform at startup time.
      *
+     * @return A unique key corresponding to this session.
      * @throws IllegalArgumentException if no profile was found for the given package name.
      * @hide
      */
     @Override
-    public void startVpnProfile(@NonNull String packageName) {
+    public String startVpnProfile(@NonNull String packageName) {
         final int callingUid = Binder.getCallingUid();
         verifyCallingUidAndPackage(packageName, callingUid);
         final int user = UserHandle.getUserId(callingUid);
         synchronized (mVpns) {
             throwIfLockdownEnabled();
-            mVpns.get(user).startVpnProfile(packageName);
+            return mVpns.get(user).startVpnProfile(packageName);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9353dd8..dc64d80 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3411,9 +3411,11 @@
                     final Intent.FilterComparison filter
                             = new Intent.FilterComparison(service.cloneFilter());
                     final ServiceRestarter res = new ServiceRestarter();
+                    String supplementalProcessName = isSupplementalProcessService ? instanceName
+                                                                                  : null;
                     r = new ServiceRecord(mAm, className, name, definingPackageName,
                             definingUid, filter, sInfo, callingFromFg, res,
-                            isSupplementalProcessService);
+                            supplementalProcessName);
                     res.setService(r);
                     smap.mServicesByInstanceName.put(name, r);
                     smap.mServicesByIntent.put(filter, r);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index d9ee7d9..535340b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,10 +17,12 @@
 package com.android.server.am;
 
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.TransactionTooLargeException;
+import android.os.RemoteException;
 
 /**
  * Interface for in-process calls into
@@ -63,22 +65,28 @@
     void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs);
 
     /**
-     * Starts a supplemental process service and binds to it. You can through the arguments here
-     * have the system bring up multiple concurrent processes hosting their own instance of that
-     * service. The <var>userAppUid</var> you provide here identifies the different instances - each
-     * unique uid is attributed to a supplemental process.
+     * Binds to a supplemental process service, creating it if needed. You can through the arguments
+     * here have the system bring up multiple concurrent processes hosting their own instance of
+     * that service. The {@code processName} you provide here identifies the different instances.
      *
      * @param service Identifies the supplemental process service to connect to. The Intent must
      *        specify an explicit component name. This value cannot be null.
      * @param conn Receives information as the service is started and stopped.
      *        This must be a valid ServiceConnection object; it must not be null.
      * @param userAppUid Uid of the app for which the supplemental process needs to be spawned.
+     * @param processName Unique identifier for the service instance. Each unique name here will
+     *        result in a different service instance being created. Identifiers must only contain
+     *        ASCII letters, digits, underscores, and periods.
+     * @param flags Operation options provided by Context class for the binding.
      * @return {@code true} if the system is in the process of bringing up a
      *         service that your client has permission to bind to; {@code false}
      *         if the system couldn't find the service or if your client doesn't
      *         have permission to bind to it.
+     * @throws RemoteException If the service could not be brought up.
+     * @see Context#bindService(Intent, ServiceConnection, int)
      */
-    boolean startAndBindSupplementalProcessService(@NonNull Intent service,
-            @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException;
-
+    @SuppressLint("RethrowRemoteException")
+    boolean bindSupplementalProcessService(@NonNull Intent service, @NonNull ServiceConnection conn,
+            int userAppUid, @NonNull String processName, @Context.BindServiceFlags int flags)
+            throws RemoteException;
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5cc163d..e3a0161 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15951,14 +15951,17 @@
         }
 
         @Override
-        public boolean startAndBindSupplementalProcessService(Intent service,
-                ServiceConnection conn, int userAppUid) throws TransactionTooLargeException {
+        public boolean bindSupplementalProcessService(Intent service, ServiceConnection conn,
+                int userAppUid, String processName, int flags) throws RemoteException {
             if (service == null) {
                 throw new IllegalArgumentException("intent is null");
             }
             if (conn == null) {
                 throw new IllegalArgumentException("connection is null");
             }
+            if (processName == null) {
+                throw new IllegalArgumentException("processName is null");
+            }
             if (service.getComponent() == null) {
                 throw new IllegalArgumentException("service must specify explicit component");
             }
@@ -15967,15 +15970,14 @@
             }
 
             Handler handler = mContext.getMainThreadHandler();
-            int flags = Context.BIND_AUTO_CREATE;
 
             final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags);
             service.prepareToLeaveProcess(mContext);
             return ActivityManagerService.this.bindServiceInstance(
-                        mContext.getIApplicationThread(), mContext.getActivityToken(), service,
-                        service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
-                        Integer.toString(userAppUid), /*isSupplementalProcessService*/ true,
-                        mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0;
+                    mContext.getIApplicationThread(), mContext.getActivityToken(), service,
+                    service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags,
+                    processName, /*isSupplementalProcessService*/ true, mContext.getOpPackageName(),
+                    UserHandle.getUserId(userAppUid)) != 0;
         }
 
         @Override
@@ -16119,23 +16121,6 @@
         }
 
         /**
-         * Returns package name by pid.
-         */
-        @Override
-        @Nullable
-        public String getPackageNameByPid(int pid) {
-            synchronized (mPidsSelfLocked) {
-                final ProcessRecord app = mPidsSelfLocked.get(pid);
-
-                if (app != null && app.info != null) {
-                    return app.info.packageName;
-                }
-
-                return null;
-            }
-        }
-
-        /**
          * Sets if the given pid has an overlay UI or not.
          *
          * @param pid The pid we are setting overlay UI for.
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 043ea08..97727b08 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -101,7 +101,6 @@
 import android.window.SplashScreen;
 
 import com.android.internal.compat.CompatibilityChangeConfig;
-import com.android.internal.util.HexDump;
 import com.android.internal.util.MemInfoReader;
 import com.android.server.am.LowMemDetector.MemFactor;
 import com.android.server.compat.PlatformCompat;
@@ -1958,22 +1957,42 @@
         return 0;
     }
 
-    private static byte[] argToBytes(String arg) {
-        if (arg.equals("!")) {
-            return null;
-        } else {
-            return HexDump.hexStringToByteArray(arg);
-        }
-    }
-
     int runUnlockUser(PrintWriter pw) throws RemoteException {
         int userId = Integer.parseInt(getNextArgRequired());
-        byte[] token = argToBytes(getNextArgRequired());
-        byte[] secret = argToBytes(getNextArgRequired());
-        boolean success = mInterface.unlockUser(userId, token, secret, null);
+
+        /*
+         * Originally this command required two more parameters: the hardware
+         * authentication token and secret needed to unlock the user.  However,
+         * unlockUser() no longer takes a token parameter at all, and there
+         * isn't really any way for callers of this shell command to get the
+         * secret if one is needed (i.e., when the user has an LSKF), given that
+         * the secret must be a cryptographic key derived from the user's
+         * synthetic password.  I.e. the secret is *not* the LSKF here, but
+         * rather an intermediate value several steps down the chain.
+         *
+         * As such, the only supported use for this command is unlocking a user
+         * who doesn't have an LSKF, with empty or unspecified token and secret.
+         *
+         * To preserve previous behavior, an exclamation mark ("!") is also
+         * accepted for these values; it means the same as an empty string.
+         */
+        String token = getNextArg();
+        if (!TextUtils.isEmpty(token) && !"!".equals(token)) {
+            getErrPrintWriter().println("Error: token parameter not supported");
+            return -1;
+        }
+        String secret = getNextArg();
+        if (!TextUtils.isEmpty(secret) && !"!".equals(secret)) {
+            getErrPrintWriter().println("Error: secret parameter not supported");
+            return -1;
+        }
+
+        boolean success = mInterface.unlockUser(userId, null, null, null);
         if (success) {
             pw.println("Success: user unlocked");
         } else {
+            // TODO(b/218389026): we can reach here even if the user's storage
+            // was successfully unlocked.
             getErrPrintWriter().println("Error: could not unlock user");
         }
         return 0;
@@ -3494,8 +3513,9 @@
             pw.println("      Start USER_ID in background if it is currently stopped;");
             pw.println("      use switch-user if you want to start the user in foreground.");
             pw.println("      -w: wait for start-user to complete and the user to be unlocked.");
-            pw.println("  unlock-user <USER_ID> [TOKEN_HEX]");
-            pw.println("      Attempt to unlock the given user using the given authorization token.");
+            pw.println("  unlock-user <USER_ID>");
+            pw.println("      Unlock the given user.  This will only work if the user doesn't");
+            pw.println("      have an LSKF (PIN/pattern/password).");
             pw.println("  stop-user [-w] [-f] <USER_ID>");
             pw.println("      Stop execution of USER_ID, not allowing it to run any");
             pw.println("      code until a later explicit start or switch to it.");
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 465623f..8cff13e 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -546,7 +546,7 @@
         static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL =
                 DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval";
 
-        static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = true;
+        static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = false;
         static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 24 * 60 * 60 * 1000;
 
         volatile boolean mBgAutoRestrictedBucket;
@@ -1136,8 +1136,7 @@
         final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
         if (level >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
                 && curLevel < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
-            if (!mConstantsObserver.mRestrictedBucketEnabled
-                    || !mConstantsObserver.mBgAutoRestrictedBucket) {
+            if (!mConstantsObserver.mRestrictedBucketEnabled) {
                 return;
             }
             // Moving the app standby bucket to restricted in the meanwhile.
@@ -1146,7 +1145,9 @@
                 Slog.i(TAG, pkgName + "/" + UserHandle.formatUid(uid)
                         + " is bg-restricted, moving to restricted standby bucket");
             }
-            if (curBucket != STANDBY_BUCKET_RESTRICTED) {
+            if (curBucket != STANDBY_BUCKET_RESTRICTED
+                    && (mConstantsObserver.mBgAutoRestrictedBucket
+                    || level == RESTRICTION_LEVEL_RESTRICTED_BUCKET)) {
                 // restrict the app if it hasn't done so.
                 boolean doIt = true;
                 synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 2f7249e..91822ac 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -79,7 +79,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.BatteryUsageStatsProvider;
 import com.android.internal.os.BatteryUsageStatsStore;
@@ -2488,7 +2487,7 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            if (BatteryStatsHelper.checkWifiOnly(mContext)) {
+            if (BatteryStats.checkWifiOnly(mContext)) {
                 flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
             }
             awaitCompletion();
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index da78e2d..d3b5752 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -571,13 +571,13 @@
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
             Runnable restarter) {
         this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
-                restarter, false);
+                restarter, null);
     }
 
     ServiceRecord(ActivityManagerService ams, ComponentName name,
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
-            Runnable restarter, boolean isSupplementalProcessService) {
+            Runnable restarter, String supplementalProcessName) {
         this.ams = ams;
         this.name = name;
         this.instanceName = instanceName;
@@ -588,9 +588,10 @@
         serviceInfo = sInfo;
         appInfo = sInfo.applicationInfo;
         packageName = sInfo.applicationInfo.packageName;
-        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
-                || isSupplementalProcessService) {
+        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
             processName = sInfo.processName + ":" + instanceName.getClassName();
+        } else if (supplementalProcessName != null) {
+            processName = supplementalProcessName;
         } else {
             processName = sInfo.processName;
         }
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
index 6982513..ab4d856 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
@@ -20,22 +20,30 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.app.AppGlobals;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
-import android.app.ambientcontext.AmbientContextEventResponse;
 import android.app.ambientcontext.AmbientContextManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
-import android.service.ambientcontext.AmbientContextDetectionService;
+import android.provider.Settings;
+import android.service.ambientcontext.AmbientContextDetectionResult;
+import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -44,7 +52,8 @@
 import com.android.server.infra.AbstractPerUserSystemService;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Set;
 
 /**
@@ -60,14 +69,12 @@
     RemoteAmbientContextDetectionService mRemoteService;
 
     private ComponentName mComponentName;
-    private Context mContext;
     private Set<PendingIntent> mExistingPendingIntents;
 
     AmbientContextManagerPerUserService(
             @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) {
         super(master, lock, userId);
-        mContext = master.getContext();
-        mExistingPendingIntents = new HashSet<>();
+        mExistingPendingIntents = new ArraySet<>();
     }
 
     void destroyLocked() {
@@ -154,12 +161,13 @@
      * package. A new registration from the same package will overwrite the previous registration.
      */
     public void onRegisterObserver(AmbientContextEventRequest request,
-            PendingIntent pendingIntent) {
+            PendingIntent pendingIntent, RemoteCallback clientStatusCallback) {
         synchronized (mLock) {
             if (!setUpServiceIfNeeded()) {
-                Slog.w(TAG, "Service is not available at this moment.");
-                sendStatusUpdateIntent(
-                        pendingIntent, AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                sendStatusCallback(
+                        clientStatusCallback,
+                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
 
@@ -173,29 +181,58 @@
             }
 
             // Register new package and add request to mExistingRequests
-            startDetection(request, callingPackage, createRemoteCallback());
+            startDetection(request, callingPackage, createDetectionResultRemoteCallback(),
+                    getServerStatusCallback(clientStatusCallback));
             mExistingPendingIntents.add(pendingIntent);
         }
     }
 
+    /**
+     * Returns a RemoteCallback that handles the status from the detection service, and
+     * sends results to the client callback.
+     */
+    private RemoteCallback getServerStatusCallback(RemoteCallback clientStatusCallback) {
+        return new RemoteCallback(result -> {
+            AmbientContextDetectionServiceStatus serviceStatus =
+                    (AmbientContextDetectionServiceStatus) result.get(
+                            AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                String packageName = serviceStatus.getPackageName();
+                Bundle bundle = new Bundle();
+                bundle.putInt(
+                        AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
+                        serviceStatus.getStatusCode());
+                clientStatusCallback.sendResult(bundle);
+                int statusCode = serviceStatus.getStatusCode();
+                Slog.i(TAG, "Got detection status of " + statusCode
+                        + " for " + packageName);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        });
+    }
+
     @VisibleForTesting
     void startDetection(AmbientContextEventRequest request, String callingPackage,
-            RemoteCallback callback) {
+            RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
         Slog.d(TAG, "Requested detection of " + request.getEventTypes());
         synchronized (mLock) {
             ensureRemoteServiceInitiated();
-            mRemoteService.startDetection(request, callingPackage, callback);
+            mRemoteService.startDetection(request, callingPackage, detectionResultCallback,
+                    statusCallback);
         }
     }
 
     /**
      * Sends an intent with a status code and empty events.
      */
-    void sendStatusUpdateIntent(PendingIntent pendingIntent, int statusCode) {
-        AmbientContextEventResponse response = new AmbientContextEventResponse.Builder()
-                .setStatusCode(statusCode)
-                .build();
-        sendResponseIntent(pendingIntent, response);
+    void sendStatusCallback(RemoteCallback statusCallback, int statusCode) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(
+                AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
+                statusCode);
+        statusCallback.sendResult(bundle);
     }
 
     /**
@@ -217,6 +254,113 @@
         }
     }
 
+    public void onQueryServiceStatus(int[] eventTypes, String callingPackage,
+            RemoteCallback statusCallback) {
+        Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes)
+                + " for " + callingPackage);
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                sendStatusToCallback(statusCallback,
+                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            ensureRemoteServiceInitiated();
+            mRemoteService.queryServiceStatus(
+                    eventTypes,
+                    callingPackage,
+                    getServerStatusCallback(statusCallback));
+        }
+    }
+
+    public void onStartConsentActivity(int[] eventTypes, String callingPackage) {
+        Slog.d(TAG, "Opening consent activity of " + Arrays.toString(eventTypes)
+                + " for " + callingPackage);
+
+        // Look up the recent task from the callingPackage
+        ActivityManager.RecentTaskInfo task;
+        ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks;
+        int userId = getUserId();
+        try {
+            recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1,
+                    /*flags*/ 0, userId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to query recent tasks!");
+            return;
+        }
+
+        if ((recentTasks != null) || recentTasks.getList().isEmpty()) {
+            Slog.e(TAG, "Recent task list is empty!");
+            return;
+        }
+
+        task = recentTasks.getList().get(0);
+        if (!callingPackage.equals(task.topActivityInfo.packageName)) {
+            Slog.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName
+                    + " doesn't match with camera client package name: " + callingPackage);
+            return;
+        }
+
+        // Start activity as the same task from the callingPackage
+        Intent intent = new Intent();
+        ComponentName consentComponent = getConsentComponent();
+        if (consentComponent != null) {
+            Slog.e(TAG, "Starting consent activity for " + callingPackage);
+            Context context = getContext();
+            String packageNameExtraKey = Settings.Secure.getStringForUser(
+                    context.getContentResolver(),
+                    Settings.Secure.AMBIENT_CONTEXT_PACKAGE_NAME_EXTRA_KEY,
+                    userId);
+            String eventArrayExtraKey = Settings.Secure.getStringForUser(
+                    context.getContentResolver(),
+                    Settings.Secure.AMBIENT_CONTEXT_EVENT_ARRAY_EXTRA_KEY,
+                    userId);
+
+            // Create consent activity intent with the calling package name and requested events.
+            intent.setComponent(consentComponent);
+            if (packageNameExtraKey != null) {
+                intent.putExtra(packageNameExtraKey, callingPackage);
+            }
+            if (eventArrayExtraKey != null) {
+                intent.putExtra(eventArrayExtraKey, eventTypes);
+            }
+
+            // Set parent to the calling app's task
+            ActivityOptions options = ActivityOptions.makeBasic();
+            options.setLaunchTaskId(task.taskId);
+            context.startActivity(intent, options.toBundle());
+        } else {
+            Slog.e(TAG, "Consent component not found!");
+        }
+    }
+
+    /**
+     * Returns the consent activity component from config lookup.
+     */
+    private ComponentName getConsentComponent() {
+        Context context = getContext();
+        String consentComponent = Settings.Secure.getStringForUser(
+                context.getContentResolver(),
+                Settings.Secure.AMBIENT_CONTEXT_CONSENT_COMPONENT,
+                getUserId());
+        if (TextUtils.isEmpty(consentComponent)) {
+            return null;
+        }
+        return ComponentName.unflattenFromString(consentComponent);
+    }
+
+    /**
+     * Sends the result response with the specified status to the callback.
+     */
+    void sendStatusToCallback(RemoteCallback callback,
+                    @AmbientContextManager.StatusCode int status) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(
+                AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
+                status);
+        callback.sendResult(bundle);
+    }
+
     @VisibleForTesting
     void stopDetection(String packageName) {
         Slog.d(TAG, "Stop detection for " + packageName);
@@ -240,12 +384,13 @@
      * Sends out the Intent to the client after the event is detected.
      *
      * @param pendingIntent Client's PendingIntent for callback
-     * @param response Response with status code and detection result
+     * @param result result from the detection service
      */
-    private void sendResponseIntent(PendingIntent pendingIntent,
-            AmbientContextEventResponse response) {
+    private void sendDetectionResultIntent(PendingIntent pendingIntent,
+            AmbientContextDetectionResult result) {
         Intent intent = new Intent();
-        intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE, response);
+        intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS,
+                new ArrayList(result.getEvents()));
         // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing
         // the PendingIntent as a backdoor to do this.
         BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -254,38 +399,29 @@
             pendingIntent.send(getContext(), 0, intent, null, null, null,
                     options.toBundle());
             Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
-                    + response);
+                    + result);
         } catch (PendingIntent.CanceledException e) {
             Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent);
         }
     }
 
     @NonNull
-    private RemoteCallback createRemoteCallback() {
+    private RemoteCallback createDetectionResultRemoteCallback() {
         return new RemoteCallback(result -> {
-            AmbientContextEventResponse response = (AmbientContextEventResponse) result.get(
-                            AmbientContextDetectionService.RESPONSE_BUNDLE_KEY);
+            AmbientContextDetectionResult detectionResult =
+                    (AmbientContextDetectionResult) result.get(
+                            AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
             final long token = Binder.clearCallingIdentity();
             try {
-                Set<PendingIntent> pendingIntentForFailedRequests = new HashSet<>();
                 for (PendingIntent pendingIntent : mExistingPendingIntents) {
-                    // Send PendingIntent if a requesting package matches the response packages.
-                    if (response.getPackageName().equals(pendingIntent.getCreatorPackage())) {
-                        sendResponseIntent(pendingIntent, response);
-
-                        int statusCode = response.getStatusCode();
-                        if (statusCode != AmbientContextEventResponse.STATUS_SUCCESS) {
-                            pendingIntentForFailedRequests.add(pendingIntent);
-                        }
-                        Slog.i(TAG, "Got response of " + response.getEvents() + " for "
-                                + pendingIntent.getCreatorPackage() + ". Status: " + statusCode);
+                    // Send PendingIntent to requesting packages
+                    String creatorPackage = pendingIntent.getCreatorPackage();
+                    if (detectionResult.getPackageName().equals(creatorPackage)) {
+                        sendDetectionResultIntent(pendingIntent, detectionResult);
+                        Slog.i(TAG, "Got detection result of " + detectionResult.getEvents()
+                                + " for " + creatorPackage);
                     }
                 }
-
-                // Removes the failed requests from the existing requests.
-                for (PendingIntent pendingIntent : pendingIntentForFailedRequests) {
-                    mExistingPendingIntents.remove(pendingIntent);
-                }
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index 33905f2..2cdf7e7 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -24,8 +24,8 @@
 import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
-import android.app.ambientcontext.AmbientContextEventResponse;
-import android.app.ambientcontext.IAmbientContextEventObserver;
+import android.app.ambientcontext.AmbientContextManager;
+import android.app.ambientcontext.IAmbientContextManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
@@ -76,7 +76,7 @@
 
     @Override
     public void onStart() {
-        publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextEventObserver());
+        publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextManagerInternal());
     }
 
     @Override
@@ -128,14 +128,16 @@
      * specified events. Intended for use by shell command for testing.
      * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
      */
-    void startAmbientContextEvent(@UserIdInt int userId, AmbientContextEventRequest request,
-            String packageName, RemoteCallback callback) {
+    void startDetection(@UserIdInt int userId, AmbientContextEventRequest request,
+            String packageName, RemoteCallback detectionResultCallback,
+            RemoteCallback statusCallback) {
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
         synchronized (mLock) {
             final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
             if (service != null) {
-                service.startDetection(request, packageName, callback);
+                service.startDetection(request, packageName, detectionResultCallback,
+                        statusCallback);
             } else {
                 Slog.i(TAG, "service not available for user_id: " + userId);
             }
@@ -161,6 +163,25 @@
     }
 
     /**
+     * Send request to the remote AmbientContextDetectionService impl to query the status of the
+     * specified events. Intended for use by shell command for testing.
+     * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
+     */
+    void queryServiceStatus(@UserIdInt int userId, String packageName,
+            int[] eventTypes, RemoteCallback callback) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+        synchronized (mLock) {
+            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service != null) {
+                service.onQueryServiceStatus(eventTypes, packageName, callback);
+            } else {
+                Slog.i(TAG, "query service not available for user_id: " + userId);
+            }
+        }
+    }
+
+    /**
      * Returns the AmbientContextManagerPerUserService component for this user.
      */
     public ComponentName getComponentName(@UserIdInt int userId) {
@@ -173,34 +194,65 @@
         return null;
     }
 
-    private final class AmbientContextEventObserver extends IAmbientContextEventObserver.Stub {
+    private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub {
         final AmbientContextManagerPerUserService mService = getServiceForUserLocked(
                 UserHandle.getCallingUserId());
 
         @Override
         public void registerObserver(
-                AmbientContextEventRequest request, PendingIntent pendingIntent) {
+                AmbientContextEventRequest request, PendingIntent resultPendingIntent,
+                RemoteCallback statusCallback) {
             Objects.requireNonNull(request);
-            Objects.requireNonNull(pendingIntent);
+            Objects.requireNonNull(resultPendingIntent);
+            Objects.requireNonNull(statusCallback);
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+            assertCalledByPackageOwner(resultPendingIntent.getCreatorPackage());
             if (!mIsServiceEnabled) {
                 Slog.w(TAG, "Service not available.");
-                mService.sendStatusUpdateIntent(pendingIntent,
-                        AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE);
+                mService.sendStatusCallback(statusCallback,
+                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onRegisterObserver(request, pendingIntent);
+            mService.onRegisterObserver(request, resultPendingIntent, statusCallback);
         }
 
         @Override
         public void unregisterObserver(String callingPackage) {
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+            assertCalledByPackageOwner(callingPackage);
             mService.onUnregisterObserver(callingPackage);
         }
 
         @Override
+        public void queryServiceStatus(int[] eventTypes, String callingPackage,
+                RemoteCallback statusCallback) {
+            Objects.requireNonNull(eventTypes);
+            Objects.requireNonNull(callingPackage);
+            Objects.requireNonNull(statusCallback);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
+            assertCalledByPackageOwner(callingPackage);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Detection service not available.");
+                mService.sendStatusToCallback(statusCallback,
+                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            mService.onQueryServiceStatus(eventTypes, callingPackage,
+                    statusCallback);
+        }
+
+        @Override
+        public void startConsentActivity(int[] eventTypes, String callingPackage) {
+            Objects.requireNonNull(eventTypes);
+            Objects.requireNonNull(callingPackage);
+            assertCalledByPackageOwner(callingPackage);
+            mService.onStartConsentActivity(eventTypes, callingPackage);
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
                 return;
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
index b5cd985..010bf1b 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
@@ -21,12 +21,12 @@
 import android.annotation.NonNull;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
-import android.app.ambientcontext.AmbientContextEventResponse;
 import android.content.ComponentName;
 import android.os.Binder;
 import android.os.RemoteCallback;
 import android.os.ShellCommand;
-import android.service.ambientcontext.AmbientContextDetectionService;
+import android.service.ambientcontext.AmbientContextDetectionResult;
+import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
 
 import java.io.PrintWriter;
 
@@ -35,6 +35,12 @@
  */
 final class AmbientContextShellCommand extends ShellCommand {
 
+    private static final AmbientContextEventRequest REQUEST =
+            new AmbientContextEventRequest.Builder()
+                    .addEventType(AmbientContextEvent.EVENT_COUGH)
+                    .addEventType(AmbientContextEvent.EVENT_SNORE)
+                    .build();
+
     @NonNull
     private final AmbientContextManagerService mService;
 
@@ -44,22 +50,43 @@
 
     /** Callbacks for AmbientContextEventService results used internally for testing. */
     static class TestableCallbackInternal {
-        private AmbientContextEventResponse mLastResponse;
+        private AmbientContextDetectionResult mLastResult;
+        private AmbientContextDetectionServiceStatus mLastStatus;
 
-        public AmbientContextEventResponse getLastResponse() {
-            return mLastResponse;
+        public AmbientContextDetectionResult getLastResult() {
+            return mLastResult;
+        }
+
+        public AmbientContextDetectionServiceStatus getLastStatus() {
+            return mLastStatus;
         }
 
         @NonNull
-        private RemoteCallback createRemoteCallback() {
+        private RemoteCallback createRemoteDetectionResultCallback() {
             return new RemoteCallback(result -> {
-                AmbientContextEventResponse response =
-                        (AmbientContextEventResponse) result.get(
-                                AmbientContextDetectionService.RESPONSE_BUNDLE_KEY);
+                AmbientContextDetectionResult detectionResult =
+                        (AmbientContextDetectionResult) result.get(
+                                AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
                 final long token = Binder.clearCallingIdentity();
                 try {
-                    mLastResponse = response;
-                    out.println("Response available: " + response);
+                    mLastResult = detectionResult;
+                    out.println("Detection result available: " + detectionResult);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            });
+        }
+
+        @NonNull
+        private RemoteCallback createRemoteStatusCallback() {
+            return new RemoteCallback(result -> {
+                AmbientContextDetectionServiceStatus status =
+                        (AmbientContextDetectionServiceStatus) result.get(
+                                AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mLastStatus = status;
+                    out.println("Status available: " + status);
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -83,6 +110,8 @@
                 return runStopDetection();
             case "get-last-status-code":
                 return getLastStatusCode();
+            case "query-service-status":
+                return runQueryServiceStatus();
             case "get-bound-package":
                 return getBoundPackageName();
             case "set-temporary-service":
@@ -95,13 +124,9 @@
     private int runStartDetection() {
         final int userId = Integer.parseInt(getNextArgRequired());
         final String packageName = getNextArgRequired();
-        AmbientContextEventRequest request = new AmbientContextEventRequest.Builder()
-                .addEventType(AmbientContextEvent.EVENT_COUGH)
-                .addEventType(AmbientContextEvent.EVENT_SNORE)
-                .build();
-
-        mService.startAmbientContextEvent(userId, request, packageName,
-                sTestableCallbackInternal.createRemoteCallback());
+        mService.startDetection(userId, REQUEST, packageName,
+                sTestableCallbackInternal.createRemoteDetectionResultCallback(),
+                sTestableCallbackInternal.createRemoteStatusCallback());
         return 0;
     }
 
@@ -112,8 +137,20 @@
         return 0;
     }
 
+    private int runQueryServiceStatus() {
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String packageName = getNextArgRequired();
+        int[] types = new int[] {
+                AmbientContextEvent.EVENT_COUGH,
+                AmbientContextEvent.EVENT_SNORE};
+        mService.queryServiceStatus(userId, packageName, types,
+                sTestableCallbackInternal.createRemoteStatusCallback());
+        return 0;
+    }
+
     private int getLastStatusCode() {
-        AmbientContextEventResponse lastResponse = sTestableCallbackInternal.getLastResponse();
+        AmbientContextDetectionServiceStatus lastResponse =
+                sTestableCallbackInternal.getLastStatus();
         if (lastResponse == null) {
             return -1;
         }
@@ -130,6 +167,7 @@
         pw.println("  start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection.");
         pw.println("  stop-detection USER_ID: Stops AmbientContextEvent detection.");
         pw.println("  get-last-status-code: Prints the latest request status code.");
+        pw.println("  query-event-status USER_ID PACKAGE_NAME: Prints the event status code.");
         pw.println("  get-bound-package USER_ID:"
                 + "     Print the bound package that implements the service.");
         pw.println("  set-temporary-service USER_ID [COMPONENT_NAME DURATION]");
diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
index 5cc29b3..f42080d 100644
--- a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
+++ b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
@@ -20,6 +20,7 @@
 import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
 
 import android.annotation.NonNull;
+import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
 import android.content.ComponentName;
 import android.content.Context;
@@ -53,13 +54,15 @@
      *
      * @param request The request with events to detect, and optional detection options.
      * @param packageName The app package that requested the detection
-     * @param callback callback for detection results
+     * @param detectionResultCallback callback for detection results
+     * @param statusCallback callback for service status
      */
     public void startDetection(
             @NonNull AmbientContextEventRequest request, String packageName,
-            RemoteCallback callback) {
+            RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
         Slog.i(TAG, "Start detection for " + request.getEventTypes());
-        post(service -> service.startDetection(request, packageName, callback));
+        post(service -> service.startDetection(request, packageName, detectionResultCallback,
+                statusCallback));
     }
 
     /**
@@ -71,4 +74,15 @@
         Slog.i(TAG, "Stop detection for " + packageName);
         post(service -> service.stopDetection(packageName));
     }
+
+    /**
+     * Asks the implementation to return the event status for the package.
+     */
+    public void queryServiceStatus(
+            @AmbientContextEvent.EventCode int[] eventTypes,
+            String packageName,
+            RemoteCallback callback) {
+        Slog.i(TAG, "Query status for " + packageName);
+        post(service -> service.queryServiceStatus(eventTypes, packageName, callback));
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 066c263..ea054a5 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -153,6 +153,7 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -205,6 +206,7 @@
     private final NetworkInfo mNetworkInfo;
     private int mLegacyState;
     @VisibleForTesting protected String mPackage;
+    private String mSessionKey;
     private int mOwnerUID;
     private boolean mIsPackageTargetingAtLeastQ;
     @VisibleForTesting
@@ -1317,6 +1319,7 @@
                 .setLegacyType(ConnectivityManager.TYPE_VPN)
                 .setLegacyTypeName("VPN")
                 .setBypassableVpn(mConfig.allowBypass && !mLockdown)
+                .setVpnRequiresValidation(mConfig.requiresInternetValidation)
                 .build();
 
         capsBuilder.setOwnerUid(mOwnerUID);
@@ -2517,6 +2520,7 @@
             mProfile = profile;
             mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
             mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this);
+            mSessionKey = UUID.randomUUID().toString();
         }
 
         @Override
@@ -2838,6 +2842,7 @@
          */
         private void disconnectVpnRunner() {
             mActiveNetwork = null;
+            mSessionKey = null;
             mIsRunning = false;
 
             resetIkeState();
@@ -3329,7 +3334,7 @@
      *
      * @param packageName the package name of the app provisioning this profile
      */
-    public synchronized void startVpnProfile(@NonNull String packageName) {
+    public synchronized String startVpnProfile(@NonNull String packageName) {
         requireNonNull(packageName, "No package name provided");
 
         enforceNotRestrictedUser();
@@ -3347,6 +3352,7 @@
             }
 
             startVpnProfilePrivileged(profile, packageName);
+            return mSessionKey;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -3378,6 +3384,7 @@
             }
             mConfig.startTime = SystemClock.elapsedRealtime();
             mConfig.proxyInfo = profile.proxy;
+            mConfig.requiresInternetValidation = profile.requiresInternetValidation;
 
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 35e3db78..a311ba1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -418,7 +418,7 @@
                 || !Objects.equals(deviceProductInfo, other.deviceProductInfo)
                 || ownerUid != other.ownerUid
                 || !Objects.equals(ownerPackageName, other.ownerPackageName)
-                || !Objects.equals(frameRateOverrides, other.frameRateOverrides)
+                || !Arrays.equals(frameRateOverrides, other.frameRateOverrides)
                 || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
                 || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
                 || !BrightnessSynchronizer.floatEquals(brightnessDefault,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index a31c231..c02e725 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -997,7 +997,7 @@
 
         public boolean updateFrameRateOverridesLocked(
                 DisplayEventReceiver.FrameRateOverride[] overrides) {
-            if (overrides.equals(mFrameRateOverrides)) {
+            if (Arrays.equals(overrides, mFrameRateOverrides)) {
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b814782..b2f500a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -422,7 +422,7 @@
             addFreshWindowToken();
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
-                    null, null, mCurId, mCurSeq, false);
+                    null, null, null, mCurId, mCurSeq, false);
         }
 
         Slog.w(InputMethodManagerService.TAG,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 80c83e9..29dcdfa 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInputMethodSession;
 import com.android.internal.view.InlineSuggestionsRequestInfo;
 import com.android.server.LocalServices;
 
@@ -149,6 +150,24 @@
     public abstract void updateImeWindowStatus(boolean disableImeIcon);
 
     /**
+     * Callback when the IInputMethodSession from the accessibility service with the specified
+     * accessibilityConnectionId is created.
+     *
+     * @param accessibilityConnectionId The connection id of the accessibility service.
+     * @param session The session passed back from the accessibility service.
+     */
+    public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId,
+            IInputMethodSession session);
+
+    /**
+     * Unbind the accessibility service with the specified accessibilityConnectionId from current
+     * client.
+     *
+     * @param accessibilityConnectionId The connection id of the accessibility service.
+     */
+    public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId);
+
+    /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
      */
     private static final InputMethodManagerInternal NOP =
@@ -211,6 +230,15 @@
                 @Override
                 public void updateImeWindowStatus(boolean disableImeIcon) {
                 }
+
+                @Override
+                public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
+                        IInputMethodSession session) {
+                }
+
+                @Override
+                public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
+                }
             };
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ba15a04..7068ed1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -94,6 +94,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
@@ -120,6 +121,7 @@
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.IWindowManager;
 import android.view.InputChannel;
@@ -171,6 +173,7 @@
 import com.android.internal.view.IInputMethodSession;
 import com.android.internal.view.IInputSessionCallback;
 import com.android.internal.view.InlineSuggestionsRequestInfo;
+import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -228,7 +231,9 @@
     private static final int MSG_START_HANDWRITING = 1100;
 
     private static final int MSG_UNBIND_CLIENT = 3000;
+    private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 3001;
     private static final int MSG_BIND_CLIENT = 3010;
+    private static final int MSG_BIND_ACCESSIBILITY_SERVICE = 3011;
     private static final int MSG_SET_ACTIVE = 3020;
     private static final int MSG_SET_INTERACTIVE = 3030;
     private static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
@@ -351,6 +356,33 @@
         }
     }
 
+    /**
+     * Record session state for an accessibility service.
+     */
+    private static class AccessibilitySessionState {
+        final ClientState mClient;
+        // Id of the accessibility service.
+        final int mId;
+
+        public IInputMethodSession mSession;
+
+        @Override
+        public String toString() {
+            return "AccessibilitySessionState{uid " + mClient.uid + " pid " + mClient.pid
+                    + " id " + Integer.toHexString(mId)
+                    + " session " + Integer.toHexString(
+                    System.identityHashCode(mSession))
+                    + "}";
+        }
+
+        AccessibilitySessionState(ClientState client, int id,
+                IInputMethodSession session) {
+            mClient = client;
+            mId = id;
+            mSession = session;
+        }
+    }
+
     private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
         private final InputMethodManagerService mImms;
         private final IInputMethodClient mClient;
@@ -376,7 +408,9 @@
         final ClientDeathRecipient clientDeathRecipient;
 
         boolean sessionRequested;
+        boolean mSessionRequestedForAccessibility;
         SessionState curSession;
+        SparseArray<AccessibilitySessionState> mAccessibilitySessions = new SparseArray<>();
 
         @Override
         public String toString() {
@@ -637,10 +671,16 @@
     boolean mBoundToMethod;
 
     /**
+     * Have we called bindInput() for accessibility services?
+     */
+    boolean mBoundToAccessibility;
+
+     /**
      * Currently enabled session.
      */
     @GuardedBy("ImfLock.class")
     SessionState mEnabledSession;
+    SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>();
 
     /**
      * True if the device is currently interactive with user.  The value is true initially.
@@ -2188,6 +2228,7 @@
             if (cs != null) {
                 client.asBinder().unlinkToDeath(cs.clientDeathRecipient, 0);
                 clearClientSessionLocked(cs);
+                clearClientSessionForAccessibilityLocked(cs);
                 if (mCurClient == cs) {
                     hideCurrentInputLocked(
                             mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
@@ -2195,9 +2236,13 @@
                         mBoundToMethod = false;
                         IInputMethodInvoker curMethod = getCurMethodLocked();
                         if (curMethod != null) {
+                            // When we unbind input, we are unbinding the client, so we always
+                            // unbind ime and a11y together.
                             curMethod.unbindInput();
+                            AccessibilityManagerInternal.get().unbindInput();
                         }
                     }
+                    mBoundToAccessibility = false;
                     mCurClient = null;
                 }
                 if (mCurFocusedWindowClient == cs) {
@@ -2216,6 +2261,24 @@
     }
 
     @NonNull
+    private Message obtainMessageOOO(int what, Object arg1, Object arg2, Object arg3) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg1;
+        args.arg2 = arg2;
+        args.arg3 = arg3;
+        return mHandler.obtainMessage(what, 0, 0, args);
+    }
+
+    @NonNull
+    private Message obtainMessageIIOO(int what, int arg1, int arg2,
+            Object arg3, Object arg4) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = arg3;
+        args.arg2 = arg4;
+        return mHandler.obtainMessage(what, arg1, arg2, args);
+    }
+
+    @NonNull
     private Message obtainMessageIIIO(int what, int argi1, int argi2, int argi3, Object arg1) {
         final SomeArgs args = SomeArgs.obtain();
         args.arg1 = arg1;
@@ -2252,13 +2315,18 @@
                     curMethod.unbindInput();
                 }
             }
+            mBoundToAccessibility = false;
 
+            // Since we set active false to current client and set mCurClient to null, let's unbind
+            // all accessibility too. That means, when input method get disconnected (including
+            // switching ime), we also unbind accessibility
             scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */,
                     false /* reportToImeController */);
             executeOrSendMessage(mCurClient.client, mHandler.obtainMessage(
                     MSG_UNBIND_CLIENT, getSequenceNumberLocked(), unbindClientReason,
                     mCurClient.client));
             mCurClient.sessionRequested = false;
+            mCurClient.mSessionRequestedForAccessibility = false;
             mCurClient = null;
 
             mMenuController.hideInputMethodMenuLocked();
@@ -2340,11 +2408,63 @@
         final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
+        final SparseArray<IInputMethodSession> accessibilityInputMethodSessions =
+                createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
-                session.session, (session.channel != null ? session.channel.dup() : null),
+                session.session, accessibilityInputMethodSessions,
+                (session.channel != null ? session.channel.dup() : null),
                 curId, getSequenceNumberLocked(), suppressesSpellChecker);
     }
 
+    @GuardedBy("ImfLock.class")
+    @Nullable
+    InputBindResult attachNewAccessibilityLocked(@StartInputReason int startInputReason,
+            boolean initial, int id) {
+        if (!mBoundToAccessibility) {
+            AccessibilityManagerInternal.get().bindInput(mCurClient.binding);
+            mBoundToAccessibility = true;
+        }
+
+        // TODO(b/187453053): grantImplicitAccess to accessibility services access? if so, need to
+        //  record accessibility services uid.
+
+        final AccessibilitySessionState accessibilitySession =
+                mCurClient.mAccessibilitySessions.get(id);
+        // We don't start input when session for a11y is created. We start input when
+        // input method start input, a11y manager service is always on.
+        if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) {
+            final Binder startInputToken = new Binder();
+            setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions);
+            AccessibilityManagerInternal.get().startInput(startInputToken, mCurInputContext,
+                    mCurAttribute, !initial /* restarting */);
+        }
+
+        if (accessibilitySession != null) {
+            final SessionState session = mCurClient.curSession;
+            IInputMethodSession imeSession = session == null ? null : session.session;
+            final SparseArray<IInputMethodSession> accessibilityInputMethodSessions =
+                    createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
+            return new InputBindResult(
+                    InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
+                    imeSession, accessibilityInputMethodSessions, null,
+                    getCurIdLocked(), getSequenceNumberLocked(), false);
+        }
+        return null;
+    }
+
+    private SparseArray<IInputMethodSession> createAccessibilityInputMethodSessions(
+            SparseArray<AccessibilitySessionState> accessibilitySessions) {
+        final SparseArray<IInputMethodSession> accessibilityInputMethodSessions =
+                new SparseArray<>();
+        if (accessibilitySessions != null) {
+            for (int i = 0; i < accessibilitySessions.size(); i++) {
+                accessibilityInputMethodSessions.append(accessibilitySessions.keyAt(i),
+                        accessibilitySessions.valueAt(i).mSession);
+            }
+        }
+        return accessibilityInputMethodSessions;
+    }
+
     /**
      * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the
      * selected InputMethod to the given focused IME client.
@@ -2370,7 +2490,7 @@
             // party code.
             return new InputBindResult(
                     InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
-                    null, null, selectedMethodId, getSequenceNumberLocked(), false);
+                    null, null, null, selectedMethodId, getSequenceNumberLocked(), false);
         }
 
         if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
@@ -2422,6 +2542,17 @@
             if (cs.curSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
+                // This doesn't mean a11y sessions are there. When a11y service is
+                // enabled while this client is switched out, this client doesn't have the session.
+                // A11yManagerService will only request missing sessions (will not request existing
+                // sessions again). Note when an a11y service is disabled, it will clear its
+                // session from all clients, so we don't need to worry about disabled a11y services.
+                cs.mSessionRequestedForAccessibility = false;
+                requestClientSessionForAccessibilityLocked(cs);
+                // we can always attach to accessibility because AccessibilityManagerService is
+                // always on.
+                attachNewAccessibilityLocked(startInputReason,
+                        (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, -1);
                 return attachNewInputLocked(startInputReason,
                         (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
             }
@@ -2464,9 +2595,10 @@
                 // Return to client, and we will get back with it when
                 // we have had a session made for it.
                 requestClientSessionLocked(cs);
+                requestClientSessionForAccessibilityLocked(cs);
                 return new InputBindResult(
                         InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
-                        null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+                        null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
             } else {
                 long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked();
                 if (bindingDuration < TIME_TO_RECONNECT) {
@@ -2479,7 +2611,7 @@
                     // to see if we can get back in touch with the service.
                     return new InputBindResult(
                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
-                            null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+                            null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
                 } else {
                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
                             getSelectedMethodIdLocked(), bindingDuration, 0);
@@ -2565,6 +2697,8 @@
                                 method, session, channel);
                         InputBindResult res = attachNewInputLocked(
                                 StartInputReason.SESSION_CREATED_BY_IME, true);
+                        attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME,
+                                true, -1);
                         if (res.method != null) {
                             executeOrSendMessage(mCurClient.client, obtainMessageOO(
                                     MSG_BIND_CLIENT, mCurClient.client, res));
@@ -2602,7 +2736,9 @@
     void reRequestCurrentClientSessionLocked() {
         if (mCurClient != null) {
             clearClientSessionLocked(mCurClient);
+            clearClientSessionForAccessibilityLocked(mCurClient);
             requestClientSessionLocked(mCurClient);
+            requestClientSessionForAccessibilityLocked(mCurClient);
         }
     }
 
@@ -2646,6 +2782,19 @@
     }
 
     @GuardedBy("ImfLock.class")
+    void requestClientSessionForAccessibilityLocked(ClientState cs) {
+        if (!cs.mSessionRequestedForAccessibility) {
+            if (DEBUG) Slog.v(TAG, "Creating new accessibility sessions for client " + cs);
+            cs.mSessionRequestedForAccessibility = true;
+            ArraySet<Integer> ignoreSet = new ArraySet<>();
+            for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) {
+                ignoreSet.add(cs.mAccessibilitySessions.keyAt(i));
+            }
+            AccessibilityManagerInternal.get().createImeSession(ignoreSet);
+        }
+    }
+
+    @GuardedBy("ImfLock.class")
     void clearClientSessionLocked(ClientState cs) {
         finishSessionLocked(cs.curSession);
         cs.curSession = null;
@@ -2653,6 +2802,24 @@
     }
 
     @GuardedBy("ImfLock.class")
+    void clearClientSessionForAccessibilityLocked(ClientState cs) {
+        for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) {
+            finishSessionForAccessibilityLocked(cs.mAccessibilitySessions.valueAt(i));
+        }
+        cs.mAccessibilitySessions.clear();
+        cs.mSessionRequestedForAccessibility = false;
+    }
+
+    @GuardedBy("ImfLock.class")
+    void clearClientSessionForAccessibilityLocked(ClientState cs, int id) {
+        AccessibilitySessionState session = cs.mAccessibilitySessions.get(id);
+        if (session != null) {
+            finishSessionForAccessibilityLocked(session);
+            cs.mAccessibilitySessions.remove(id);
+        }
+    }
+
+    @GuardedBy("ImfLock.class")
     private void finishSessionLocked(SessionState sessionState) {
         if (sessionState != null) {
             if (sessionState.session != null) {
@@ -2672,15 +2839,34 @@
     }
 
     @GuardedBy("ImfLock.class")
+    private void finishSessionForAccessibilityLocked(AccessibilitySessionState sessionState) {
+        if (sessionState != null) {
+            if (sessionState.mSession != null) {
+                try {
+                    sessionState.mSession.finishSession();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Session failed to close due to remote exception", e);
+                }
+                sessionState.mSession = null;
+            }
+        }
+    }
+
+    @GuardedBy("ImfLock.class")
     void clearClientSessionsLocked() {
         if (getCurMethodLocked() != null) {
             final int numClients = mClients.size();
             for (int i = 0; i < numClients; ++i) {
                 clearClientSessionLocked(mClients.valueAt(i));
+                clearClientSessionForAccessibilityLocked(mClients.valueAt(i));
             }
 
             finishSessionLocked(mEnabledSession);
+            for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
+                finishSessionForAccessibilityLocked(mEnabledAccessibilitySessions.valueAt(i));
+            }
             mEnabledSession = null;
+            mEnabledAccessibilitySessions.clear();
             scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
         }
         hideStatusBarIconLocked();
@@ -3455,7 +3641,7 @@
             }
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
-                    null, null, null, -1, false);
+                    null, null, null, null, -1, false);
         }
 
         mCurFocusedWindow = windowToken;
@@ -4250,6 +4436,41 @@
         }
     }
 
+    @GuardedBy("ImfLock.class")
+    void setEnabledSessionForAccessibilityLocked(
+            SparseArray<AccessibilitySessionState> accessibilitySessions) {
+        // mEnabledAccessibilitySessions could the same object as accessibilitySessions.
+        SparseArray<IInputMethodSession> disabledSessions = new SparseArray<>();
+        for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
+            if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) {
+                AccessibilitySessionState sessionState  = mEnabledAccessibilitySessions.valueAt(i);
+                if (sessionState != null) {
+                    disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i),
+                            sessionState.mSession);
+                }
+            }
+        }
+        if (disabledSessions.size() > 0) {
+            AccessibilityManagerInternal.get().setImeSessionEnabled(disabledSessions,
+                    false);
+        }
+        SparseArray<IInputMethodSession> enabledSessions = new SparseArray<>();
+        for (int i = 0; i < accessibilitySessions.size(); i++) {
+            if (!mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) {
+                AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i);
+                if (sessionState != null) {
+                    enabledSessions.append(accessibilitySessions.keyAt(i), sessionState.mSession);
+                }
+            }
+        }
+        if (enabledSessions.size() > 0) {
+            AccessibilityManagerInternal.get().setImeSessionEnabled(enabledSessions,
+                    true);
+        }
+        mEnabledAccessibilitySessions = accessibilitySessions;
+    }
+
+    @SuppressWarnings("unchecked")
     @UiThread
     @Override
     public boolean handleMessage(Message msg) {
@@ -4319,13 +4540,34 @@
 
             // ---------------------------------------------------------
 
-            case MSG_UNBIND_CLIENT:
+            case MSG_UNBIND_CLIENT: {
                 try {
-                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
+                    // This unbinds all accessibility services too.
+                    ((IInputMethodClient) msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
                 } catch (RemoteException e) {
                     // There is nothing interesting about the last client dying.
+                    if (!(e instanceof DeadObjectException)) {
+                        Slog.w(TAG, "RemoteException when unbinding input method service or"
+                                + "accessibility services");
+                    }
                 }
                 return true;
+            }
+            case MSG_UNBIND_ACCESSIBILITY_SERVICE: {
+                args = (SomeArgs) msg.obj;
+                IInputMethodClient client = (IInputMethodClient) args.arg1;
+                int id = (int) args.arg2;
+                try {
+                    client.onUnbindAccessibilityService(msg.arg1, id);
+                } catch (RemoteException e) {
+                    // There is nothing interesting about the last client dying.
+                    if (!(e instanceof DeadObjectException)) {
+                        Slog.w(TAG, "RemoteException when unbinding accessibility services");
+                    }
+                }
+                args.recycle();
+                return true;
+            }
             case MSG_BIND_CLIENT: {
                 args = (SomeArgs)msg.obj;
                 IInputMethodClient client = (IInputMethodClient)args.arg1;
@@ -4344,6 +4586,25 @@
                 args.recycle();
                 return true;
             }
+            case MSG_BIND_ACCESSIBILITY_SERVICE: {
+                args = (SomeArgs) msg.obj;
+                IInputMethodClient client = (IInputMethodClient) args.arg1;
+                InputBindResult res = (InputBindResult) args.arg2;
+                int id = (int) args.arg3;
+                try {
+                    client.onBindAccessibilityService(res, id);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
+                } finally {
+                    // Dispose the channel if the accessibility service is not local to this process
+                    // because the remote proxy will get its own copy when unparceled.
+                    if (res.channel != null && Binder.isProxy(client)) {
+                        res.channel.dispose();
+                    }
+                }
+                args.recycle();
+                return true;
+            }
             case MSG_SET_ACTIVE: {
                 args = (SomeArgs) msg.obj;
                 final ClientState clientState = (ClientState) args.arg1;
@@ -5025,6 +5286,59 @@
             mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
                     .sendToTarget();
         }
+
+        @Override
+        public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
+                IInputMethodSession session) {
+            synchronized (ImfLock.class) {
+                if (mCurClient != null) {
+                    clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
+                    mCurClient.mAccessibilitySessions.put(accessibilityConnectionId,
+                            new AccessibilitySessionState(mCurClient, accessibilityConnectionId,
+                                    session));
+                    InputBindResult res = attachNewAccessibilityLocked(
+                            StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY, true,
+                            accessibilityConnectionId);
+                    executeOrSendMessage(mCurClient.client, obtainMessageOOO(
+                            MSG_BIND_ACCESSIBILITY_SERVICE, mCurClient.client, res,
+                            accessibilityConnectionId));
+                }
+            }
+        }
+
+        @Override
+        public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
+            synchronized (ImfLock.class) {
+                if (mCurClient != null) {
+                    if (DEBUG) {
+                        Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client="
+                                + mCurClient.client.asBinder());
+                    }
+                    // A11yManagerService unbinds the disabled accessibility service. We don't need
+                    // to do it here.
+                    @UnbindReason int unbindClientReason =
+                            UnbindReason.ACCESSIBILITY_SERVICE_DISABLED;
+                    executeOrSendMessage(mCurClient.client, obtainMessageIIOO(
+                            MSG_UNBIND_ACCESSIBILITY_SERVICE, getSequenceNumberLocked(),
+                            unbindClientReason, mCurClient.client, accessibilityConnectionId));
+                }
+                // We only have sessions when we bound to an input method. Remove this session
+                // from all clients.
+                if (getCurMethodLocked() != null) {
+                    final int numClients = mClients.size();
+                    for (int i = 0; i < numClients; ++i) {
+                        clearClientSessionForAccessibilityLocked(mClients.valueAt(i),
+                                accessibilityConnectionId);
+                    }
+                    AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
+                            accessibilityConnectionId);
+                    if (session != null) {
+                        finishSessionForAccessibilityLocked(session);
+                        mEnabledAccessibilitySessions.remove(accessibilityConnectionId);
+                    }
+                }
+            }
+        }
     }
 
     @BinderThread
@@ -5184,6 +5498,8 @@
                 p.println("    client=" + ci.client);
                 p.println("    inputContext=" + ci.inputContext);
                 p.println("    sessionRequested=" + ci.sessionRequested);
+                p.println("    sessionRequestedForAccessibility="
+                        + ci.mSessionRequestedForAccessibility);
                 p.println("    curSession=" + ci.curSession);
             }
             p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
deleted file mode 100644
index 6b442a6..0000000
--- a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2022 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.logcat;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.ServiceManager;
-import android.os.logcat.ILogcatManagerService;
-import android.util.Slog;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.internal.R;
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
-
-
-/**
- * This dialog is shown to the user before an activity in a harmful app is launched.
- *
- * See {@code PackageManager.setLogcatAppInfo} for more info.
- */
-public class LogAccessConfirmationActivity extends AlertActivity implements
-        DialogInterface.OnClickListener {
-    private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName();
-
-    private String mPackageName;
-    private IntentSender mTarget;
-    private final ILogcatManagerService mLogcatManagerService =
-            ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat"));
-
-    private int mUid;
-    private int mGid;
-    private int mPid;
-    private int mFd;
-
-    private static final String EXTRA_UID = "uid";
-    private static final String EXTRA_GID = "gid";
-    private static final String EXTRA_PID = "pid";
-    private static final String EXTRA_FD = "fd";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final Intent intent = getIntent();
-        mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-        mUid = intent.getIntExtra("uid", 0);
-        mGid = intent.getIntExtra("gid", 0);
-        mPid = intent.getIntExtra("pid", 0);
-        mFd = intent.getIntExtra("fd", 0);
-
-        final AlertController.AlertParams p = mAlertParams;
-        p.mTitle = getString(R.string.log_access_confirmation_title);
-        p.mView = createView();
-
-        p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow);
-        p.mPositiveButtonListener = this;
-        p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny);
-        p.mNegativeButtonListener = this;
-
-        mAlert.installContent(mAlertParams);
-    }
-
-    private View createView() {
-        final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog,
-                null /*root*/);
-        ((TextView) view.findViewById(R.id.app_name_text))
-                .setText(mPackageName);
-        ((TextView) view.findViewById(R.id.message))
-                .setText(getIntent().getExtras().getString("body"));
-        return view;
-    }
-
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
-                try {
-                    mLogcatManagerService.approve(mUid, mGid, mPid, mFd);
-                } catch (Throwable t) {
-                    Slog.e(TAG, "Could not start the LogcatManagerService.", t);
-                }
-                finish();
-                break;
-            case DialogInterface.BUTTON_NEGATIVE:
-                try {
-                    mLogcatManagerService.decline(mUid, mGid, mPid, mFd);
-                } catch (Throwable t) {
-                    Slog.e(TAG, "Could not start the LogcatManagerService.", t);
-                }
-                finish();
-                break;
-        }
-    }
-
-    /**
-     * Create the Intent for a LogAccessConfirmationActivity.
-     */
-    public static Intent createIntent(Context context, String targetPackageName,
-            IntentSender target, int uid, int gid, int pid, int fd) {
-        final Intent intent = new Intent();
-        intent.setClass(context, LogAccessConfirmationActivity.class);
-        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
-        intent.putExtra(EXTRA_UID, uid);
-        intent.putExtra(EXTRA_GID, gid);
-        intent.putExtra(EXTRA_PID, pid);
-        intent.putExtra(EXTRA_FD, fd);
-
-        return intent;
-    }
-
-}
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 7832296..ff6372ae 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -16,36 +16,20 @@
 
 package com.android.server.logcat;
 
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.app.ActivityManagerInternal;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.ILogd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.os.logcat.ILogcatManagerService;
 import android.util.Slog;
 
-import com.android.internal.R;
-import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.util.ArrayUtils;
-import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
-import java.util.Arrays;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 /**
- * Service responsible for managing the access to Logcat.
+ * Service responsible for manage the access to Logcat.
  */
 public final class LogcatManagerService extends SystemService {
 
@@ -54,43 +38,6 @@
     private final BinderService mBinderService;
     private final ExecutorService mThreadExecutor;
     private ILogd mLogdService;
-    private NotificationManager mNotificationManager;
-    private @NonNull ActivityManager mActivityManager;
-    private ActivityManagerInternal mActivityManagerInternal;
-    private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2;
-    private static int sUidImportanceListenerCount = 0;
-    private static final int AID_APP_UID = 10000;
-
-    // TODO This allowlist is just a temporary workaround for the tests:
-    //      FrameworksServicesTests
-    //      PlatformRuleTests
-    // After adapting the test suites, the allowlist will be removed in
-    // the upcoming bug fix patches.
-    private static final String[] ALLOWABLE_TESTING_PACKAGES = {
-            "android.platform.test.rule.tests",
-            "com.android.frameworks.servicestests"
-    };
-
-    // TODO Same as the above ALLOWABLE_TESTING_PACKAGES.
-    private boolean isAllowableTestingPackage(int uid) {
-        PackageManager pm = mContext.getPackageManager();
-
-        String[] packageNames = pm.getPackagesForUid(uid);
-
-        if (ArrayUtils.isEmpty(packageNames)) {
-            return false;
-        }
-
-        for (String name : packageNames) {
-            Slog.e(TAG, "isAllowableTestingPackage: " + name);
-
-            if (Arrays.asList(ALLOWABLE_TESTING_PACKAGES).contains(name)) {
-                return true;
-            }
-        }
-
-        return false;
-    };
 
     private final class BinderService extends ILogcatManagerService.Stub {
         @Override
@@ -104,197 +51,6 @@
             // the logd data access is finished.
             mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false));
         }
-
-        @Override
-        public void approve(int uid, int gid, int pid, int fd) {
-            try {
-                getLogdService().approve(uid, gid, pid, fd);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        }
-
-        @Override
-        public void decline(int uid, int gid, int pid, int fd) {
-            try {
-                getLogdService().decline(uid, gid, pid, fd);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    private ILogd getLogdService() {
-        synchronized (LogcatManagerService.this) {
-            if (mLogdService == null) {
-                LogcatManagerService.this.addLogdService();
-            }
-            return mLogdService;
-        }
-    }
-
-    private String getBodyString(Context context, String callingPackage, int uid) {
-        PackageManager pm = context.getPackageManager();
-        try {
-            return context.getString(
-                com.android.internal.R.string.log_access_confirmation_body,
-                pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO,
-                    UserHandle.getUserId(uid)).loadLabel(pm));
-        } catch (NameNotFoundException e) {
-            // App name is unknown.
-            return null;
-        }
-    }
-
-    private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid,
-            int fd) {
-
-        final ActivityManagerInternal activityManagerInternal =
-                LocalServices.getService(ActivityManagerInternal.class);
-
-        PackageManager pm = mContext.getPackageManager();
-        String packageName = activityManagerInternal.getPackageNameByPid(pid);
-        if (packageName != null) {
-            String notificationBody = getBodyString(mContext, packageName, uid);
-
-            final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
-                    packageName, null, uid, gid, pid, fd);
-
-            if (notificationBody == null) {
-                // Decline the logd access if the nofitication body is unknown
-                Slog.e(TAG, "Unknown notification body, declining the logd access");
-                declineLogdAccess(uid, gid, pid, fd);
-                return;
-            }
-
-            // TODO Next version will replace notification with dialogue
-            // per UX guidance.
-            generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody,
-                    mIntent);
-            return;
-
-        }
-
-        String[] packageNames = pm.getPackagesForUid(uid);
-
-        if (ArrayUtils.isEmpty(packageNames)) {
-            // Decline the logd access if the app name is unknown
-            Slog.e(TAG, "Unknown calling package name, declining the logd access");
-            declineLogdAccess(uid, gid, pid, fd);
-            return;
-        }
-
-        String firstPackageName = packageNames[0];
-
-        if (firstPackageName == null || firstPackageName.length() == 0) {
-            // Decline the logd access if the package name from uid is unknown
-            Slog.e(TAG, "Unknown calling package name, declining the logd access");
-            declineLogdAccess(uid, gid, pid, fd);
-            return;
-        }
-
-        String notificationBody = getBodyString(mContext, firstPackageName, uid);
-
-        final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext,
-                firstPackageName, null, uid, gid, pid, fd);
-
-        if (notificationBody == null) {
-            Slog.e(TAG, "Unknown notification body, declining the logd access");
-            declineLogdAccess(uid, gid, pid, fd);
-            return;
-        }
-
-        // TODO Next version will replace notification with dialogue
-        // per UX guidance.
-        generateNotificationWithBodyContent(notificationId, clientInfo,
-                notificationBody, mIntent);
-    }
-
-    private void declineLogdAccess(int uid, int gid, int pid, int fd) {
-        try {
-            getLogdService().decline(uid, gid, pid, fd);
-        } catch (RemoteException ex) {
-            Slog.e(TAG, "Fails to call remote functions ", ex);
-        }
-    }
-
-    private void generateNotificationWithBodyContent(int notificationId, String clientInfo,
-            String notificationBody, Intent intent) {
-        final Notification.Builder notificationBuilder = new Notification.Builder(
-                mContext,
-                SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY);
-        intent.setFlags(
-                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        intent.setIdentifier(String.valueOf(notificationId) + clientInfo);
-        intent.putExtra("body", notificationBody);
-
-        notificationBuilder
-            .setSmallIcon(R.drawable.ic_info)
-            .setContentTitle(
-                mContext.getString(R.string.log_access_confirmation_title))
-            .setContentText(notificationBody)
-            .setContentIntent(
-                PendingIntent.getActivity(mContext, 0, intent,
-                    PendingIntent.FLAG_IMMUTABLE))
-            .setTicker(mContext.getString(R.string.log_access_confirmation_title))
-            .setOnlyAlertOnce(true)
-            .setAutoCancel(true);
-        mNotificationManager.notify(notificationId, notificationBuilder.build());
-    }
-
-    /**
-     * A class which watches an uid for background access and notifies the logdMonitor when
-     * the package status becomes foreground (importance change)
-     */
-    private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
-        private final int mExpectedUid;
-        private final int mExpectedGid;
-        private final int mExpectedPid;
-        private final int mExpectedFd;
-        private int mExpectedImportance;
-        private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE;
-
-        UidImportanceListener(int uid, int gid, int pid, int fd, int importance) {
-            mExpectedUid = uid;
-            mExpectedGid = gid;
-            mExpectedPid = pid;
-            mExpectedFd = fd;
-            mExpectedImportance = importance;
-        }
-
-        @Override
-        public void onUidImportance(int uid, int importance) {
-            if (uid == mExpectedUid) {
-                mCurrentImportance = importance;
-
-                /**
-                 * 1) If the process status changes to foreground, send a notification
-                 * for user consent.
-                 * 2) If the process status remains background, we decline logd access request.
-                 **/
-                if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
-                    String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd);
-                    sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid,
-                            mExpectedFd);
-                    mActivityManager.removeOnUidImportanceListener(this);
-
-                    synchronized (LogcatManagerService.this) {
-                        sUidImportanceListenerCount--;
-                    }
-                } else {
-                    try {
-                        getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd);
-                    } catch (RemoteException ex) {
-                        Slog.e(TAG, "Fails to call remote functions ", ex);
-                    }
-                }
-            }
-        }
-    }
-
-    private static String getClientInfo(int uid, int gid, int pid, int fd) {
-        return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID="
-            + Integer.toString(pid) + " FD=" + Integer.toString(fd);
     }
 
     private class LogdMonitor implements Runnable {
@@ -318,7 +74,9 @@
         }
 
         /**
-         * LogdMonitor generates a prompt for users.
+         * The current version grant the permission by default.
+         * And track the logd access.
+         * The next version will generate a prompt for users.
          * The users decide whether the logd access is allowed.
          */
         @Override
@@ -328,63 +86,10 @@
             }
 
             if (mStart) {
-
-                // TODO See the comments of ALLOWABLE_TESTING_PACKAGES.
-                if (isAllowableTestingPackage(mUid)) {
-                    try {
-                        getLogdService().approve(mUid, mGid, mPid, mFd);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    return;
-                }
-
-                // If the access request is coming from native apps, approve the logd access
-                // TODO: This is needed to make tooling to work. However,
-                // we intend to be stricter with respect to native processes in a follow-up CL
-                if (mUid < AID_APP_UID) {
-                    try {
-                        getLogdService().approve(mUid, mGid, mPid, mFd);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                    return;
-                }
-
-                final int procState = LocalServices.getService(ActivityManagerInternal.class)
-                        .getUidProcessState(mUid);
-                // If the process is foreground, send a notification for user consent
-                if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-                    String clientInfo = getClientInfo(mUid, mGid, mPid, mFd);
-                    sendNotification(0, clientInfo, mUid, mGid, mPid, mFd);
-                } else {
-                    /**
-                     * If the process is background, add a background process change listener and
-                     * monitor if the process status changes.
-                     * To avoid clients registering multiple listeners, we limit the number of
-                     * maximum listeners to MAX_UID_IMPORTANCE_COUNT_LISTENER.
-                     **/
-                    if (mActivityManager == null) {
-                        return;
-                    }
-
-                    synchronized (LogcatManagerService.this) {
-                        if (sUidImportanceListenerCount < MAX_UID_IMPORTANCE_COUNT_LISTENER) {
-                            // Trigger addOnUidImportanceListener when there is an update from
-                            // the importance of the process
-                            mActivityManager.addOnUidImportanceListener(new UidImportanceListener(
-                                    mUid, mGid, mPid, mFd,
-                                    RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE),
-                                    RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
-                            sUidImportanceListenerCount++;
-                        } else {
-                            try {
-                                getLogdService().decline(mUid, mGid, mPid, mFd);
-                            } catch (RemoteException e) {
-                                e.printStackTrace();
-                            }
-                        }
-                    }
+                try {
+                    mLogdService.approve(mUid, mGid, mPid, mFd);
+                } catch (RemoteException ex) {
+                    Slog.e(TAG, "Fails to call remote functions ", ex);
                 }
             }
         }
@@ -395,8 +100,6 @@
         mContext = context;
         mBinderService = new BinderService();
         mThreadExecutor = Executors.newCachedThreadPool();
-        mActivityManager = context.getSystemService(ActivityManager.class);
-        mNotificationManager = mContext.getSystemService(NotificationManager.class);
     }
 
     @Override
@@ -411,4 +114,5 @@
     private void addLogdService() {
         mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd"));
     }
+
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 204ebfc..b82e3a3 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -86,7 +86,7 @@
             mProviderInfo = null;
         } else {
             mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo)
-                    .setUniqueId(mUniqueId)
+                    .setUniqueId(mComponentName.getPackageName(), mUniqueId)
                     .setSystemRouteProvider(mIsSystemRouteProvider)
                     .build();
         }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 7f997df..b307266 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -64,9 +64,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
@@ -2200,9 +2202,21 @@
                 }
             }
 
+            // Build a composite RouteDiscoveryPreference that matches all of the routes
+            // that match one or more of the individual discovery preferences. It may also
+            // match additional routes. The composite RouteDiscoveryPreference can be used
+            // to query route providers once to obtain all of the routes of interest, which
+            // can be subsequently filtered for the individual discovery preferences.
+            Set<String> preferredFeatures = new HashSet<>();
+            boolean activeScan = false;
+            for (RouteDiscoveryPreference preference : discoveryPreferences) {
+                preferredFeatures.addAll(preference.getPreferredFeatures());
+                activeScan |= preference.shouldPerformActiveScan();
+            }
+            RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(
+                    List.copyOf(preferredFeatures), activeScan).build();
+
             synchronized (service.mLock) {
-                RouteDiscoveryPreference newPreference =
-                        new RouteDiscoveryPreference.Builder(discoveryPreferences).build();
                 if (newPreference.equals(mUserRecord.mCompositeDiscoveryPreference)) {
                     return;
                 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 76d06c8..bb22902 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -279,6 +279,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.IntConsumer;
 
@@ -1012,10 +1013,11 @@
             mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
 
             // listen for stats updated callbacks for interested network types.
+            final Executor executor = new HandlerExecutor(mHandler);
             mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_MOBILE).build(),
-                    0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
+                    0 /* thresholdBytes */, executor, mStatsCallback);
             mNetworkStats.registerUsageCallback(new NetworkTemplate.Builder(MATCH_WIFI).build(),
-                    0 /* thresholdBytes */, new HandlerExecutor(mHandler), mStatsCallback);
+                    0 /* thresholdBytes */, executor, mStatsCallback);
 
             // Listen for snooze from notifications
             mContext.registerReceiver(mSnoozeReceiver,
@@ -1239,6 +1241,12 @@
          * Used to determine if NetworkStatsService is ready.
          */
         public boolean isAnyCallbackReceived() {
+            // Warning : threading for this member is broken. It should only be read
+            // and written on the handler thread ; furthermore, the constructor
+            // is called on a different thread, so this stops working if the default
+            // value is not false or if this member ever goes back to false after
+            // being set to true.
+            // TODO : fix threading for this member.
             return mIsAnyCallbackReceived;
         }
     };
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dddec42..2717f0c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -83,6 +83,7 @@
 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
@@ -124,6 +125,7 @@
 
 import android.Manifest;
 import android.Manifest.permission;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -475,6 +477,14 @@
     @LoggingOnly
     private static final long RATE_LIMIT_TOASTS = 174840628L;
 
+    /**
+     * Whether listeners understand the more specific reason provided for notification
+     * cancellations from an assistant, rather than using the more general REASON_LISTENER_CANCEL.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
+    private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L;
+
     private IActivityManager mAm;
     private ActivityTaskManagerInternal mAtm;
     private ActivityManager mActivityManager;
@@ -851,7 +861,8 @@
         }
 
         if (summary.getSbn().getNotification().flags != oldFlags) {
-            mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground));
+            mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
+                    SystemClock.elapsedRealtime()));
         }
     }
 
@@ -1373,7 +1384,7 @@
                         // Force isAppForeground true here, because for sysui's purposes we
                         // want to adjust the flag behaviour.
                         mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
-                                r, true /* isAppForeground*/));
+                                r, true /* isAppForeground*/, SystemClock.elapsedRealtime()));
                     }
                 }
             }
@@ -1404,7 +1415,7 @@
                         r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
                         mHandler.post(
                                 new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
-                                        true /* isAppForeground */));
+                                        true /* isAppForeground */, SystemClock.elapsedRealtime()));
                     }
                 }
             }
@@ -2554,7 +2565,8 @@
                 if (r != null) {
                     final boolean isAppForeground =
                             mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
-                    mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
+                    mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+                            SystemClock.elapsedRealtime()));
                 }
             }
 
@@ -4385,6 +4397,13 @@
                 synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
 
+                    // Cancellation reason. If the token comes from assistant, label the
+                    // cancellation as coming from the assistant; default to LISTENER_CANCEL.
+                    int reason = REASON_LISTENER_CANCEL;
+                    if (mAssistants.isServiceTokenValidLocked(token)) {
+                        reason = REASON_ASSISTANT_CANCEL;
+                    }
+
                     if (keys != null) {
                         final int N = keys.length;
                         for (int i = 0; i < N; i++) {
@@ -4397,7 +4416,7 @@
                             }
                             cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                     r.getSbn().getPackageName(), r.getSbn().getTag(),
-                                    r.getSbn().getId(), userId);
+                                    r.getSbn().getId(), userId, reason);
                         }
                     } else {
                         cancelAllLocked(callingUid, callingPid, info.userid,
@@ -4491,12 +4510,13 @@
          */
         @GuardedBy("mNotificationLock")
         private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
-                int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
+                int callingUid, int callingPid, String pkg, String tag, int id, int userId,
+                int reason) {
             int mustNotHaveFlags = FLAG_ONGOING_EVENT;
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */,
                     mustNotHaveFlags,
                     true,
-                    userId, REASON_LISTENER_CANCEL, info);
+                    userId, reason, info);
         }
 
         /**
@@ -4638,13 +4658,17 @@
             try {
                 synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                    int cancelReason = REASON_LISTENER_CANCEL;
+                    if (mAssistants.isServiceTokenValidLocked(token)) {
+                        cancelReason = REASON_ASSISTANT_CANCEL;
+                    }
                     if (info.supportsProfiles()) {
                         Slog.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
                                 + "from " + info.component
                                 + " use cancelNotification(key) instead.");
                     } else {
                         cancelNotificationFromListenerLocked(info, callingUid, callingPid,
-                                pkg, tag, id, info.userid);
+                                pkg, tag, id, info.userid, cancelReason);
                     }
                 }
             } finally {
@@ -5750,7 +5774,8 @@
             final NotificationRecord removed = findNotificationByKeyLocked(summaries.remove(pkg));
             if (removed != null) {
                 boolean wasPosted = removeFromNotificationListsLocked(removed);
-                cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED, wasPosted, null);
+                cancelNotificationLocked(removed, false, REASON_UNAUTOBUNDLED, wasPosted, null,
+                        SystemClock.elapsedRealtime());
             }
         }
     }
@@ -6481,7 +6506,8 @@
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-        mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
+        mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+                SystemClock.elapsedRealtime()));
     }
 
     private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) {
@@ -6625,7 +6651,8 @@
                             r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
                             mHandler.post(
                                     new NotificationManagerService.EnqueueNotificationRunnable(
-                                            r.getUser().getIdentifier(), r, isAppForeground));
+                                            r.getUser().getIdentifier(), r, isAppForeground,
+                                            SystemClock.elapsedRealtime()));
                         }
                     }
                 }
@@ -6929,7 +6956,8 @@
                     NotificationRecordLogger.NotificationEvent.NOTIFICATION_SNOOZED, r);
             reportUserInteraction(r);
             boolean wasPosted = removeFromNotificationListsLocked(r);
-            cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
+            cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
+                    SystemClock.elapsedRealtime());
             updateLightsLocked();
             if (mSnoozeCriterionId != null) {
                 mAssistants.notifyAssistantSnoozedLocked(r, mSnoozeCriterionId);
@@ -6956,12 +6984,14 @@
         private final int mRank;
         private final int mCount;
         private final ManagedServiceInfo mListener;
+        private final long mCancellationElapsedTimeMs;
 
         CancelNotificationRunnable(final int callingUid, final int callingPid,
                 final String pkg, final String tag, final int id,
                 final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
                 final int userId, final int reason, int rank, int count,
-                final ManagedServiceInfo listener) {
+                final ManagedServiceInfo listener,
+                @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
             this.mCallingUid = callingUid;
             this.mCallingPid = callingPid;
             this.mPkg = pkg;
@@ -6975,6 +7005,7 @@
             this.mRank = rank;
             this.mCount = count;
             this.mListener = listener;
+            this.mCancellationElapsedTimeMs = cancellationElapsedTimeMs;
         }
 
         @Override
@@ -7037,9 +7068,11 @@
                     // Cancel the notification.
                     boolean wasPosted = removeFromNotificationListsLocked(r);
                     cancelNotificationLocked(
-                            r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
+                            r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName,
+                            mCancellationElapsedTimeMs);
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
-                            mSendDelete, childrenFlagChecker, mReason);
+                            mSendDelete, childrenFlagChecker, mReason,
+                            mCancellationElapsedTimeMs);
                     updateLightsLocked();
                     if (mShortcutHelper != null) {
                         mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
@@ -7101,11 +7134,14 @@
         private final NotificationRecord r;
         private final int userId;
         private final boolean isAppForeground;
+        private final long enqueueElapsedTimeMs;
 
-        EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {
+        EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground,
+                @ElapsedRealtimeLong long enqueueElapsedTimeMs) {
             this.userId = userId;
             this.r = r;
             this.isAppForeground = foreground;
+            this.enqueueElapsedTimeMs = enqueueElapsedTimeMs;
         }
 
         @Override
@@ -7178,10 +7214,11 @@
                 // tell the assistant service about the notification
                 if (mAssistants.isEnabled()) {
                     mAssistants.onNotificationEnqueuedLocked(r);
-                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
+                    mHandler.postDelayed(
+                            new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs),
                             DELAY_FOR_ASSISTANT_TIME);
                 } else {
-                    mHandler.post(new PostNotificationRunnable(r.getKey()));
+                    mHandler.post(new PostNotificationRunnable(r.getKey(), enqueueElapsedTimeMs));
                 }
             }
         }
@@ -7204,9 +7241,11 @@
 
     protected class PostNotificationRunnable implements Runnable {
         private final String key;
+        private final long postElapsedTimeMs;
 
-        PostNotificationRunnable(String key) {
+        PostNotificationRunnable(String key, @ElapsedRealtimeLong long postElapsedTimeMs) {
             this.key = key;
+            this.postElapsedTimeMs = postElapsedTimeMs;
         }
 
         @Override
@@ -7262,7 +7301,7 @@
                         mNotificationList.add(r);
                         mUsageStats.registerPostedByApp(r);
                         mUsageStatsManagerInternal.reportNotificationPosted(r.getSbn().getOpPkg(),
-                                r.getSbn().getUser(), SystemClock.elapsedRealtime());
+                                r.getSbn().getUser(), postElapsedTimeMs);
                         final boolean isInterruptive = isVisuallyInterruptive(null, r);
                         r.setInterruptive(isInterruptive);
                         r.setTextChanged(isInterruptive);
@@ -7271,7 +7310,7 @@
                         mNotificationList.set(index, r);
                         mUsageStats.registerUpdatedByApp(r, old);
                         mUsageStatsManagerInternal.reportNotificationUpdated(r.getSbn().getOpPkg(),
-                                r.getSbn().getUser(), SystemClock.elapsedRealtime());
+                                r.getSbn().getUser(), postElapsedTimeMs);
                         // Make sure we don't lose the foreground service state.
                         notification.flags |=
                                 old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
@@ -7576,7 +7615,7 @@
         // notification was a summary and its group key changed.
         if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
             cancelGroupChildrenLocked(old, callingUid, callingPid, null, false /* sendDelete */,
-                    childrenFlagChecker, REASON_APP_CANCEL);
+                    childrenFlagChecker, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
         }
     }
 
@@ -8662,14 +8701,17 @@
     @GuardedBy("mNotificationLock")
     private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete,
             @NotificationListenerService.NotificationCancelReason int reason,
-            boolean wasPosted, String listenerName) {
-        cancelNotificationLocked(r, sendDelete, reason, -1, -1, wasPosted, listenerName);
+            boolean wasPosted, String listenerName,
+            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
+        cancelNotificationLocked(r, sendDelete, reason, -1, -1, wasPosted, listenerName,
+                cancellationElapsedTimeMs);
     }
 
     @GuardedBy("mNotificationLock")
     private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete,
             @NotificationListenerService.NotificationCancelReason int reason,
-            int rank, int count, boolean wasPosted, String listenerName) {
+            int rank, int count, boolean wasPosted, String listenerName,
+            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         final String canceledKey = r.getKey();
 
         // Get pending intent used to create alarm, use FLAG_NO_CREATE if PendingIntent
@@ -8755,7 +8797,7 @@
             case REASON_APP_CANCEL_ALL:
                 mUsageStats.registerRemovedByApp(r);
                 mUsageStatsManagerInternal.reportNotificationRemoved(r.getSbn().getOpPkg(),
-                        r.getUser(), SystemClock.elapsedRealtime());
+                        r.getUser(), cancellationElapsedTimeMs);
                 break;
         }
 
@@ -8932,7 +8974,7 @@
      * and none of the {@code mustNotHaveFlags}.
      */
     void cancelNotification(final int callingUid, final int callingPid,
-            final String pkg, final String tag, final int id,
+            final String pkg, final String tag, int id,
             final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
             final int userId, final int reason, final ManagedServiceInfo listener) {
         cancelNotification(callingUid, callingPid, pkg, tag, id, mustHaveFlags, mustNotHaveFlags,
@@ -8954,7 +8996,7 @@
         // remove notification call ends up in not removing the notification.
         mHandler.scheduleCancelNotification(new CancelNotificationRunnable(callingUid, callingPid,
                 pkg, tag, id, mustHaveFlags, mustNotHaveFlags, sendDelete, userId, reason, rank,
-                count, listener));
+                count, listener, SystemClock.elapsedRealtime()));
     }
 
     /**
@@ -8988,6 +9030,7 @@
     void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
             int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
             ManagedServiceInfo listener) {
+        final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime();
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -9015,11 +9058,12 @@
                     cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
                             pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
                             false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
-                            listenerName, true /* wasPosted */);
+                            listenerName, true /* wasPosted */, cancellationElapsedTimeMs);
                     cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
                             callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
                             flagChecker, false /*includeCurrentProfiles*/, userId,
-                            false /*sendDelete*/, reason, listenerName, false /* wasPosted */);
+                            false /*sendDelete*/, reason, listenerName, false /* wasPosted */,
+                            cancellationElapsedTimeMs);
                     mSnoozeHelper.cancel(userId, pkg);
                 }
             }
@@ -9035,7 +9079,8 @@
     private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
             int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
             String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
-            boolean sendDelete, int reason, String listenerName, boolean wasPosted) {
+            boolean sendDelete, int reason, String listenerName, boolean wasPosted,
+            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         Set<String> childNotifications = null;
         for (int i = notificationList.size() - 1; i >= 0; --i) {
             NotificationRecord r = notificationList.get(i);
@@ -9069,7 +9114,8 @@
             notificationList.remove(i);
             mNotificationsByKey.remove(r.getKey());
             r.recordDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_NEUTRAL);
-            cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
+            cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName,
+                    cancellationElapsedTimeMs);
         }
         if (childNotifications != null) {
             final int M = notificationList.size();
@@ -9081,7 +9127,8 @@
                     notificationList.remove(i);
                     mNotificationsByKey.remove(r.getKey());
                     r.recordDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_NEUTRAL);
-                    cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
+                    cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName,
+                            cancellationElapsedTimeMs);
                 }
             }
             updateLightsLocked();
@@ -9127,6 +9174,7 @@
     @GuardedBy("mNotificationLock")
     void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
             ManagedServiceInfo listener, boolean includeCurrentProfiles) {
+        final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime();
         mHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -9151,11 +9199,11 @@
                     cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
                             null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
                             includeCurrentProfiles, userId, true /*sendDelete*/, reason,
-                            listenerName, true);
+                            listenerName, true, cancellationElapsedTimeMs);
                     cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
                             callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
                             flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
-                            reason, listenerName, false);
+                            reason, listenerName, false, cancellationElapsedTimeMs);
                     mSnoozeHelper.cancel(userId, includeCurrentProfiles);
                 }
             }
@@ -9165,7 +9213,8 @@
     // Warning: The caller is responsible for invoking updateLightsLocked().
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
-            String listenerName, boolean sendDelete, FlagChecker flagChecker, int reason) {
+            String listenerName, boolean sendDelete, FlagChecker flagChecker, int reason,
+            @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         Notification n = r.getNotification();
         if (!n.isGroupSummary()) {
             return;
@@ -9179,16 +9228,16 @@
         }
 
         cancelGroupChildrenByListLocked(mNotificationList, r, callingUid, callingPid, listenerName,
-                sendDelete, true, flagChecker, reason);
+                sendDelete, true, flagChecker, reason, cancellationElapsedTimeMs);
         cancelGroupChildrenByListLocked(mEnqueuedNotifications, r, callingUid, callingPid,
-                listenerName, sendDelete, false, flagChecker, reason);
+                listenerName, sendDelete, false, flagChecker, reason, cancellationElapsedTimeMs);
     }
 
     @GuardedBy("mNotificationLock")
     private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
             NotificationRecord parentNotification, int callingUid, int callingPid,
             String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker,
-            int reason) {
+            int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         final String pkg = parentNotification.getSbn().getPackageName();
         final int userId = parentNotification.getUserId();
         final int childReason = REASON_GROUP_SUMMARY_CANCELED;
@@ -9204,7 +9253,8 @@
                         childSbn.getTag(), userId, 0, 0, childReason, listenerName);
                 notificationList.remove(i);
                 mNotificationsByKey.remove(childR.getKey());
-                cancelNotificationLocked(childR, sendDelete, childReason, wasPosted, listenerName);
+                cancelNotificationLocked(childR, sendDelete, childReason, wasPosted, listenerName,
+                        cancellationElapsedTimeMs);
             }
         }
     }
@@ -11020,6 +11070,12 @@
                         && (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) {
                     reason = REASON_CHANNEL_BANNED;
                 }
+                // apps before T don't know about REASON_ASSISTANT, so replace it with the
+                // previously-used case, REASON_LISTENER_CANCEL
+                if (!CompatChanges.isChangeEnabled(NOTIFICATION_LOG_ASSISTANT_CANCEL, info.uid)
+                        && reason == REASON_ASSISTANT_CANCEL) {
+                    reason = REASON_LISTENER_CANCEL;
+                }
                 listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index f3dc2dd..9a89efa 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
 import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
@@ -180,7 +181,9 @@
                 + " shade.")
         NOTIFICATION_CANCEL_USER_SHADE(192),
         @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
-        NOTIFICATION_CANCEL_USER_LOCKSCREEN(193);
+        NOTIFICATION_CANCEL_USER_LOCKSCREEN(193),
+        @UiEvent(doc = "Notification was canceled due to an assistant adjustment update.")
+        NOTIFICATION_CANCEL_ASSISTANT(906);
 
         private final int mId;
         NotificationCancelledEvent(int id) {
@@ -206,6 +209,9 @@
                 if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) {
                     return NotificationCancelledEvent.values()[reason];
                 }
+                if (reason == REASON_ASSISTANT_CANCEL) {
+                    return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT;
+                }
                 if (NotificationManagerService.DBG) {
                     throw new IllegalArgumentException("Unexpected cancel reason " + reason);
                 }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index db860fe..3fb4ab1 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -222,8 +222,12 @@
                         deleteFlags | PackageManager.DELETE_CHATTY, info, true);
             }
             if (res && pkg != null) {
+                final boolean packageInstalledForSomeUsers;
+                synchronized (mPm.mLock) {
+                    packageInstalledForSomeUsers = mPm.mPackages.get(pkg.getPackageName()) != null;
+                }
                 mPm.mInstantAppRegistry.onPackageUninstalled(pkg, uninstalledPs,
-                        info.mRemovedUsers);
+                        info.mRemovedUsers, packageInstalledForSomeUsers);
             }
             synchronized (mPm.mLock) {
                 if (res) {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index ea6e458..39a2839 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -444,14 +444,14 @@
     }
 
     public void onPackageUninstalled(@NonNull AndroidPackage pkg, @NonNull PackageSetting ps,
-            @NonNull int[] userIds) {
+            @NonNull int[] userIds, boolean packageInstalledForSomeUsers) {
         if (ps == null) {
             return;
         }
 
         synchronized (mLock) {
             for (int userId : userIds) {
-                if (ps.getInstalled(userId)) {
+                if (packageInstalledForSomeUsers && ps.getInstalled(userId)) {
                     continue;
                 }
 
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ca87685..e657838 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -872,7 +872,7 @@
         PendingIntent injectCreatePendingIntent(int requestCode, @NonNull Intent[] intents,
                 int flags, Bundle options, String ownerPackage, int ownerUserId) {
             return mActivityManagerInternal.getPendingIntentActivityAsApp(requestCode, intents,
-                    flags, options, ownerPackage, ownerUserId);
+                    flags, null /* options */, ownerPackage, ownerUserId);
         }
 
         @Override
@@ -1237,7 +1237,7 @@
                 // calling identity to mirror the startActivityAsUser() call which does not validate
                 // the calling user
                 return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent,
-                        FLAG_IMMUTABLE, opts, user);
+                        FLAG_IMMUTABLE, null /* options */, user);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 44b9877..36a4b43 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -62,8 +62,6 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
@@ -104,7 +102,6 @@
 import android.os.FileBridge;
 import android.os.FileUtils;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
@@ -562,19 +559,6 @@
             return install();
         }
 
-        private void updateRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
-            synchronized (mLock) {
-                setRemoteStatusReceiver(remoteStatusReceiver);
-                if (isMultiPackage()) {
-                    final IntentSender childIntentSender = new ChildStatusIntentReceiver(
-                            mChildSessions.clone(), remoteStatusReceiver).getIntentSender();
-                    for (int i = mChildSessions.size() - 1; i >= 0; --i) {
-                        mChildSessions.valueAt(i).setRemoteStatusReceiver(childIntentSender);
-                    }
-                }
-            }
-        }
-
         @Override
         public boolean hasParentSessionId() {
             return PackageInstallerSession.this.hasParentSessionId();
@@ -1778,71 +1762,6 @@
         }
     }
 
-    private class ChildStatusIntentReceiver {
-        private final SparseArray<PackageInstallerSession> mChildSessionsRemaining;
-        private final IntentSender mStatusReceiver;
-        private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                statusUpdate(intent);
-            }
-        };
-
-        private ChildStatusIntentReceiver(SparseArray<PackageInstallerSession> remainingSessions,
-                IntentSender statusReceiver) {
-            this.mChildSessionsRemaining = remainingSessions;
-            this.mStatusReceiver = statusReceiver;
-        }
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-
-        public void statusUpdate(Intent intent) {
-            mHandler.post(() -> {
-                if (mChildSessionsRemaining.size() == 0) {
-                    // no children to deal with, ignore.
-                    return;
-                }
-                final boolean destroyed;
-                synchronized (mLock) {
-                    destroyed = mDestroyed;
-                }
-                if (destroyed) {
-                    // the parent has already been terminated, ignore.
-                    return;
-                }
-                final int sessionId = intent.getIntExtra(
-                        PackageInstaller.EXTRA_SESSION_ID, 0);
-                final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                        PackageInstaller.STATUS_FAILURE);
-                final int sessionIndex = mChildSessionsRemaining.indexOfKey(sessionId);
-                final String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-                if (PackageInstaller.STATUS_SUCCESS == status) {
-                    mChildSessionsRemaining.removeAt(sessionIndex);
-                    if (mChildSessionsRemaining.size() == 0) {
-                        destroyInternal();
-                        dispatchSessionFinished(INSTALL_SUCCEEDED,
-                                "Session installed", null);
-                    }
-                } else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) {
-                    try {
-                        mStatusReceiver.sendIntent(mContext, 0, intent, null, null);
-                    } catch (IntentSender.SendIntentException ignore) {
-                    }
-                } else { // failure, let's forward and clean up this session.
-                    intent.putExtra(PackageInstaller.EXTRA_SESSION_ID,
-                            PackageInstallerSession.this.sessionId);
-                    mChildSessionsRemaining.clear(); // we're done. Don't send any more.
-                    destroyInternal();
-                    dispatchSessionFinished(INSTALL_FAILED_INTERNAL_ERROR,
-                            "Child session " + sessionId + " failed: " + message, null);
-                }
-            });
-        }
-    }
-
     /**
      * Returns whether or not a package can be installed while Secure FRP is enabled.
      * <p>
@@ -2065,12 +1984,6 @@
         maybeFinishChildSessions(error, msg);
     }
 
-    private void onSessionInstallationFailure(int error, String detailedMessage) {
-        Slog.e(TAG, "Install of session " + sessionId + " failed: " + detailedMessage);
-        destroyInternal();
-        dispatchSessionFinished(error, detailedMessage, null);
-    }
-
     private void onSystemDataLoaderUnrecoverable() {
         final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
         final String packageName = getPackageName();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 2760578..1cf2dc5 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -735,6 +735,9 @@
         if (DEBUG || DEBUG_REBOOT) {
             Slog.d(TAG, "unloadUserLocked: user=" + userId);
         }
+        // Cancel any ongoing background tasks.
+        getUserShortcutsLocked(userId).cancelAllInFlightTasks();
+
         // Save all dirty information.
         saveDirtyInfo(false);
 
@@ -3736,6 +3739,7 @@
             synchronized (mLock) {
                 if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) {
                     mHandler.removeCallbacks(mSaveDirtyInfoRunner);
+                    forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks);
                     saveDirtyInfo(false);
                 }
                 mShutdown.set(true);
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 408f045..4bb5dcf 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -33,6 +33,7 @@
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.logging.MetricsLogger;
@@ -50,7 +51,9 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -138,6 +141,11 @@
     private String mLastAppScanOsFingerprint;
     private String mRestoreFromOsFingerprint;
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final ArrayList<AndroidFuture<AppSearchSession>> mInFlightSessions = new ArrayList<>();
+
     public ShortcutUser(ShortcutService service, int userId) {
         mService = service;
         mUserId = userId;
@@ -718,6 +726,10 @@
     AndroidFuture<AppSearchSession> getAppSearch(
             @NonNull final AppSearchManager.SearchContext searchContext) {
         final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
+        synchronized (mLock) {
+            mInFlightSessions.removeIf(CompletableFuture::isDone);
+            mInFlightSessions.add(future);
+        }
         if (mAppSearchManager == null) {
             future.completeExceptionally(new RuntimeException("app search manager is null"));
             return future;
@@ -743,4 +755,13 @@
         }
         return future;
     }
+
+    void cancelAllInFlightTasks() {
+        synchronized (mLock) {
+            for (AndroidFuture<AppSearchSession> session : mInFlightSessions) {
+                session.cancel(true);
+            }
+            mInFlightSessions.clear();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index ccbdfed..52a7bed 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -23,11 +23,8 @@
 import android.apex.ApexSessionParams;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.IntentSender;
 import android.content.pm.ApexStagedEvent;
 import android.content.pm.IStagedApexObserver;
 import android.content.pm.PackageInstaller;
@@ -36,7 +33,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.StagedApexInfo;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -78,8 +74,6 @@
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 
 /**
@@ -809,35 +803,6 @@
         BackgroundThread.getExecutor().execute(() -> logFailedApexSessionsIfNecessary());
     }
 
-    private static class LocalIntentReceiverSync {
-        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
-
-        private final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                    IIntentReceiver finishedReceiver, String requiredPermission,
-                    Bundle options) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-
-        public Intent getResult() {
-            try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
     private StagedSession getStagedSession(int sessionId) {
         StagedSession session;
         synchronized (mStagedSessions) {
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 698068d..71554ee 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -606,6 +606,10 @@
             int granted = PermissionManagerService.this.checkUidPermission(uid,
                     POST_NOTIFICATIONS);
             AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
+            if (pkg == null) {
+                Slog.e(LOG_TAG, "No package for uid " + uid);
+                return granted;
+            }
             if (granted != PackageManager.PERMISSION_GRANTED
                     && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
                 int flags = PermissionManagerService.this.getPermissionFlags(pkg.getPackageName(),
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7e36290..6e4651c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -635,6 +635,7 @@
     private static final int MSG_HIDE_BOOT_MESSAGE = 11;
     private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12;
     private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU = 15;
+    private static final int MSG_SCREENSHOT_CHORD = 16;
     private static final int MSG_ACCESSIBILITY_SHORTCUT = 17;
     private static final int MSG_BUGREPORT_TV = 18;
     private static final int MSG_ACCESSIBILITY_TV = 19;
@@ -710,6 +711,9 @@
                 case MSG_RINGER_TOGGLE_CHORD:
                     handleRingerChordGesture();
                     break;
+                case MSG_SCREENSHOT_CHORD:
+                    handleScreenShot(msg.arg1, msg.arg2);
+                    break;
             }
         }
     }
@@ -1465,11 +1469,10 @@
                 || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
     }
 
-    private void interceptScreenshotChord() {
-        mHandler.removeCallbacks(mScreenshotRunnable);
-        mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
-        mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);
-        mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
+    private void interceptScreenshotChord(int type, int source, long pressDelay) {
+        mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, type, source),
+                pressDelay);
     }
 
     private void interceptAccessibilityShortcutChord() {
@@ -1509,7 +1512,7 @@
     }
 
     private void cancelPendingScreenshotChordAction() {
-        mHandler.removeCallbacks(mScreenshotRunnable);
+        mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
     }
 
     private void cancelPendingAccessibilityShortcutAction() {
@@ -1530,26 +1533,11 @@
         }
     };
 
-    private class ScreenshotRunnable implements Runnable {
-        private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
-        private int mScreenshotSource = SCREENSHOT_KEY_OTHER;
-
-        public void setScreenshotType(int screenshotType) {
-            mScreenshotType = screenshotType;
-        }
-
-        public void setScreenshotSource(int screenshotSource) {
-            mScreenshotSource = screenshotSource;
-        }
-
-        @Override
-        public void run() {
-            mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource);
-        }
+    private void handleScreenShot(@WindowManager.ScreenshotType int type,
+            @WindowManager.ScreenshotSource int source) {
+        mDefaultDisplayPolicy.takeScreenshot(type, source);
     }
 
-    private final ScreenshotRunnable mScreenshotRunnable = new ScreenshotRunnable();
-
     @Override
     public void showGlobalActions() {
         mHandler.removeMessages(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS);
@@ -2124,7 +2112,8 @@
                         @Override
                         void execute() {
                             mPowerKeyHandled = true;
-                            interceptScreenshotChord();
+                            interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN,
+                                    SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
                         }
                         @Override
                         void cancel() {
@@ -2798,9 +2787,7 @@
                 if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
                     int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
                             : TAKE_SCREENSHOT_FULLSCREEN;
-                    mScreenshotRunnable.setScreenshotType(type);
-                    mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
-                    mHandler.post(mScreenshotRunnable);
+                    interceptScreenshotChord(type, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
                     return key_consumed;
                 }
                 break;
@@ -2835,13 +2822,6 @@
             case KeyEvent.KEYCODE_DEMO_APP_4:
                 Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
                 return key_consumed;
-            case KeyEvent.KEYCODE_SYSRQ:
-                if (down && repeatCount == 0) {
-                    mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
-                    mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
-                    mHandler.post(mScreenshotRunnable);
-                }
-                return key_consumed;
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
             case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
                 if (down) {
@@ -3158,6 +3138,12 @@
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_SYSRQ:
+                if (down && repeatCount == 0) {
+                    interceptScreenshotChord(
+                            TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+                }
+                return true;
         }
 
         return false;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 3857072..e523153 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -521,6 +521,9 @@
     // The screen off timeout setting value in milliseconds.
     private long mScreenOffTimeoutSetting;
 
+    // The screen off timeout setting value in milliseconds to apply while device is docked.
+    private long mScreenOffTimeoutDockedSetting;
+
     // Default for attentive warning duration.
     private long mAttentiveWarningDurationConfig;
 
@@ -1272,6 +1275,9 @@
         resolver.registerContentObserver(Settings.System.getUriFor(
                 Settings.System.SCREEN_OFF_TIMEOUT),
                 false, mSettingsObserver, UserHandle.USER_ALL);
+        resolver.registerContentObserver(Settings.System.getUriFor(
+                Settings.System.SCREEN_OFF_TIMEOUT_DOCKED),
+                false, mSettingsObserver, UserHandle.USER_ALL);
         resolver.registerContentObserver(Settings.Secure.getUriFor(
                 Settings.Secure.SLEEP_TIMEOUT),
                 false, mSettingsObserver, UserHandle.USER_ALL);
@@ -1394,6 +1400,9 @@
         mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver,
                 Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT,
                 UserHandle.USER_CURRENT);
+        mScreenOffTimeoutDockedSetting = Settings.System.getLongForUser(resolver,
+                Settings.System.SCREEN_OFF_TIMEOUT_DOCKED, mScreenOffTimeoutSetting,
+                UserHandle.USER_CURRENT);
         mSleepTimeoutSetting = Settings.Secure.getIntForUser(resolver,
                 Settings.Secure.SLEEP_TIMEOUT, DEFAULT_SLEEP_TIMEOUT,
                 UserHandle.USER_CURRENT);
@@ -2946,7 +2955,9 @@
 
     @GuardedBy("mLock")
     private long getScreenOffTimeoutLocked(long sleepTimeout, long attentiveTimeout) {
-        long timeout = mScreenOffTimeoutSetting;
+        long timeout = mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED
+                ? mScreenOffTimeoutSetting
+                : mScreenOffTimeoutDockedSetting;
         if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
             timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin);
         }
@@ -4974,7 +4985,8 @@
         }
     }
 
-    private final class DockReceiver extends BroadcastReceiver {
+    @VisibleForTesting
+    final class DockReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index 21d4cbb..f4b335e 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -365,6 +365,20 @@
         }
 
         @Override
+        public void onSegmentResults(Bundle results) throws RemoteException {
+            mRemoteListener.onSegmentResults(results);
+        }
+
+        @Override
+        public void onEndOfSegmentedSession() throws RemoteException {
+            if (DEBUG) {
+                Slog.i(TAG, "#onEndOfSegmentedSession invoked for a recognition session");
+            }
+            mOnSessionComplete.run();
+            mRemoteListener.onEndOfSegmentedSession();
+        }
+
+        @Override
         public void onEvent(int eventType, Bundle params) throws RemoteException {
             mRemoteListener.onEvent(eventType, params);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ff96aeb..ec4c58f 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1135,13 +1135,13 @@
     }
 
     @Override
-    public void setDisablePreviewScreenshots(IBinder token, boolean disable) {
+    public void setRecentsScreenshotEnabled(IBinder token, boolean enabled) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
                 final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
                 if (r != null) {
-                    r.setDisablePreviewScreenshots(disable);
+                    r.setRecentsScreenshotEnabled(enabled);
                 }
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index aca84a9..9532a5c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -766,7 +766,7 @@
     // Last visibility state we reported to the app token.
     boolean reportedVisible;
 
-    boolean mDisablePreviewScreenshots;
+    boolean mEnablePreviewScreenshots = true;
 
     // Information about an application starting window if displayed.
     // Note: these are de-referenced before the starting window animates away.
@@ -5116,22 +5116,22 @@
     }
 
     /**
-     * See {@link Activity#setDisablePreviewScreenshots}.
+     * See {@link Activity#setRecentsScreenshotEnabled}.
      */
-    void setDisablePreviewScreenshots(boolean disable) {
-        mDisablePreviewScreenshots = disable;
+    void setRecentsScreenshotEnabled(boolean enabled) {
+        mEnablePreviewScreenshots = enabled;
     }
 
     /**
      * Retrieves whether we'd like to generate a snapshot that's based solely on the theme. This is
-     * the case when preview screenshots are disabled {@link #setDisablePreviewScreenshots} or when
+     * the case when preview screenshots are disabled {@link #setRecentsScreenshotEnabled} or when
      * we can't take a snapshot for other reasons, for example, if we have a secure window.
      *
      * @return True if we need to generate an app theme snapshot, false if we'd like to take a real
      *         screenshot.
      */
     boolean shouldUseAppThemeSnapshot() {
-        return mDisablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked,
+        return !mEnablePreviewScreenshots || forAllWindows(WindowState::isSecureLocked,
                 true /* topToBottom */);
     }
 
@@ -5215,6 +5215,10 @@
                 mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
                 break;
             case DESTROYED:
+                if (app != null && (mVisible || mVisibleRequested)) {
+                    // The app may be died while visible (no PAUSED state).
+                    mAtmService.updateBatteryStats(this, false);
+                }
                 mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED);
                 // Fall through.
             case DESTROYING:
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 5c8502b..c55af9b 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -235,7 +235,7 @@
      */
     void keyguardGoingAway(int displayId, int flags) {
         final KeyguardDisplayState state = getDisplayState(displayId);
-        if (!state.mKeyguardShowing) {
+        if (!state.mKeyguardShowing || state.mKeyguardGoingAway) {
             return;
         }
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "keyguardGoingAway");
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ee03d02..76a7981 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -981,29 +981,6 @@
             mWmService.checkDrawnWindowsLocked();
         }
 
-        final int N = mWmService.mPendingRemove.size();
-        if (N > 0) {
-            if (mWmService.mPendingRemoveTmp.length < N) {
-                mWmService.mPendingRemoveTmp = new WindowState[N + 10];
-            }
-            mWmService.mPendingRemove.toArray(mWmService.mPendingRemoveTmp);
-            mWmService.mPendingRemove.clear();
-            ArrayList<DisplayContent> displayList = new ArrayList();
-            for (i = 0; i < N; i++) {
-                final WindowState w = mWmService.mPendingRemoveTmp[i];
-                w.removeImmediately();
-                final DisplayContent displayContent = w.getDisplayContent();
-                if (displayContent != null && !displayList.contains(displayContent)) {
-                    displayList.add(displayContent);
-                }
-            }
-
-            for (int j = displayList.size() - 1; j >= 0; --j) {
-                final DisplayContent dc = displayList.get(j);
-                dc.assignWindowLayers(true /*setLayoutNeeded*/);
-            }
-        }
-
         forAllDisplays(dc -> {
             dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             dc.updateSystemGestureExclusion();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index bf11ebc..a9add59 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -196,15 +196,21 @@
         snapshotTasks(tasks, false /* allowSnapshotHome */);
     }
 
-    void recordTaskSnapshot(Task task, boolean allowSnapshotHome) {
+    /**
+     * This is different than {@link #recordTaskSnapshot(Task, boolean)} because it doesn't store
+     * the snapshot to the cache and returns the TaskSnapshot immediately.
+     *
+     * This is only used for testing so the snapshot content can be verified.
+     */
+    @VisibleForTesting
+    TaskSnapshot captureTaskSnapshot(Task task, boolean snapshotHome) {
         final TaskSnapshot snapshot;
-        final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
         if (snapshotHome) {
             snapshot = snapshotTask(task);
         } else {
             switch (getSnapshotMode(task)) {
                 case SNAPSHOT_MODE_NONE:
-                    return;
+                    return null;
                 case SNAPSHOT_MODE_APP_THEME:
                     snapshot = drawAppThemeSnapshot(task);
                     break;
@@ -216,19 +222,27 @@
                     break;
             }
         }
-        if (snapshot != null) {
-            final HardwareBuffer buffer = snapshot.getHardwareBuffer();
-            if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
-                buffer.close();
-                Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
-                        + buffer.getHeight());
-            } else {
-                mCache.putSnapshot(task, snapshot);
-                // Don't persist or notify the change for the temporal snapshot.
-                if (!snapshotHome) {
-                    mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
-                    task.onSnapshotChanged(snapshot);
-                }
+        return snapshot;
+    }
+
+    void recordTaskSnapshot(Task task, boolean allowSnapshotHome) {
+        final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
+        final TaskSnapshot snapshot = captureTaskSnapshot(task, snapshotHome);
+        if (snapshot == null) {
+            return;
+        }
+
+        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+        if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+            buffer.close();
+            Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
+                    + buffer.getHeight());
+        } else {
+            mCache.putSnapshot(task, snapshot);
+            // Don't persist or notify the change for the temporal snapshot.
+            if (!snapshotHome) {
+                mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+                task.onSnapshotChanged(snapshot);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c9c3f1d..cacff5a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -116,6 +116,7 @@
 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
 import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -224,7 +225,6 @@
 import android.util.EventLog;
 import android.util.MergedConfiguration;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
@@ -584,20 +584,6 @@
     final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
 
     /**
-     * Windows whose animations have ended and now must be removed.
-     */
-    final ArrayList<WindowState> mPendingRemove = new ArrayList<>();
-
-    /**
-     * Used when processing mPendingRemove to avoid working on the original array.
-     */
-    WindowState[] mPendingRemoveTmp = new WindowState[20];
-
-    // TODO: use WindowProcessController once go/wm-unified is done.
-    /** Mapping of process pids to configurations */
-    final SparseArray<Configuration> mProcessConfigurations = new SparseArray<>();
-
-    /**
      * Mapping of displayId to {@link DisplayImePolicy}.
      * Note that this can be accessed without holding the lock.
      */
@@ -2039,7 +2025,6 @@
             dc.mWinRemovedSinceNullFocus.add(win);
         }
         mEmbeddedWindowController.onWindowRemoved(win);
-        mPendingRemove.remove(win);
         mResizingWindows.remove(win);
         updateNonSystemOverlayWindowsVisibilityIfNeeded(win, false /* surfaceShown */);
         mWindowsChanged = true;
@@ -6411,23 +6396,6 @@
                 }
             }
         }
-        if (mPendingRemove.size() > 0) {
-            pw.println();
-            pw.println("  Remove pending for:");
-            for (int i=mPendingRemove.size()-1; i>=0; i--) {
-                WindowState w = mPendingRemove.get(i);
-                if (windows == null || windows.contains(w)) {
-                    pw.print("  Remove #"); pw.print(i); pw.print(' ');
-                            pw.print(w);
-                    if (dumpAll) {
-                        pw.println(":");
-                        w.dump(pw, "    ", true);
-                    } else {
-                        pw.println();
-                    }
-                }
-            }
-        }
         if (mForceRemoves != null && mForceRemoves.size() > 0) {
             pw.println();
             pw.println("  Windows force removing:");
@@ -8941,4 +8909,27 @@
 
         mTaskFpsCallbackController.unregisterCallback(listener);
     }
+
+    @Override
+    public Bitmap snapshotTaskForRecents(int taskId) {
+        if (!checkCallingPermission(READ_FRAME_BUFFER, "snapshotTaskForRecents()")) {
+            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+        }
+
+        TaskSnapshot taskSnapshot;
+        synchronized (mGlobalLock) {
+            Task task = mRoot.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
+            if (task == null) {
+                throw new IllegalArgumentException(
+                        "Failed to find matching task for taskId=" + taskId);
+            }
+            taskSnapshot = mTaskSnapshotController.captureTaskSnapshot(task, false);
+        }
+
+        if (taskSnapshot == null || taskSnapshot.getHardwareBuffer() == null) {
+            return null;
+        }
+        return Bitmap.wrapHardwareBuffer(taskSnapshot.getHardwareBuffer(),
+                taskSnapshot.getColorSpace());
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0d72e9a..11a6141 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4910,15 +4910,20 @@
             if (hasSurface) {
                 mWmService.mDestroySurface.add(this);
             }
-            if (mRemoveOnExit) {
-                mWmService.mPendingRemove.add(this);
-                mRemoveOnExit = false;
-            }
         }
         mAnimatingExit = false;
         getDisplayContent().mWallpaperController.hideWallpapers(this);
     }
 
+    @Override
+    boolean handleCompleteDeferredRemoval() {
+        if (mRemoveOnExit && !isSelfAnimating(0 /* flags */, EXIT_ANIMATING_TYPES)) {
+            mRemoveOnExit = false;
+            removeImmediately();
+        }
+        return super.handleCompleteDeferredRemoval();
+    }
+
     boolean clearAnimatingFlags() {
         boolean didSomething = false;
         // We don't want to clear it out for windows that get replaced, because the
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 54cb79a..a9c6b8d 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -28,6 +28,7 @@
 #include <android/hardware/gnss/2.1/IGnssAntennaInfo.h>
 #include <android/hardware/gnss/2.1/IGnssMeasurement.h>
 #include <android/hardware/gnss/BnGnss.h>
+#include <android/hardware/gnss/BnGnssAntennaInfo.h>
 #include <android/hardware/gnss/BnGnssCallback.h>
 #include <android/hardware/gnss/BnGnssDebug.h>
 #include <android/hardware/gnss/BnGnssGeofence.h>
@@ -35,8 +36,6 @@
 #include <android/hardware/gnss/BnGnssMeasurementCallback.h>
 #include <android/hardware/gnss/BnGnssPowerIndicationCallback.h>
 #include <android/hardware/gnss/BnGnssPsdsCallback.h>
-#include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
-#include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h>
 #include <binder/IServiceManager.h>
 #include <nativehelper/JNIHelp.h>
 #include <pthread.h>
@@ -51,6 +50,7 @@
 #include "android_runtime/Log.h"
 #include "gnss/AGnss.h"
 #include "gnss/AGnssRil.h"
+#include "gnss/GnssAntennaInfo.h"
 #include "gnss/GnssAntennaInfoCallback.h"
 #include "gnss/GnssBatching.h"
 #include "gnss/GnssConfiguration.h"
@@ -59,6 +59,7 @@
 #include "gnss/GnssMeasurement.h"
 #include "gnss/GnssNavigationMessage.h"
 #include "gnss/GnssVisibilityControl.h"
+#include "gnss/MeasurementCorrections.h"
 #include "gnss/Utils.h"
 #include "hardware_legacy/power.h"
 #include "jni.h"
@@ -80,31 +81,6 @@
 static jmethodID method_requestUtcTime;
 static jmethodID method_reportGnssServiceDied;
 static jmethodID method_reportGnssPowerStats;
-static jmethodID method_setSubHalMeasurementCorrectionsCapabilities;
-static jmethodID method_correctionsGetLatitudeDegrees;
-static jmethodID method_correctionsGetLongitudeDegrees;
-static jmethodID method_correctionsGetAltitudeMeters;
-static jmethodID method_correctionsGetHorPosUncMeters;
-static jmethodID method_correctionsGetVerPosUncMeters;
-static jmethodID method_correctionsGetToaGpsNanosecondsOfWeek;
-static jmethodID method_correctionsGetSingleSatCorrectionList;
-static jmethodID method_correctionsHasEnvironmentBearing;
-static jmethodID method_correctionsGetEnvironmentBearingDegrees;
-static jmethodID method_correctionsGetEnvironmentBearingUncertaintyDegrees;
-static jmethodID method_listSize;
-static jmethodID method_correctionListGet;
-static jmethodID method_correctionSatFlags;
-static jmethodID method_correctionSatConstType;
-static jmethodID method_correctionSatId;
-static jmethodID method_correctionSatCarrierFreq;
-static jmethodID method_correctionSatIsLosProb;
-static jmethodID method_correctionSatEpl;
-static jmethodID method_correctionSatEplUnc;
-static jmethodID method_correctionSatRefPlane;
-static jmethodID method_correctionPlaneLatDeg;
-static jmethodID method_correctionPlaneLngDeg;
-static jmethodID method_correctionPlaneAltDeg;
-static jmethodID method_correctionPlaneAzimDeg;
 static jmethodID method_reportNfwNotification;
 static jmethodID method_isInEmergencySession;
 static jmethodID method_gnssPowerStatsCtor;
@@ -133,15 +109,6 @@
 using android::hardware::gnss::V1_0::IGnssXtraCallback;
 using android::hardware::gnss::V2_0::ElapsedRealtimeFlags;
 
-using MeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::MeasurementCorrections;
-using MeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::MeasurementCorrections;
-
-using SingleSatCorrection_V1_0 =
-        android::hardware::gnss::measurement_corrections::V1_0::SingleSatCorrection;
-using SingleSatCorrection_V1_1 =
-        android::hardware::gnss::measurement_corrections::V1_1::SingleSatCorrection;
-using android::hardware::gnss::measurement_corrections::V1_0::ReflectingPlane;
-
 using android::hidl::base::V1_0::IBase;
 
 using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
@@ -155,12 +122,6 @@
 using IGnssCallback_V1_0 = android::hardware::gnss::V1_0::IGnssCallback;
 using IGnssCallback_V2_0 = android::hardware::gnss::V2_0::IGnssCallback;
 using IGnssCallback_V2_1 = android::hardware::gnss::V2_1::IGnssCallback;
-using IGnssAntennaInfo = android::hardware::gnss::V2_1::IGnssAntennaInfo;
-
-using IMeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
-using IMeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::IMeasurementCorrections;
-using android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrectionsCallback;
-using android::hardware::gnss::measurement_corrections::V1_0::GnssSingleSatCorrectionFlags;
 
 using android::hardware::gnss::BlocklistedSource;
 using android::hardware::gnss::GnssConstellationType;
@@ -179,6 +140,7 @@
 using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback;
 using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration;
 using GnssLocationAidl = android::hardware::gnss::GnssLocation;
+using IGnssAntennaInfoAidl = android::hardware::gnss::IGnssAntennaInfo;
 
 struct GnssDeathRecipient : virtual public hidl_death_recipient
 {
@@ -205,9 +167,6 @@
 sp<IGnssXtra> gnssXtraIface = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr;
-sp<IMeasurementCorrections_V1_0> gnssCorrectionsIface_V1_0 = nullptr;
-sp<IMeasurementCorrections_V1_1> gnssCorrectionsIface_V1_1 = nullptr;
-sp<IGnssAntennaInfo> gnssAntennaInfoIface = nullptr;
 
 std::unique_ptr<GnssConfigurationInterface> gnssConfigurationIface = nullptr;
 std::unique_ptr<android::gnss::GnssMeasurementInterface> gnssMeasurementIface = nullptr;
@@ -218,6 +177,9 @@
 std::unique_ptr<android::gnss::GnssDebugInterface> gnssDebugIface = nullptr;
 std::unique_ptr<android::gnss::AGnssRilInterface> agnssRilIface = nullptr;
 std::unique_ptr<android::gnss::GnssVisibilityControlInterface> gnssVisibilityControlIface = nullptr;
+std::unique_ptr<android::gnss::GnssAntennaInfoInterface> gnssAntennaInfoIface = nullptr;
+std::unique_ptr<android::gnss::MeasurementCorrectionsInterface> gnssMeasurementCorrectionsIface =
+        nullptr;
 
 #define WAKE_LOCK_NAME  "GPS"
 
@@ -823,23 +785,6 @@
 }
 
 /*
- * MeasurementCorrectionsCallback implements callback methods of interface
- * IMeasurementCorrectionsCallback.hal.
- */
-struct MeasurementCorrectionsCallback : public IMeasurementCorrectionsCallback {
-    Return<void> setCapabilitiesCb(uint32_t capabilities) override;
-};
-
-Return<void> MeasurementCorrectionsCallback::setCapabilitiesCb(uint32_t capabilities) {
-    ALOGD("%s: %du\n", __func__, capabilities);
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setSubHalMeasurementCorrectionsCapabilities,
-                        capabilities);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-/*
  * GnssNiCallback implements callback methods required by the IGnssNi interface.
  */
 struct GnssNiCallback : public IGnssNiCallback {
@@ -942,63 +887,9 @@
                              "(Lcom/android/server/location/gnss/GnssPowerStats;)V");
     method_isInEmergencySession = env->GetMethodID(clazz, "isInEmergencySession", "()Z");
 
-    method_setSubHalMeasurementCorrectionsCapabilities = env->GetMethodID(clazz,
-            "setSubHalMeasurementCorrectionsCapabilities", "(I)V");
     method_setSubHalPowerIndicationCapabilities =
             env->GetMethodID(clazz, "setSubHalPowerIndicationCapabilities", "(I)V");
 
-    jclass measCorrClass = env->FindClass("android/location/GnssMeasurementCorrections");
-    method_correctionsGetLatitudeDegrees = env->GetMethodID(
-            measCorrClass,"getLatitudeDegrees", "()D");
-    method_correctionsGetLongitudeDegrees = env->GetMethodID(
-            measCorrClass, "getLongitudeDegrees", "()D");
-    method_correctionsGetAltitudeMeters = env->GetMethodID(
-            measCorrClass, "getAltitudeMeters", "()D");
-    method_correctionsGetHorPosUncMeters = env->GetMethodID(
-            measCorrClass, "getHorizontalPositionUncertaintyMeters", "()D");
-    method_correctionsGetVerPosUncMeters = env->GetMethodID(
-            measCorrClass, "getVerticalPositionUncertaintyMeters", "()D");
-    method_correctionsGetToaGpsNanosecondsOfWeek = env->GetMethodID(
-            measCorrClass, "getToaGpsNanosecondsOfWeek", "()J");
-
-    method_correctionsGetSingleSatCorrectionList = env->GetMethodID(
-            measCorrClass, "getSingleSatelliteCorrectionList", "()Ljava/util/List;");
-
-    method_correctionsHasEnvironmentBearing = env->GetMethodID(
-            measCorrClass, "hasEnvironmentBearing", "()Z");
-    method_correctionsGetEnvironmentBearingDegrees = env->GetMethodID(
-            measCorrClass, "getEnvironmentBearingDegrees", "()F");
-    method_correctionsGetEnvironmentBearingUncertaintyDegrees = env->GetMethodID(
-            measCorrClass, "getEnvironmentBearingUncertaintyDegrees", "()F");
-
-    jclass corrListClass = env->FindClass("java/util/List");
-    method_listSize = env->GetMethodID(corrListClass, "size", "()I");
-    method_correctionListGet = env->GetMethodID(corrListClass, "get", "(I)Ljava/lang/Object;");
-
-    jclass singleSatCorrClass = env->FindClass("android/location/GnssSingleSatCorrection");
-    method_correctionSatFlags = env->GetMethodID(
-            singleSatCorrClass, "getSingleSatelliteCorrectionFlags", "()I");
-    method_correctionSatConstType = env->GetMethodID(
-            singleSatCorrClass, "getConstellationType", "()I");
-    method_correctionSatId= env->GetMethodID(
-            singleSatCorrClass, "getSatelliteId", "()I");
-    method_correctionSatCarrierFreq = env->GetMethodID(
-            singleSatCorrClass, "getCarrierFrequencyHz", "()F");
-    method_correctionSatIsLosProb = env->GetMethodID(
-            singleSatCorrClass,"getProbabilityLineOfSight", "()F");
-    method_correctionSatEpl = env->GetMethodID(
-            singleSatCorrClass, "getExcessPathLengthMeters", "()F");
-    method_correctionSatEplUnc = env->GetMethodID(
-            singleSatCorrClass, "getExcessPathLengthUncertaintyMeters", "()F");
-    method_correctionSatRefPlane = env->GetMethodID(
-            singleSatCorrClass, "getReflectingPlane", "()Landroid/location/GnssReflectingPlane;");
-
-    jclass refPlaneClass = env->FindClass("android/location/GnssReflectingPlane");
-    method_correctionPlaneLatDeg = env->GetMethodID(refPlaneClass, "getLatitudeDegrees", "()D");
-    method_correctionPlaneLngDeg = env->GetMethodID(refPlaneClass, "getLongitudeDegrees", "()D");
-    method_correctionPlaneAltDeg = env->GetMethodID(refPlaneClass, "getAltitudeMeters", "()D");
-    method_correctionPlaneAzimDeg = env->GetMethodID(refPlaneClass, "getAzimuthDegrees", "()D");
-
     jclass gnssPowerStatsClass = env->FindClass("com/android/server/location/gnss/GnssPowerStats");
     class_gnssPowerStats = (jclass)env->NewGlobalRef(gnssPowerStatsClass);
     method_gnssPowerStatsCtor = env->GetMethodID(class_gnssPowerStats, "<init>", "(IJDDDDDD[D)V");
@@ -1010,6 +901,8 @@
     gnss::GnssMeasurement_class_init_once(env, clazz);
     gnss::GnssNavigationMessage_class_init_once(env, clazz);
     gnss::GnssVisibilityControl_class_init_once(env, clazz);
+    gnss::MeasurementCorrections_class_init_once(env, clazz);
+    gnss::MeasurementCorrectionsCallback_class_init_once(env, clazz);
     gnss::AGnss_class_init_once(env, clazz);
     gnss::AGnssRil_class_init_once(env, clazz);
     gnss::Utils_class_init_once(env);
@@ -1163,29 +1056,49 @@
         }
     }
 
-    if (gnssHal_V2_1 != nullptr) {
-        auto gnssAntennaInfo = gnssHal_V2_1->getExtensionGnssAntennaInfo();
-        if (!gnssAntennaInfo.isOk()) {
-            ALOGD("Unable to get a handle to GnssAntennaInfo");
-        } else {
-            gnssAntennaInfoIface = gnssAntennaInfo;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IGnssAntennaInfoAidl> gnssAntennaInfoAidl;
+        auto status = gnssHalAidl->getExtensionGnssAntennaInfo(&gnssAntennaInfoAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssAntennaInfo interface.")) {
+            gnssAntennaInfoIface = std::make_unique<gnss::GnssAntennaInfoAidl>(gnssAntennaInfoAidl);
+        }
+    } else if (gnssHal_V2_1 != nullptr) {
+        auto gnssAntennaInfo_V2_1 = gnssHal_V2_1->getExtensionGnssAntennaInfo();
+        if (checkHidlReturn(gnssAntennaInfo_V2_1,
+                            "Unable to get a handle to GnssAntennaInfo_V2_1")) {
+            gnssAntennaInfoIface =
+                    std::make_unique<gnss::GnssAntennaInfo_V2_1>(gnssAntennaInfo_V2_1);
         }
     }
 
-    if (gnssHal_V2_1 != nullptr) {
-        auto gnssCorrections = gnssHal_V2_1->getExtensionMeasurementCorrections_1_1();
-        if (!gnssCorrections.isOk()) {
-            ALOGD("Unable to get a handle to GnssMeasurementCorrections 1.1 interface");
-        } else {
-            gnssCorrectionsIface_V1_1 = gnssCorrections;
-            gnssCorrectionsIface_V1_0 = gnssCorrectionsIface_V1_1;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<android::hardware::gnss::measurement_corrections::IMeasurementCorrectionsInterface>
+                gnssMeasurementCorrectionsAidl;
+        auto status =
+                gnssHalAidl->getExtensionMeasurementCorrections(&gnssMeasurementCorrectionsAidl);
+        if (checkAidlStatus(status,
+                            "Unable to get a handle to GnssVisibilityControl AIDL interface.")) {
+            gnssMeasurementCorrectionsIface =
+                    std::make_unique<gnss::MeasurementCorrectionsIface_Aidl>(
+                            gnssMeasurementCorrectionsAidl);
         }
-    } else if (gnssHal_V2_0 != nullptr) {
+    }
+    if (gnssHal_V2_1 != nullptr && gnssMeasurementCorrectionsIface == nullptr) {
+        auto gnssCorrections = gnssHal_V2_1->getExtensionMeasurementCorrections_1_1();
+        if (checkHidlReturn(gnssCorrections,
+                            "Unable to get a handle to GnssMeasurementCorrections HIDL "
+                            "interface")) {
+            gnssMeasurementCorrectionsIface =
+                    std::make_unique<gnss::MeasurementCorrectionsIface_V1_1>(gnssCorrections);
+        }
+    }
+    if (gnssHal_V2_0 != nullptr && gnssMeasurementCorrectionsIface == nullptr) {
         auto gnssCorrections = gnssHal_V2_0->getExtensionMeasurementCorrections();
-        if (!gnssCorrections.isOk()) {
-            ALOGD("Unable to get a handle to GnssMeasurementCorrections interface");
-        } else {
-            gnssCorrectionsIface_V1_0 = gnssCorrections;
+        if (checkHidlReturn(gnssCorrections,
+                            "Unable to get a handle to GnssMeasurementCorrections HIDL "
+                            "interface")) {
+            gnssMeasurementCorrectionsIface =
+                    std::make_unique<gnss::MeasurementCorrectionsIface_V1_0>(gnssCorrections);
         }
     }
 
@@ -1439,19 +1352,11 @@
         ALOGI("Unable to initialize IGnssVisibilityControl interface.");
     }
 
-    // Set IMeasurementCorrections.hal callback.
-    if (gnssCorrectionsIface_V1_1 != nullptr) {
-        sp<IMeasurementCorrectionsCallback> gnssCorrectionsIfaceCbIface =
-                new MeasurementCorrectionsCallback();
-        auto result = gnssCorrectionsIface_V1_1->setCallback(gnssCorrectionsIfaceCbIface);
-        checkHidlReturn(result, "IMeasurementCorrections 1.1 setCallback() failed.");
-    } else if (gnssCorrectionsIface_V1_0 != nullptr) {
-        sp<IMeasurementCorrectionsCallback> gnssCorrectionsIfaceCbIface =
-                new MeasurementCorrectionsCallback();
-        auto result = gnssCorrectionsIface_V1_0->setCallback(gnssCorrectionsIfaceCbIface);
-        checkHidlReturn(result, "IMeasurementCorrections 1.0 setCallback() failed.");
-    } else {
-        ALOGI("Unable to find IMeasurementCorrections.");
+    // Set IMeasurementCorrection callback.
+    if (gnssMeasurementCorrectionsIface == nullptr ||
+        !gnssMeasurementCorrectionsIface->setCallback(
+                std::make_unique<gnss::MeasurementCorrectionsCallback>())) {
+        ALOGI("Unable to initialize IGnssMeasurementCorrections interface.");
     }
 
     // Set IGnssPowerIndication.hal callback.
@@ -1933,25 +1838,7 @@
         ALOGE("%s: IGnssAntennaInfo interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    sp<gnss::GnssAntennaInfoCallback> cbIface = new gnss::GnssAntennaInfoCallback(mCallbacksObj);
-
-    auto result = gnssAntennaInfoIface->setCallback(cbIface);
-
-    if (!checkHidlReturn(result, "IGnssAntennaInfo setCallback() failed.")) {
-        return JNI_FALSE;
-    }
-
-    IGnssAntennaInfo::GnssAntennaInfoStatus initRet = result;
-    if (initRet != IGnssAntennaInfo::GnssAntennaInfoStatus::SUCCESS) {
-        ALOGE("An error has been found on GnssAntennaInfoInterface::init, status=%d",
-              static_cast<int32_t>(initRet));
-        return JNI_FALSE;
-    } else {
-        ALOGD("gnss antenna info has been enabled");
-    }
-
-    return JNI_TRUE;
+    return gnssAntennaInfoIface->setCallback(std::make_unique<gnss::GnssAntennaInfoCallback>());
 }
 
 static jboolean android_location_gnss_hal_GnssNative_stop_antenna_info_listening(JNIEnv* /* env */,
@@ -1960,9 +1847,7 @@
         ALOGE("%s: IGnssAntennaInfo interface not available.", __func__);
         return JNI_FALSE;
     }
-
-    auto result = gnssAntennaInfoIface->close();
-    return checkHidlReturn(result, "IGnssAntennaInfo close() failed.");
+    return gnssAntennaInfoIface->close();
 }
 
 static jboolean android_location_gnss_hal_GnssNative_is_measurement_supported(JNIEnv* env, jclass) {
@@ -2002,174 +1887,21 @@
 
 static jboolean android_location_gnss_hal_GnssNative_is_measurement_corrections_supported(
         JNIEnv* env, jclass) {
-    if (gnssCorrectionsIface_V1_0 != nullptr || gnssCorrectionsIface_V1_1 != nullptr) {
+    if (gnssMeasurementCorrectionsIface != nullptr) {
         return JNI_TRUE;
     }
 
     return JNI_FALSE;
 }
 
-static SingleSatCorrection_V1_0 getSingleSatCorrection_1_0_withoutConstellation(
-        JNIEnv* env, jobject singleSatCorrectionObj) {
-    jint correctionFlags = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatFlags);
-    jint satId = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatId);
-    jfloat carrierFreqHz =
-            env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatCarrierFreq);
-    jfloat probSatIsLos =
-            env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatIsLosProb);
-    jfloat eplMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEpl);
-    jfloat eplUncMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEplUnc);
-    uint16_t corrFlags = static_cast<uint16_t>(correctionFlags);
-    jobject reflectingPlaneObj = nullptr;
-    bool has_ref_plane = (corrFlags & GnssSingleSatCorrectionFlags::HAS_REFLECTING_PLANE) != 0;
-    if (has_ref_plane) {
-        reflectingPlaneObj =
-                env->CallObjectMethod(singleSatCorrectionObj, method_correctionSatRefPlane);
-    }
-
-    ReflectingPlane reflectingPlane;
-    if (has_ref_plane) {
-        jdouble latitudeDegreesRefPlane =
-                env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneLatDeg);
-        jdouble longitudeDegreesRefPlane =
-                env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneLngDeg);
-        jdouble altitudeDegreesRefPlane =
-                env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneAltDeg);
-        jdouble azimuthDegreeRefPlane =
-                env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneAzimDeg);
-        reflectingPlane = {
-                .latitudeDegrees = latitudeDegreesRefPlane,
-                .longitudeDegrees = longitudeDegreesRefPlane,
-                .altitudeMeters = altitudeDegreesRefPlane,
-                .azimuthDegrees = azimuthDegreeRefPlane,
-        };
-    }
-    env->DeleteLocalRef(reflectingPlaneObj);
-
-    SingleSatCorrection_V1_0 singleSatCorrection = {
-            .singleSatCorrectionFlags = corrFlags,
-            .svid = static_cast<uint16_t>(satId),
-            .carrierFrequencyHz = carrierFreqHz,
-            .probSatIsLos = probSatIsLos,
-            .excessPathLengthMeters = eplMeters,
-            .excessPathLengthUncertaintyMeters = eplUncMeters,
-            .reflectingPlane = reflectingPlane,
-    };
-
-    return singleSatCorrection;
-}
-
-static void getSingleSatCorrectionList_1_1(JNIEnv* env, jobject singleSatCorrectionList,
-                                           hidl_vec<SingleSatCorrection_V1_1>& list) {
-    for (uint16_t i = 0; i < list.size(); ++i) {
-        jobject singleSatCorrectionObj =
-                env->CallObjectMethod(singleSatCorrectionList, method_correctionListGet, i);
-
-        SingleSatCorrection_V1_0 singleSatCorrection_1_0 =
-                getSingleSatCorrection_1_0_withoutConstellation(env, singleSatCorrectionObj);
-
-        jint constType = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatConstType);
-
-        SingleSatCorrection_V1_1 singleSatCorrection_1_1 = {
-                .v1_0 = singleSatCorrection_1_0,
-                .constellation = static_cast<GnssConstellationType_V2_0>(constType),
-        };
-
-        list[i] = singleSatCorrection_1_1;
-        env->DeleteLocalRef(singleSatCorrectionObj);
-    }
-}
-
-static void getSingleSatCorrectionList_1_0(JNIEnv* env, jobject singleSatCorrectionList,
-                                           hidl_vec<SingleSatCorrection_V1_0>& list) {
-    for (uint16_t i = 0; i < list.size(); ++i) {
-        jobject singleSatCorrectionObj =
-                env->CallObjectMethod(singleSatCorrectionList, method_correctionListGet, i);
-
-        SingleSatCorrection_V1_0 singleSatCorrection =
-                getSingleSatCorrection_1_0_withoutConstellation(env, singleSatCorrectionObj);
-
-        jint constType = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatConstType);
-
-        singleSatCorrection.constellation = static_cast<GnssConstellationType_V1_0>(constType),
-
-        list[i] = singleSatCorrection;
-        env->DeleteLocalRef(singleSatCorrectionObj);
-    }
-}
-
 static jboolean android_location_gnss_hal_GnssNative_inject_measurement_corrections(
         JNIEnv* env, jclass, jobject correctionsObj) {
-    if (gnssCorrectionsIface_V1_0 == nullptr && gnssCorrectionsIface_V1_1 == nullptr) {
+    if (gnssMeasurementCorrectionsIface == nullptr) {
         ALOGW("Trying to inject GNSS measurement corrections on a chipset that does not"
             " support them.");
         return JNI_FALSE;
     }
-
-    jobject singleSatCorrectionList = env->CallObjectMethod(correctionsObj,
-        method_correctionsGetSingleSatCorrectionList);
-
-    auto len = (singleSatCorrectionList == nullptr)
-        ? 0
-        : env->CallIntMethod(singleSatCorrectionList, method_listSize);
-    if (len == 0) {
-        ALOGI("Empty correction list injected....Returning with no HAL injection");
-        return JNI_TRUE;
-    }
-
-    jdouble latitudeDegreesCorr = env->CallDoubleMethod(
-        correctionsObj, method_correctionsGetLatitudeDegrees);
-    jdouble longitudeDegreesCorr = env->CallDoubleMethod(
-        correctionsObj, method_correctionsGetLongitudeDegrees);
-    jdouble altitudeDegreesCorr = env->CallDoubleMethod(
-        correctionsObj, method_correctionsGetAltitudeMeters);
-    jdouble horizontalPositionUncertaintyMeters = env->CallDoubleMethod(
-        correctionsObj, method_correctionsGetHorPosUncMeters);
-    jdouble verticalPositionUncertaintyMeters = env->CallDoubleMethod(
-            correctionsObj, method_correctionsGetVerPosUncMeters);
-    jlong toaGpsNanosOfWeek = env->CallLongMethod(
-        correctionsObj, method_correctionsGetToaGpsNanosecondsOfWeek);
-
-    MeasurementCorrections_V1_0 measurementCorrections_1_0 = {
-        .latitudeDegrees = latitudeDegreesCorr,
-        .longitudeDegrees = longitudeDegreesCorr,
-        .altitudeMeters = altitudeDegreesCorr,
-        .horizontalPositionUncertaintyMeters = horizontalPositionUncertaintyMeters,
-        .verticalPositionUncertaintyMeters = verticalPositionUncertaintyMeters,
-        .toaGpsNanosecondsOfWeek = static_cast<uint64_t>(toaGpsNanosOfWeek),
-    };
-
-    if (gnssCorrectionsIface_V1_1 != nullptr) {
-
-        jboolean hasEnvironmentBearingCorr = env->CallBooleanMethod(
-            correctionsObj, method_correctionsHasEnvironmentBearing);
-        jfloat environmentBearingDegreesCorr = env->CallFloatMethod(
-            correctionsObj, method_correctionsGetEnvironmentBearingDegrees);
-        jfloat environmentBearingUncertaintyDegreesCorr = env->CallFloatMethod(
-            correctionsObj, method_correctionsGetEnvironmentBearingUncertaintyDegrees);
-
-        hidl_vec<SingleSatCorrection_V1_1> list(len);
-        getSingleSatCorrectionList_1_1(env, singleSatCorrectionList, list);
-
-        MeasurementCorrections_V1_1 measurementCorrections_1_1 = {
-                .v1_0 = measurementCorrections_1_0,
-                .hasEnvironmentBearing = static_cast<bool>(hasEnvironmentBearingCorr),
-                .environmentBearingDegrees = environmentBearingDegreesCorr,
-                .environmentBearingUncertaintyDegrees = environmentBearingUncertaintyDegreesCorr,
-                .satCorrections = list,
-        };
-
-        auto result = gnssCorrectionsIface_V1_1->setCorrections_1_1(measurementCorrections_1_1);
-        return checkHidlReturn(result, "IMeasurementCorrections 1.1 setCorrections() failed.");
-    }
-
-    hidl_vec<SingleSatCorrection_V1_0> list(len);
-    getSingleSatCorrectionList_1_0(env, singleSatCorrectionList, list);
-    env->DeleteLocalRef(singleSatCorrectionList);
-    measurementCorrections_1_0.satCorrections = list;
-
-    auto result = gnssCorrectionsIface_V1_0->setCorrections(measurementCorrections_1_0);
-    return checkHidlReturn(result, "IMeasurementCorrections 1.0 setCorrections() failed.");
+    return gnssMeasurementCorrectionsIface->setCorrections(env, correctionsObj);
 }
 
 static jboolean android_location_gnss_hal_GnssNative_is_navigation_message_supported(JNIEnv* env,
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index bed72a8..e52df15 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -27,6 +27,7 @@
         "AGnssCallback.cpp",
         "AGnssRil.cpp",
         "AGnssRilCallback.cpp",
+        "GnssAntennaInfo.cpp",
         "GnssAntennaInfoCallback.cpp",
         "GnssBatching.cpp",
         "GnssBatchingCallback.cpp",
@@ -40,6 +41,8 @@
         "GnssNavigationMessageCallback.cpp",
         "GnssVisibilityControl.cpp",
         "GnssVisibilityControlCallback.cpp",
+        "MeasurementCorrections.cpp",
+        "MeasurementCorrectionsCallback.cpp",
         "Utils.cpp",
     ],
 }
diff --git a/services/core/jni/gnss/GnssAntennaInfo.cpp b/services/core/jni/gnss/GnssAntennaInfo.cpp
new file mode 100644
index 0000000..0f41b0f
--- /dev/null
+++ b/services/core/jni/gnss/GnssAntennaInfo.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "GnssAntennaInfoJni"
+
+#include "GnssAntennaInfo.h"
+
+#include "Utils.h"
+
+using IGnssAntennaInfoAidl = android::hardware::gnss::IGnssAntennaInfo;
+using IGnssAntennaInfo_V2_1 = android::hardware::gnss::V2_1::IGnssAntennaInfo;
+
+namespace android::gnss {
+
+// Implementation of GnssAntennaInfo (AIDL HAL)
+
+GnssAntennaInfoAidl::GnssAntennaInfoAidl(const sp<IGnssAntennaInfoAidl>& iGnssAntennaInfo)
+      : mIGnssAntennaInfoAidl(iGnssAntennaInfo) {
+    assert(mIGnssAntennaInfoAidl != nullptr);
+}
+
+jboolean GnssAntennaInfoAidl::setCallback(
+        const std::unique_ptr<GnssAntennaInfoCallback>& callback) {
+    auto status = mIGnssAntennaInfoAidl->setCallback(callback->getAidl());
+    return checkAidlStatus(status, "IGnssAntennaInfoAidl setCallback() failed.");
+}
+
+jboolean GnssAntennaInfoAidl::close() {
+    auto status = mIGnssAntennaInfoAidl->close();
+    return checkAidlStatus(status, "IGnssAntennaInfoAidl close() failed");
+}
+
+// Implementation of GnssAntennaInfo_V2_1
+
+GnssAntennaInfo_V2_1::GnssAntennaInfo_V2_1(const sp<IGnssAntennaInfo_V2_1>& iGnssAntennaInfo)
+      : mIGnssAntennaInfo_V2_1(iGnssAntennaInfo) {
+    assert(mIGnssAntennaInfo_V2_1 != nullptr);
+}
+
+jboolean GnssAntennaInfo_V2_1::setCallback(
+        const std::unique_ptr<GnssAntennaInfoCallback>& callback) {
+    auto result = mIGnssAntennaInfo_V2_1->setCallback(callback->getV2_1());
+    if (!checkHidlReturn(result, "IGnssAntennaInfo_V2_1 setCallback() failed.")) {
+        return JNI_FALSE;
+    }
+
+    IGnssAntennaInfo_V2_1::GnssAntennaInfoStatus initRet = result;
+    if (initRet != IGnssAntennaInfo_V2_1::GnssAntennaInfoStatus::SUCCESS) {
+        ALOGE("An error has been found on GnssAntennaInfoInterface::init, status=%d",
+              static_cast<int32_t>(initRet));
+        return JNI_FALSE;
+    } else {
+        ALOGD("gnss antenna info v2_1 has been enabled");
+    }
+    return JNI_TRUE;
+}
+
+jboolean GnssAntennaInfo_V2_1::close() {
+    auto result = mIGnssAntennaInfo_V2_1->close();
+    return checkHidlReturn(result, "IGnssAntennaInfo_V2_1 close() failed.");
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssAntennaInfo.h b/services/core/jni/gnss/GnssAntennaInfo.h
new file mode 100644
index 0000000..9a9dc2a
--- /dev/null
+++ b/services/core/jni/gnss/GnssAntennaInfo.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_GNSSANTENNAINFO_H
+#define _ANDROID_SERVER_GNSS_GNSSANTENNAINFO_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/2.1/IGnssAntennaInfo.h>
+#include <android/hardware/gnss/BnGnssAntennaInfo.h>
+#include <log/log.h>
+
+#include "GnssAntennaInfoCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+class GnssAntennaInfoInterface {
+public:
+    virtual ~GnssAntennaInfoInterface() {}
+    virtual jboolean setCallback(const std::unique_ptr<GnssAntennaInfoCallback>& callback) = 0;
+    virtual jboolean close() = 0;
+};
+
+class GnssAntennaInfoAidl : public GnssAntennaInfoInterface {
+public:
+    GnssAntennaInfoAidl(const sp<android::hardware::gnss::IGnssAntennaInfo>& iGnssAntennaInfo);
+    jboolean setCallback(const std::unique_ptr<GnssAntennaInfoCallback>& callback) override;
+    jboolean close() override;
+
+private:
+    const sp<android::hardware::gnss::IGnssAntennaInfo> mIGnssAntennaInfoAidl;
+};
+
+class GnssAntennaInfo_V2_1 : public GnssAntennaInfoInterface {
+public:
+    GnssAntennaInfo_V2_1(
+            const sp<android::hardware::gnss::V2_1::IGnssAntennaInfo>& iGnssAntennaInfo);
+    jboolean setCallback(const std::unique_ptr<GnssAntennaInfoCallback>& callback) override;
+    jboolean close() override;
+
+private:
+    const sp<android::hardware::gnss::V2_1::IGnssAntennaInfo> mIGnssAntennaInfo_V2_1;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_GNSSANTENNAINFO_H
diff --git a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
index 99d06eb..2e99b2b 100644
--- a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
+++ b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp
@@ -25,8 +25,10 @@
 using android::hardware::hidl_vec;
 using android::hardware::Return;
 using android::hardware::Void;
+using binder::Status;
 
-using IGnssAntennaInfoCallback = android::hardware::gnss::V2_1::IGnssAntennaInfoCallback;
+using IGnssAntennaInfoCallbackAidl = android::hardware::gnss::IGnssAntennaInfoCallback;
+using IGnssAntennaInfoCallback_V2_1 = android::hardware::gnss::V2_1::IGnssAntennaInfoCallback;
 
 namespace {
 jclass class_gnssAntennaInfoBuilder;
@@ -92,14 +94,21 @@
     class_doubleArray = (jclass)env->NewGlobalRef(doubleArrayClass);
 }
 
-Return<void> GnssAntennaInfoCallback::gnssAntennaInfoCb(
-        const hidl_vec<IGnssAntennaInfoCallback::GnssAntennaInfo>& gnssAntennaInfos) {
-    translateAndReportGnssAntennaInfo(gnssAntennaInfos);
+binder::Status GnssAntennaInfoCallbackAidl::gnssAntennaInfoCb(
+        const std::vector<IGnssAntennaInfoCallbackAidl::GnssAntennaInfo>& gnssAntennaInfos) {
+    GnssAntennaInfoCallbackUtil::translateAndReportGnssAntennaInfo(gnssAntennaInfos);
+    return Status::ok();
+}
+
+Return<void> GnssAntennaInfoCallback_V2_1::gnssAntennaInfoCb(
+        const hidl_vec<IGnssAntennaInfoCallback_V2_1::GnssAntennaInfo>& gnssAntennaInfos) {
+    GnssAntennaInfoCallbackUtil::translateAndReportGnssAntennaInfo(gnssAntennaInfos);
     return Void();
 }
 
-jobjectArray GnssAntennaInfoCallback::translate2dDoubleArray(
-        JNIEnv* env, const hidl_vec<IGnssAntennaInfoCallback::Row>& array) {
+template <template <class...> class T_vector, class T_info>
+jobjectArray GnssAntennaInfoCallbackUtil::translate2dDoubleArray(JNIEnv* env,
+                                                                 const T_vector<T_info>& array) {
     jsize numRows = array.size();
     if (numRows == 0) {
         // Empty array
@@ -124,8 +133,9 @@
     return returnArray;
 }
 
-jobject GnssAntennaInfoCallback::translateAllGnssAntennaInfos(
-        JNIEnv* env, const hidl_vec<IGnssAntennaInfoCallback::GnssAntennaInfo>& gnssAntennaInfos) {
+template <template <class...> class T_vector, class T_info>
+jobject GnssAntennaInfoCallbackUtil::translateAllGnssAntennaInfos(
+        JNIEnv* env, const T_vector<T_info>& gnssAntennaInfos) {
     jobject arrayList = env->NewObject(class_arrayList,
                                        method_arrayListCtor); // Create new ArrayList instance
 
@@ -141,8 +151,9 @@
     return arrayList;
 }
 
-jobject GnssAntennaInfoCallback::translatePhaseCenterOffset(
-        JNIEnv* env, const IGnssAntennaInfoCallback::GnssAntennaInfo& gnssAntennaInfo) {
+template <class T>
+jobject GnssAntennaInfoCallbackUtil::translatePhaseCenterOffset(JNIEnv* env,
+                                                                const T& gnssAntennaInfo) {
     jobject phaseCenterOffset =
             env->NewObject(class_phaseCenterOffset, method_phaseCenterOffsetCtor,
                            gnssAntennaInfo.phaseCenterOffsetCoordinateMillimeters.x,
@@ -155,10 +166,11 @@
     return phaseCenterOffset;
 }
 
-jobject GnssAntennaInfoCallback::translatePhaseCenterVariationCorrections(
-        JNIEnv* env, const IGnssAntennaInfoCallback::GnssAntennaInfo& gnssAntennaInfo) {
-    if (gnssAntennaInfo.phaseCenterVariationCorrectionMillimeters == NULL ||
-        gnssAntennaInfo.phaseCenterVariationCorrectionUncertaintyMillimeters == NULL) {
+template <>
+jobject GnssAntennaInfoCallbackUtil::translatePhaseCenterVariationCorrections(
+        JNIEnv* env, const IGnssAntennaInfoCallbackAidl::GnssAntennaInfo& gnssAntennaInfo) {
+    if (gnssAntennaInfo.phaseCenterVariationCorrectionMillimeters.empty() ||
+        gnssAntennaInfo.phaseCenterVariationCorrectionUncertaintyMillimeters.empty()) {
         return NULL;
     }
 
@@ -171,6 +183,8 @@
 
     if (phaseCenterVariationCorrectionsArray == NULL ||
         phaseCenterVariationCorrectionsUncertaintiesArray == NULL) {
+        env->DeleteLocalRef(phaseCenterVariationCorrectionsArray);
+        env->DeleteLocalRef(phaseCenterVariationCorrectionsUncertaintiesArray);
         return NULL;
     }
 
@@ -185,10 +199,44 @@
     return phaseCenterVariationCorrections;
 }
 
-jobject GnssAntennaInfoCallback::translateSignalGainCorrections(
-        JNIEnv* env, const IGnssAntennaInfoCallback::GnssAntennaInfo& gnssAntennaInfo) {
-    if (gnssAntennaInfo.signalGainCorrectionDbi == NULL ||
-        gnssAntennaInfo.signalGainCorrectionUncertaintyDbi == NULL) {
+template <>
+jobject GnssAntennaInfoCallbackUtil::translatePhaseCenterVariationCorrections(
+        JNIEnv* env, const IGnssAntennaInfoCallback_V2_1::GnssAntennaInfo& gnssAntennaInfo) {
+    if (gnssAntennaInfo.phaseCenterVariationCorrectionMillimeters == NULL ||
+        gnssAntennaInfo.phaseCenterVariationCorrectionUncertaintyMillimeters == NULL) {
+        return NULL;
+    }
+
+    jobjectArray phaseCenterVariationCorrectionsArray =
+            translate2dDoubleArray(env, gnssAntennaInfo.phaseCenterVariationCorrectionMillimeters);
+    jobjectArray phaseCenterVariationCorrectionsUncertaintiesArray =
+            translate2dDoubleArray(env,
+                                   gnssAntennaInfo
+                                           .phaseCenterVariationCorrectionUncertaintyMillimeters);
+
+    if (phaseCenterVariationCorrectionsArray == NULL ||
+        phaseCenterVariationCorrectionsUncertaintiesArray == NULL) {
+        env->DeleteLocalRef(phaseCenterVariationCorrectionsArray);
+        env->DeleteLocalRef(phaseCenterVariationCorrectionsUncertaintiesArray);
+        return NULL;
+    }
+
+    jobject phaseCenterVariationCorrections =
+            env->NewObject(class_sphericalCorrections, method_sphericalCorrectionsCtor,
+                           phaseCenterVariationCorrectionsArray,
+                           phaseCenterVariationCorrectionsUncertaintiesArray);
+
+    env->DeleteLocalRef(phaseCenterVariationCorrectionsArray);
+    env->DeleteLocalRef(phaseCenterVariationCorrectionsUncertaintiesArray);
+
+    return phaseCenterVariationCorrections;
+}
+
+template <>
+jobject GnssAntennaInfoCallbackUtil::translateSignalGainCorrections(
+        JNIEnv* env, const IGnssAntennaInfoCallbackAidl::GnssAntennaInfo& gnssAntennaInfo) {
+    if (gnssAntennaInfo.signalGainCorrectionDbi.empty() ||
+        gnssAntennaInfo.signalGainCorrectionUncertaintyDbi.empty()) {
         return NULL;
     }
     jobjectArray signalGainCorrectionsArray =
@@ -197,6 +245,8 @@
             translate2dDoubleArray(env, gnssAntennaInfo.signalGainCorrectionUncertaintyDbi);
 
     if (signalGainCorrectionsArray == NULL || signalGainCorrectionsUncertaintiesArray == NULL) {
+        env->DeleteLocalRef(signalGainCorrectionsArray);
+        env->DeleteLocalRef(signalGainCorrectionsUncertaintiesArray);
         return NULL;
     }
 
@@ -210,8 +260,37 @@
     return signalGainCorrections;
 }
 
-jobject GnssAntennaInfoCallback::translateSingleGnssAntennaInfo(
-        JNIEnv* env, const IGnssAntennaInfoCallback::GnssAntennaInfo& gnssAntennaInfo) {
+template <>
+jobject GnssAntennaInfoCallbackUtil::translateSignalGainCorrections(
+        JNIEnv* env, const IGnssAntennaInfoCallback_V2_1::GnssAntennaInfo& gnssAntennaInfo) {
+    if (gnssAntennaInfo.signalGainCorrectionDbi == NULL ||
+        gnssAntennaInfo.signalGainCorrectionUncertaintyDbi == NULL) {
+        return NULL;
+    }
+    jobjectArray signalGainCorrectionsArray =
+            translate2dDoubleArray(env, gnssAntennaInfo.signalGainCorrectionDbi);
+    jobjectArray signalGainCorrectionsUncertaintiesArray =
+            translate2dDoubleArray(env, gnssAntennaInfo.signalGainCorrectionUncertaintyDbi);
+
+    if (signalGainCorrectionsArray == NULL || signalGainCorrectionsUncertaintiesArray == NULL) {
+        env->DeleteLocalRef(signalGainCorrectionsArray);
+        env->DeleteLocalRef(signalGainCorrectionsUncertaintiesArray);
+        return NULL;
+    }
+
+    jobject signalGainCorrections =
+            env->NewObject(class_sphericalCorrections, method_sphericalCorrectionsCtor,
+                           signalGainCorrectionsArray, signalGainCorrectionsUncertaintiesArray);
+
+    env->DeleteLocalRef(signalGainCorrectionsArray);
+    env->DeleteLocalRef(signalGainCorrectionsUncertaintiesArray);
+
+    return signalGainCorrections;
+}
+
+template <class T>
+jobject GnssAntennaInfoCallbackUtil::translateSingleGnssAntennaInfo(JNIEnv* env,
+                                                                    const T& gnssAntennaInfo) {
     jobject phaseCenterOffset = translatePhaseCenterOffset(env, gnssAntennaInfo);
 
     // Nullable
@@ -228,7 +307,7 @@
     // Set fields
     callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
                                    method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz,
-                                   gnssAntennaInfo.carrierFrequencyMHz);
+                                   getCarrierFrequencyMHz(gnssAntennaInfo));
     callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject,
                                    method_gnssAntennaInfoBuilderSetPhaseCenterOffset,
                                    phaseCenterOffset);
@@ -251,8 +330,9 @@
     return gnssAntennaInfoObject;
 }
 
-void GnssAntennaInfoCallback::translateAndReportGnssAntennaInfo(
-        const hidl_vec<IGnssAntennaInfoCallback::GnssAntennaInfo>& gnssAntennaInfos) {
+template <template <class...> class T_vector, class T_info>
+void GnssAntennaInfoCallbackUtil::translateAndReportGnssAntennaInfo(
+        const T_vector<T_info>& gnssAntennaInfos) {
     JNIEnv* env = getJniEnv();
 
     jobject arrayList = translateAllGnssAntennaInfos(env, gnssAntennaInfos);
@@ -262,7 +342,7 @@
     env->DeleteLocalRef(arrayList);
 }
 
-void GnssAntennaInfoCallback::reportAntennaInfo(JNIEnv* env, const jobject antennaInfosArray) {
+void GnssAntennaInfoCallbackUtil::reportAntennaInfo(JNIEnv* env, const jobject antennaInfosArray) {
     env->CallVoidMethod(mCallbacksObj, method_reportAntennaInfo, antennaInfosArray);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
 }
diff --git a/services/core/jni/gnss/GnssAntennaInfoCallback.h b/services/core/jni/gnss/GnssAntennaInfoCallback.h
index 0fc7633..89f27e0 100644
--- a/services/core/jni/gnss/GnssAntennaInfoCallback.h
+++ b/services/core/jni/gnss/GnssAntennaInfoCallback.h
@@ -24,7 +24,9 @@
 #endif
 
 #include <android/hardware/gnss/2.1/IGnssAntennaInfo.h>
+#include <android/hardware/gnss/BnGnssAntennaInfoCallback.h>
 #include <log/log.h>
+
 #include "Utils.h"
 #include "jni.h"
 
@@ -33,50 +35,86 @@
 void GnssAntennaInfo_class_init_once(JNIEnv* env, jclass& clazz);
 
 /*
- * GnssAntennaInfoCallback implements the callback methods required for the
- * GnssAntennaInfo interface.
+ * GnssAntennaInfoCallbackAidl implements the callback methods required for the
+ * android::hardware::gnss::IGnssAntennaInfo interface.
  */
-struct GnssAntennaInfoCallback : public android::hardware::gnss::V2_1::IGnssAntennaInfoCallback {
-    GnssAntennaInfoCallback(jobject& callbacksObj) : mCallbacksObj(callbacksObj) {}
+class GnssAntennaInfoCallbackAidl : public android::hardware::gnss::BnGnssAntennaInfoCallback {
+public:
+    binder::Status gnssAntennaInfoCb(const std::vector<GnssAntennaInfo>& gnssAntennaInfos) override;
+};
+
+/*
+ * GnssAntennaInfoCallback implements the callback methods required for the
+ * V2_1::GnssAntennaInfo interface.
+ */
+class GnssAntennaInfoCallback_V2_1
+      : public android::hardware::gnss::V2_1::IGnssAntennaInfoCallback {
+public:
     // Methods from V2_1::GnssAntennaInfoCallback follow.
     hardware::Return<void> gnssAntennaInfoCb(
             const hardware::hidl_vec<
                     android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo>&
-                    gnssAntennaInfos);
+                    gnssAntennaInfos) override;
+};
+
+class GnssAntennaInfoCallback {
+public:
+    GnssAntennaInfoCallback() {}
+    sp<GnssAntennaInfoCallbackAidl> getAidl() {
+        if (callbackAidl == nullptr) {
+            callbackAidl = sp<GnssAntennaInfoCallbackAidl>::make();
+        }
+        return callbackAidl;
+    }
+
+    sp<GnssAntennaInfoCallback_V2_1> getV2_1() {
+        if (callbackV2_1 == nullptr) {
+            callbackV2_1 = sp<GnssAntennaInfoCallback_V2_1>::make();
+        }
+        return callbackV2_1;
+    }
 
 private:
-    jobject translateAllGnssAntennaInfos(
-            JNIEnv* env,
-            const hardware::hidl_vec<
-                    android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo>&
-                    gnssAntennaInfos);
-    jobject translateSingleGnssAntennaInfo(
-            JNIEnv* env,
-            const android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo&
-                    gnssAntennaInfo);
-    jobject translatePhaseCenterOffset(
-            JNIEnv* env,
-            const android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo&
-                    gnssAntennaInfo);
-    jobject translatePhaseCenterVariationCorrections(
-            JNIEnv* env,
-            const android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo&
-                    gnssAntennaInfo);
-    jobject translateSignalGainCorrections(
-            JNIEnv* env,
-            const android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo&
-                    gnssAntennaInfo);
-    jobjectArray translate2dDoubleArray(
-            JNIEnv* env,
-            const hardware::hidl_vec<android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::Row>&
-                    array);
-    void translateAndReportGnssAntennaInfo(
-            const hardware::hidl_vec<
-                    android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo>&
-                    gnssAntennaInfos);
-    void reportAntennaInfo(JNIEnv* env, const jobject antennaInfosArray);
+    sp<GnssAntennaInfoCallbackAidl> callbackAidl;
+    sp<GnssAntennaInfoCallback_V2_1> callbackV2_1;
+};
 
-    jobject& mCallbacksObj;
+struct GnssAntennaInfoCallbackUtil {
+    template <template <class...> class T_vector, class T_info>
+    static jobject translateAllGnssAntennaInfos(JNIEnv* env,
+                                                const T_vector<T_info>& gnssAntennaInfos);
+
+    template <class T>
+    static jobject translateSingleGnssAntennaInfo(JNIEnv* env, const T& gnssAntennaInfo);
+
+    template <class T>
+    static jobject translatePhaseCenterOffset(JNIEnv* env, const T& gnssAntennaInfo);
+
+    template <class T>
+    static jobject translatePhaseCenterVariationCorrections(JNIEnv* env, const T& gnssAntennaInfo);
+
+    template <class T>
+    static jobject translateSignalGainCorrections(JNIEnv* env, const T& gnssAntennaInfo);
+
+    template <template <class...> class T_vector, class T_info>
+    static jobjectArray translate2dDoubleArray(JNIEnv* env, const T_vector<T_info>& array);
+
+    template <template <class...> class T_vector, class T_info>
+    static void translateAndReportGnssAntennaInfo(const T_vector<T_info>& gnssAntennaInfos);
+
+    static void reportAntennaInfo(JNIEnv* env, const jobject antennaInfosArray);
+
+    static double getCarrierFrequencyMHz(
+            const android::hardware::gnss::IGnssAntennaInfoCallback::GnssAntennaInfo&
+                    gnssAntennaInfo) {
+        return gnssAntennaInfo.carrierFrequencyHz * 1e-6;
+    };
+
+    static double getCarrierFrequencyMHz(
+            const android::hardware::gnss::V2_1::IGnssAntennaInfoCallback::GnssAntennaInfo&
+                    gnssAntennaInfo) {
+        return gnssAntennaInfo.carrierFrequencyMHz;
+    };
 };
 
 } // namespace android::gnss
diff --git a/services/core/jni/gnss/GnssDebug.h b/services/core/jni/gnss/GnssDebug.h
index 9f5ff21..1e1a7b4 100644
--- a/services/core/jni/gnss/GnssDebug.h
+++ b/services/core/jni/gnss/GnssDebug.h
@@ -75,7 +75,7 @@
     static uint32_t getConstellationType(const std::vector<T>& satelliteDataArray, size_t i);
 
     template <class T>
-    static uint32_t getTimeEstimateMs(const T& data);
+    static int64_t getTimeEstimateMs(const T& data);
 
     template <class T_DebugData, class T_SatelliteData>
     static jstring parseDebugData(JNIEnv* env, std::stringstream& internalState,
@@ -109,12 +109,12 @@
 }
 
 template <class T>
-uint32_t GnssDebugUtil::getTimeEstimateMs(const T& data) {
+int64_t GnssDebugUtil::getTimeEstimateMs(const T& data) {
     return data.time.timeEstimate;
 }
 
 template <>
-uint32_t GnssDebugUtil::getTimeEstimateMs(
+int64_t GnssDebugUtil::getTimeEstimateMs(
         const android::hardware::gnss::IGnssDebug::DebugData& data) {
     return data.time.timeEstimateMs;
 }
diff --git a/services/core/jni/gnss/MeasurementCorrections.cpp b/services/core/jni/gnss/MeasurementCorrections.cpp
new file mode 100644
index 0000000..8a3d84c
--- /dev/null
+++ b/services/core/jni/gnss/MeasurementCorrections.cpp
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "MeasurementCorrectionsJni"
+
+#include "MeasurementCorrections.h"
+
+#include "Utils.h"
+
+using IMeasurementCorrections_V1_0 =
+        android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
+using IMeasurementCorrections_V1_1 =
+        android::hardware::gnss::measurement_corrections::V1_1::IMeasurementCorrections;
+using IMeasurementCorrections_Aidl =
+        android::hardware::gnss::measurement_corrections::IMeasurementCorrectionsInterface;
+using MeasurementCorrections_V1_0 =
+        android::hardware::gnss::measurement_corrections::V1_0::MeasurementCorrections;
+using MeasurementCorrections_V1_1 =
+        android::hardware::gnss::measurement_corrections::V1_1::MeasurementCorrections;
+using MeasurementCorrections_Aidl =
+        android::hardware::gnss::measurement_corrections::MeasurementCorrections;
+using GnssSingleSatCorrectionFlags_V1_0 =
+        android::hardware::gnss::measurement_corrections::V1_0::GnssSingleSatCorrectionFlags;
+using SingleSatCorrection_V1_0 =
+        android::hardware::gnss::measurement_corrections::V1_0::SingleSatCorrection;
+using SingleSatCorrection_V1_1 =
+        android::hardware::gnss::measurement_corrections::V1_1::SingleSatCorrection;
+using SingleSatCorrection_Aidl =
+        android::hardware::gnss::measurement_corrections::SingleSatCorrection;
+using ReflectingPlane_V1_0 =
+        android::hardware::gnss::measurement_corrections::V1_0::ReflectingPlane;
+using ReflectingPlane_Aidl = android::hardware::gnss::measurement_corrections::ReflectingPlane;
+using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
+using GnssConstellationType_Aidl = android::hardware::gnss::GnssConstellationType;
+
+namespace android::gnss {
+
+namespace {
+jmethodID method_correctionsGetLatitudeDegrees;
+jmethodID method_correctionsGetLongitudeDegrees;
+jmethodID method_correctionsGetAltitudeMeters;
+jmethodID method_correctionsGetHorPosUncMeters;
+jmethodID method_correctionsGetVerPosUncMeters;
+jmethodID method_correctionsGetToaGpsNanosecondsOfWeek;
+jmethodID method_correctionsGetSingleSatCorrectionList;
+jmethodID method_correctionsHasEnvironmentBearing;
+jmethodID method_correctionsGetEnvironmentBearingDegrees;
+jmethodID method_correctionsGetEnvironmentBearingUncertaintyDegrees;
+jmethodID method_listSize;
+jmethodID method_correctionListGet;
+jmethodID method_correctionSatFlags;
+jmethodID method_correctionSatConstType;
+jmethodID method_correctionSatId;
+jmethodID method_correctionSatCarrierFreq;
+jmethodID method_correctionSatIsLosProb;
+jmethodID method_correctionSatEpl;
+jmethodID method_correctionSatEplUnc;
+jmethodID method_correctionSatRefPlane;
+jmethodID method_correctionPlaneLatDeg;
+jmethodID method_correctionPlaneLngDeg;
+jmethodID method_correctionPlaneAltDeg;
+jmethodID method_correctionPlaneAzimDeg;
+} // anonymous namespace
+
+void MeasurementCorrections_class_init_once(JNIEnv* env, jclass clazz) {
+    jclass measCorrClass = env->FindClass("android/location/GnssMeasurementCorrections");
+    method_correctionsGetLatitudeDegrees =
+            env->GetMethodID(measCorrClass, "getLatitudeDegrees", "()D");
+    method_correctionsGetLongitudeDegrees =
+            env->GetMethodID(measCorrClass, "getLongitudeDegrees", "()D");
+    method_correctionsGetAltitudeMeters =
+            env->GetMethodID(measCorrClass, "getAltitudeMeters", "()D");
+    method_correctionsGetHorPosUncMeters =
+            env->GetMethodID(measCorrClass, "getHorizontalPositionUncertaintyMeters", "()D");
+    method_correctionsGetVerPosUncMeters =
+            env->GetMethodID(measCorrClass, "getVerticalPositionUncertaintyMeters", "()D");
+    method_correctionsGetToaGpsNanosecondsOfWeek =
+            env->GetMethodID(measCorrClass, "getToaGpsNanosecondsOfWeek", "()J");
+    method_correctionsGetSingleSatCorrectionList =
+            env->GetMethodID(measCorrClass, "getSingleSatelliteCorrectionList",
+                             "()Ljava/util/List;");
+    method_correctionsHasEnvironmentBearing =
+            env->GetMethodID(measCorrClass, "hasEnvironmentBearing", "()Z");
+    method_correctionsGetEnvironmentBearingDegrees =
+            env->GetMethodID(measCorrClass, "getEnvironmentBearingDegrees", "()F");
+    method_correctionsGetEnvironmentBearingUncertaintyDegrees =
+            env->GetMethodID(measCorrClass, "getEnvironmentBearingUncertaintyDegrees", "()F");
+
+    jclass corrListClass = env->FindClass("java/util/List");
+    method_listSize = env->GetMethodID(corrListClass, "size", "()I");
+    method_correctionListGet = env->GetMethodID(corrListClass, "get", "(I)Ljava/lang/Object;");
+
+    jclass singleSatCorrClass = env->FindClass("android/location/GnssSingleSatCorrection");
+    method_correctionSatFlags =
+            env->GetMethodID(singleSatCorrClass, "getSingleSatelliteCorrectionFlags", "()I");
+    method_correctionSatConstType =
+            env->GetMethodID(singleSatCorrClass, "getConstellationType", "()I");
+    method_correctionSatId = env->GetMethodID(singleSatCorrClass, "getSatelliteId", "()I");
+    method_correctionSatCarrierFreq =
+            env->GetMethodID(singleSatCorrClass, "getCarrierFrequencyHz", "()F");
+    method_correctionSatIsLosProb =
+            env->GetMethodID(singleSatCorrClass, "getProbabilityLineOfSight", "()F");
+    method_correctionSatEpl =
+            env->GetMethodID(singleSatCorrClass, "getExcessPathLengthMeters", "()F");
+    method_correctionSatEplUnc =
+            env->GetMethodID(singleSatCorrClass, "getExcessPathLengthUncertaintyMeters", "()F");
+    method_correctionSatRefPlane = env->GetMethodID(singleSatCorrClass, "getReflectingPlane",
+                                                    "()Landroid/location/GnssReflectingPlane;");
+
+    jclass refPlaneClass = env->FindClass("android/location/GnssReflectingPlane");
+    method_correctionPlaneLatDeg = env->GetMethodID(refPlaneClass, "getLatitudeDegrees", "()D");
+    method_correctionPlaneLngDeg = env->GetMethodID(refPlaneClass, "getLongitudeDegrees", "()D");
+    method_correctionPlaneAltDeg = env->GetMethodID(refPlaneClass, "getAltitudeMeters", "()D");
+    method_correctionPlaneAzimDeg = env->GetMethodID(refPlaneClass, "getAzimuthDegrees", "()D");
+}
+
+template <>
+bool MeasurementCorrectionsUtil::translateMeasurementCorrections(
+        JNIEnv* env, jobject correctionsObj, MeasurementCorrections_V1_0& corrections) {
+    jobject singleSatCorrectionList =
+            env->CallObjectMethod(correctionsObj, method_correctionsGetSingleSatCorrectionList);
+    if (singleSatCorrectionList == nullptr) return false;
+    auto len = env->CallIntMethod(singleSatCorrectionList, method_listSize);
+
+    jdouble latitudeDegreesCorr =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetLatitudeDegrees);
+    jdouble longitudeDegreesCorr =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetLongitudeDegrees);
+    jdouble altitudeDegreesCorr =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetAltitudeMeters);
+    jdouble horizontalPositionUncertaintyMeters =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetHorPosUncMeters);
+    jdouble verticalPositionUncertaintyMeters =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetVerPosUncMeters);
+    jlong toaGpsNanosOfWeek =
+            env->CallLongMethod(correctionsObj, method_correctionsGetToaGpsNanosecondsOfWeek);
+
+    corrections.latitudeDegrees = latitudeDegreesCorr;
+    corrections.longitudeDegrees = longitudeDegreesCorr;
+    corrections.altitudeMeters = altitudeDegreesCorr;
+    corrections.horizontalPositionUncertaintyMeters = horizontalPositionUncertaintyMeters;
+    corrections.verticalPositionUncertaintyMeters = verticalPositionUncertaintyMeters;
+    corrections.toaGpsNanosecondsOfWeek = static_cast<uint64_t>(toaGpsNanosOfWeek);
+
+    hardware::hidl_vec<SingleSatCorrection_V1_0> list(len);
+    MeasurementCorrectionsUtil::getSingleSatCorrectionList_1_0(env, singleSatCorrectionList, list);
+    env->DeleteLocalRef(singleSatCorrectionList);
+    corrections.satCorrections = list;
+    return true;
+}
+
+template <>
+bool MeasurementCorrectionsUtil::translateMeasurementCorrections(
+        JNIEnv* env, jobject correctionsObj, MeasurementCorrections_V1_1& corrections) {
+    jobject singleSatCorrectionList =
+            env->CallObjectMethod(correctionsObj, method_correctionsGetSingleSatCorrectionList);
+    if (singleSatCorrectionList == nullptr) return false;
+    auto len = env->CallIntMethod(singleSatCorrectionList, method_listSize);
+
+    MeasurementCorrections_V1_0 measurementCorrections_1_0;
+    translateMeasurementCorrections<MeasurementCorrections_V1_0>(env, correctionsObj,
+                                                                 measurementCorrections_1_0);
+    measurementCorrections_1_0.satCorrections.resize(0);
+
+    jboolean hasEnvironmentBearingCorr =
+            env->CallBooleanMethod(correctionsObj, method_correctionsHasEnvironmentBearing);
+    jfloat environmentBearingDegreesCorr =
+            env->CallFloatMethod(correctionsObj, method_correctionsGetEnvironmentBearingDegrees);
+    jfloat environmentBearingUncertaintyDegreesCorr =
+            env->CallFloatMethod(correctionsObj,
+                                 method_correctionsGetEnvironmentBearingUncertaintyDegrees);
+
+    hardware::hidl_vec<SingleSatCorrection_V1_1> list(len);
+    MeasurementCorrectionsUtil::getSingleSatCorrectionList_1_1(env, singleSatCorrectionList, list);
+    env->DeleteLocalRef(singleSatCorrectionList);
+
+    corrections.v1_0 = measurementCorrections_1_0;
+    corrections.hasEnvironmentBearing = static_cast<bool>(hasEnvironmentBearingCorr);
+    corrections.environmentBearingDegrees = environmentBearingDegreesCorr;
+    corrections.environmentBearingUncertaintyDegrees = environmentBearingUncertaintyDegreesCorr;
+    corrections.satCorrections = list;
+    return true;
+}
+
+template <>
+bool MeasurementCorrectionsUtil::translateMeasurementCorrections(
+        JNIEnv* env, jobject correctionsObj, MeasurementCorrections_Aidl& corrections) {
+    jobject singleSatCorrectionList =
+            env->CallObjectMethod(correctionsObj, method_correctionsGetSingleSatCorrectionList);
+    if (singleSatCorrectionList == nullptr) return false;
+    auto len = env->CallIntMethod(singleSatCorrectionList, method_listSize);
+
+    jdouble latitudeDegreesCorr =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetLatitudeDegrees);
+    jdouble longitudeDegreesCorr =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetLongitudeDegrees);
+    jdouble altitudeDegreesCorr =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetAltitudeMeters);
+    jdouble horizontalPositionUncertaintyMeters =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetHorPosUncMeters);
+    jdouble verticalPositionUncertaintyMeters =
+            env->CallDoubleMethod(correctionsObj, method_correctionsGetVerPosUncMeters);
+    jlong toaGpsNanosOfWeek =
+            env->CallLongMethod(correctionsObj, method_correctionsGetToaGpsNanosecondsOfWeek);
+
+    corrections.latitudeDegrees = static_cast<double>(latitudeDegreesCorr);
+    corrections.longitudeDegrees = static_cast<double>(longitudeDegreesCorr);
+    corrections.altitudeMeters = static_cast<double>(altitudeDegreesCorr);
+    corrections.horizontalPositionUncertaintyMeters =
+            static_cast<double>(horizontalPositionUncertaintyMeters);
+    corrections.verticalPositionUncertaintyMeters =
+            static_cast<double>(verticalPositionUncertaintyMeters);
+    corrections.toaGpsNanosecondsOfWeek = static_cast<int64_t>(toaGpsNanosOfWeek);
+
+    jboolean hasEnvironmentBearingCorr =
+            env->CallBooleanMethod(correctionsObj, method_correctionsHasEnvironmentBearing);
+    jfloat environmentBearingDegreesCorr =
+            env->CallFloatMethod(correctionsObj, method_correctionsGetEnvironmentBearingDegrees);
+    jfloat environmentBearingUncertaintyDegreesCorr =
+            env->CallFloatMethod(correctionsObj,
+                                 method_correctionsGetEnvironmentBearingUncertaintyDegrees);
+
+    std::vector<SingleSatCorrection_Aidl> list(len);
+    MeasurementCorrectionsUtil::getSingleSatCorrectionList_Aidl(env, singleSatCorrectionList, list);
+    env->DeleteLocalRef(singleSatCorrectionList);
+
+    corrections.hasEnvironmentBearing = static_cast<bool>(hasEnvironmentBearingCorr);
+    corrections.environmentBearingDegrees = environmentBearingDegreesCorr;
+    corrections.environmentBearingUncertaintyDegrees = environmentBearingUncertaintyDegreesCorr;
+    corrections.satCorrections = list;
+    return true;
+}
+
+// Implementation of MeasurementCorrections (AIDL HAL)
+
+MeasurementCorrectionsIface_Aidl::MeasurementCorrectionsIface_Aidl(
+        const sp<IMeasurementCorrections_Aidl>& iMeasurementCorrections)
+      : mIMeasurementCorrectionsAidl(iMeasurementCorrections) {
+    assert(mIMeasurementCorrectionsAidl != nullptr);
+}
+
+jboolean MeasurementCorrectionsIface_Aidl::setCorrections(JNIEnv* env, jobject correctionsObj) {
+    MeasurementCorrections_Aidl measurementCorrections_aidl;
+    if (!MeasurementCorrectionsUtil::translateMeasurementCorrections<
+                MeasurementCorrections_Aidl>(env, correctionsObj, measurementCorrections_aidl)) {
+        ALOGI("Empty correction list injected....Returning with no HAL injection");
+        return JNI_TRUE;
+    }
+    auto status = mIMeasurementCorrectionsAidl->setCorrections(measurementCorrections_aidl);
+    return checkAidlStatus(status, "IMeasurementCorrectionsAidl setCorrections() failed");
+}
+
+jboolean MeasurementCorrectionsIface_Aidl::setCallback(
+        const std::unique_ptr<MeasurementCorrectionsCallback>& callback) {
+    auto status = mIMeasurementCorrectionsAidl->setCallback(callback->getAidl());
+    return checkAidlStatus(status, "IMeasurementCorrectionsAidl setCallback() failed.");
+}
+
+// Implementation of MeasurementCorrectionsIface_V1_0
+
+MeasurementCorrectionsIface_V1_0::MeasurementCorrectionsIface_V1_0(
+        const sp<IMeasurementCorrections_V1_0>& iMeasurementCorrections)
+      : mIMeasurementCorrections_V1_0(iMeasurementCorrections) {
+    assert(mIMeasurementCorrections_V1_0 != nullptr);
+}
+
+jboolean MeasurementCorrectionsIface_V1_0::setCorrections(JNIEnv* env, jobject correctionsObj) {
+    MeasurementCorrections_V1_0 measurementCorrections_1_0;
+    if (!MeasurementCorrectionsUtil::translateMeasurementCorrections<
+                MeasurementCorrections_V1_0>(env, correctionsObj, measurementCorrections_1_0)) {
+        ALOGI("Empty correction list injected....Returning with no HAL injection");
+        return JNI_TRUE;
+    }
+    auto result = mIMeasurementCorrections_V1_0->setCorrections(measurementCorrections_1_0);
+    return checkHidlReturn(result, "IMeasurementCorrections 1.0 setCorrections() failed.");
+}
+
+jboolean MeasurementCorrectionsIface_V1_0::setCallback(
+        const std::unique_ptr<MeasurementCorrectionsCallback>& callback) {
+    auto result = mIMeasurementCorrections_V1_0->setCallback(callback->getHidl());
+    return checkHidlReturn(result, "IMeasurementCorrections_V1_0 setCallback() failed.");
+}
+
+// Implementation of MeasurementCorrectionsIface_V1_1
+
+MeasurementCorrectionsIface_V1_1::MeasurementCorrectionsIface_V1_1(
+        const sp<IMeasurementCorrections_V1_1>& iMeasurementCorrections)
+      : mIMeasurementCorrections_V1_1(iMeasurementCorrections) {
+    assert(mIMeasurementCorrections_V1_1 != nullptr);
+}
+
+jboolean MeasurementCorrectionsIface_V1_1::setCorrections(JNIEnv* env, jobject correctionsObj) {
+    MeasurementCorrections_V1_1 measurementCorrections_1_1;
+    if (!MeasurementCorrectionsUtil::translateMeasurementCorrections<
+                MeasurementCorrections_V1_1>(env, correctionsObj, measurementCorrections_1_1)) {
+        ALOGI("Empty correction list injected....Returning with no HAL injection");
+        return JNI_TRUE;
+    }
+    auto result = mIMeasurementCorrections_V1_1->setCorrections_1_1(measurementCorrections_1_1);
+    return checkHidlReturn(result, "IMeasurementCorrections 1.1 setCorrections() failed.");
+}
+
+jboolean MeasurementCorrectionsIface_V1_1::setCallback(
+        const std::unique_ptr<MeasurementCorrectionsCallback>& callback) {
+    auto result = mIMeasurementCorrections_V1_1->setCallback(callback->getHidl());
+    return checkHidlReturn(result, "IMeasurementCorrections_V1_1 setCallback() failed.");
+}
+
+SingleSatCorrection_V1_0
+MeasurementCorrectionsUtil::getSingleSatCorrection_1_0_withoutConstellation(
+        JNIEnv* env, jobject singleSatCorrectionObj) {
+    jint correctionFlags = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatFlags);
+    jint satId = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatId);
+    jfloat carrierFreqHz =
+            env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatCarrierFreq);
+    jfloat probSatIsLos =
+            env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatIsLosProb);
+    jfloat eplMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEpl);
+    jfloat eplUncMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEplUnc);
+    uint16_t corrFlags = static_cast<uint16_t>(correctionFlags);
+
+    ReflectingPlane_V1_0 reflectingPlane;
+    if ((corrFlags & GnssSingleSatCorrectionFlags_V1_0::HAS_REFLECTING_PLANE) != 0)
+        MeasurementCorrectionsUtil::getReflectingPlane<ReflectingPlane_V1_0>(env,
+                                                                             singleSatCorrectionObj,
+                                                                             reflectingPlane);
+
+    SingleSatCorrection_V1_0 singleSatCorrection = {
+            .singleSatCorrectionFlags = corrFlags,
+            .svid = static_cast<uint16_t>(satId),
+            .carrierFrequencyHz = carrierFreqHz,
+            .probSatIsLos = probSatIsLos,
+            .excessPathLengthMeters = eplMeters,
+            .excessPathLengthUncertaintyMeters = eplUncMeters,
+            .reflectingPlane = reflectingPlane,
+    };
+
+    return singleSatCorrection;
+}
+
+SingleSatCorrection_Aidl MeasurementCorrectionsUtil::getSingleSatCorrection_Aidl(
+        JNIEnv* env, jobject singleSatCorrectionObj) {
+    jint correctionFlags = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatFlags);
+    jint satId = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatId);
+    jfloat carrierFreqHz =
+            env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatCarrierFreq);
+    jfloat probSatIsLos =
+            env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatIsLosProb);
+    jfloat eplMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEpl);
+    jfloat eplUncMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEplUnc);
+    int32_t corrFlags = static_cast<int32_t>(correctionFlags);
+
+    ReflectingPlane_Aidl reflectingPlane;
+    if ((corrFlags & SingleSatCorrection_Aidl::SINGLE_SAT_CORRECTION_HAS_REFLECTING_PLANE) != 0)
+        MeasurementCorrectionsUtil::getReflectingPlane<ReflectingPlane_Aidl>(env,
+                                                                             singleSatCorrectionObj,
+                                                                             reflectingPlane);
+
+    jint constType = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatConstType);
+
+    SingleSatCorrection_Aidl singleSatCorrection;
+    singleSatCorrection.singleSatCorrectionFlags = corrFlags;
+    singleSatCorrection.constellation = static_cast<GnssConstellationType_Aidl>(constType);
+    singleSatCorrection.svid = static_cast<int32_t>(satId);
+    singleSatCorrection.carrierFrequencyHz = carrierFreqHz;
+    singleSatCorrection.probSatIsLos = probSatIsLos;
+    singleSatCorrection.excessPathLengthMeters = eplMeters;
+    singleSatCorrection.excessPathLengthUncertaintyMeters = eplUncMeters;
+    singleSatCorrection.reflectingPlane = reflectingPlane;
+
+    return singleSatCorrection;
+}
+
+void MeasurementCorrectionsUtil::getSingleSatCorrectionList_1_0(
+        JNIEnv* env, jobject singleSatCorrectionList,
+        hardware::hidl_vec<SingleSatCorrection_V1_0>& list) {
+    for (uint16_t i = 0; i < list.size(); ++i) {
+        jobject singleSatCorrectionObj =
+                env->CallObjectMethod(singleSatCorrectionList, method_correctionListGet, i);
+
+        SingleSatCorrection_V1_0 singleSatCorrection =
+                getSingleSatCorrection_1_0_withoutConstellation(env, singleSatCorrectionObj);
+
+        jint constType = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatConstType);
+
+        singleSatCorrection.constellation = static_cast<GnssConstellationType_V1_0>(constType),
+
+        list[i] = singleSatCorrection;
+        env->DeleteLocalRef(singleSatCorrectionObj);
+    }
+}
+
+void MeasurementCorrectionsUtil::getSingleSatCorrectionList_1_1(
+        JNIEnv* env, jobject singleSatCorrectionList,
+        hardware::hidl_vec<SingleSatCorrection_V1_1>& list) {
+    for (uint16_t i = 0; i < list.size(); ++i) {
+        jobject singleSatCorrectionObj =
+                env->CallObjectMethod(singleSatCorrectionList, method_correctionListGet, i);
+
+        SingleSatCorrection_V1_0 singleSatCorrection_1_0 =
+                getSingleSatCorrection_1_0_withoutConstellation(env, singleSatCorrectionObj);
+
+        jint constType = env->CallIntMethod(singleSatCorrectionObj, method_correctionSatConstType);
+
+        SingleSatCorrection_V1_1 singleSatCorrection_1_1 = {
+                .v1_0 = singleSatCorrection_1_0,
+                .constellation = static_cast<GnssConstellationType_V2_0>(constType),
+        };
+
+        list[i] = singleSatCorrection_1_1;
+        env->DeleteLocalRef(singleSatCorrectionObj);
+    }
+}
+
+void MeasurementCorrectionsUtil::getSingleSatCorrectionList_Aidl(
+        JNIEnv* env, jobject singleSatCorrectionList, std::vector<SingleSatCorrection_Aidl>& list) {
+    for (uint16_t i = 0; i < list.size(); ++i) {
+        jobject singleSatCorrectionObj =
+                env->CallObjectMethod(singleSatCorrectionList, method_correctionListGet, i);
+
+        SingleSatCorrection_Aidl singleSatCorrection_Aidl =
+                getSingleSatCorrection_Aidl(env, singleSatCorrectionObj);
+
+        list[i] = singleSatCorrection_Aidl;
+        env->DeleteLocalRef(singleSatCorrectionObj);
+    }
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/MeasurementCorrections.h b/services/core/jni/gnss/MeasurementCorrections.h
new file mode 100644
index 0000000..a2e6027
--- /dev/null
+++ b/services/core/jni/gnss/MeasurementCorrections.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#ifndef _ANDROID_SERVER_MEASUREMENTCORRECTIONS_H
+#define _ANDROID_SERVER_MEASUREMENTCORRECTIONS_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/measurement_corrections/BnMeasurementCorrectionsInterface.h>
+#include <log/log.h>
+
+#include "MeasurementCorrectionsCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+extern jmethodID method_correctionsGetLatitudeDegrees;
+extern jmethodID method_correctionsGetLongitudeDegrees;
+extern jmethodID method_correctionsGetAltitudeMeters;
+extern jmethodID method_correctionsGetHorPosUncMeters;
+extern jmethodID method_correctionsGetVerPosUncMeters;
+extern jmethodID method_correctionsGetToaGpsNanosecondsOfWeek;
+extern jmethodID method_correctionsGetSingleSatCorrectionList;
+extern jmethodID method_correctionsHasEnvironmentBearing;
+extern jmethodID method_correctionsGetEnvironmentBearingDegrees;
+extern jmethodID method_correctionsGetEnvironmentBearingUncertaintyDegrees;
+extern jmethodID method_listSize;
+extern jmethodID method_correctionListGet;
+extern jmethodID method_correctionSatFlags;
+extern jmethodID method_correctionSatConstType;
+extern jmethodID method_correctionSatId;
+extern jmethodID method_correctionSatCarrierFreq;
+extern jmethodID method_correctionSatIsLosProb;
+extern jmethodID method_correctionSatEpl;
+extern jmethodID method_correctionSatEplUnc;
+extern jmethodID method_correctionSatRefPlane;
+extern jmethodID method_correctionPlaneLatDeg;
+extern jmethodID method_correctionPlaneLngDeg;
+extern jmethodID method_correctionPlaneAltDeg;
+extern jmethodID method_correctionPlaneAzimDeg;
+} // anonymous namespace
+
+void MeasurementCorrections_class_init_once(JNIEnv* env, jclass clazz);
+
+class MeasurementCorrectionsInterface {
+public:
+    virtual ~MeasurementCorrectionsInterface() {}
+    virtual jboolean setCorrections(JNIEnv* env, jobject correctionsObj) = 0;
+    virtual jboolean setCallback(
+            const std::unique_ptr<MeasurementCorrectionsCallback>& callback) = 0;
+};
+
+class MeasurementCorrectionsIface_Aidl : public MeasurementCorrectionsInterface {
+public:
+    MeasurementCorrectionsIface_Aidl(
+            const sp<android::hardware::gnss::measurement_corrections::
+                             IMeasurementCorrectionsInterface>& iMeasurementCorrections);
+    jboolean setCorrections(JNIEnv* env, jobject correctionsObj) override;
+    jboolean setCallback(const std::unique_ptr<MeasurementCorrectionsCallback>& callback) override;
+
+private:
+    const sp<android::hardware::gnss::measurement_corrections::IMeasurementCorrectionsInterface>
+            mIMeasurementCorrectionsAidl;
+};
+
+class MeasurementCorrectionsIface_V1_0 : public MeasurementCorrectionsInterface {
+public:
+    MeasurementCorrectionsIface_V1_0(
+            const sp<android::hardware::gnss::measurement_corrections::V1_0::
+                             IMeasurementCorrections>& iMeasurementCorrections);
+    jboolean setCorrections(JNIEnv* env, jobject correctionsObj) override;
+    jboolean setCallback(const std::unique_ptr<MeasurementCorrectionsCallback>& callback) override;
+
+private:
+    const sp<android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections>
+            mIMeasurementCorrections_V1_0;
+};
+
+class MeasurementCorrectionsIface_V1_1 : public MeasurementCorrectionsInterface {
+public:
+    MeasurementCorrectionsIface_V1_1(
+            const sp<android::hardware::gnss::measurement_corrections::V1_1::
+                             IMeasurementCorrections>& iMeasurementCorrections);
+    jboolean setCorrections(JNIEnv* env, jobject correctionsObj) override;
+    jboolean setCallback(const std::unique_ptr<MeasurementCorrectionsCallback>& callback) override;
+
+private:
+    const sp<android::hardware::gnss::measurement_corrections::V1_1::IMeasurementCorrections>
+            mIMeasurementCorrections_V1_1;
+};
+
+struct MeasurementCorrectionsUtil {
+    static android::hardware::gnss::measurement_corrections::V1_0::SingleSatCorrection
+    getSingleSatCorrection_1_0_withoutConstellation(JNIEnv* env, jobject singleSatCorrectionObj);
+    static android::hardware::gnss::measurement_corrections::SingleSatCorrection
+    getSingleSatCorrection_Aidl(JNIEnv* env, jobject singleSatCorrectionObj);
+    static void getSingleSatCorrectionList_1_1(
+            JNIEnv* env, jobject singleSatCorrectionList,
+            hardware::hidl_vec<
+                    android::hardware::gnss::measurement_corrections::V1_1::SingleSatCorrection>&
+                    list);
+    static void getSingleSatCorrectionList_1_0(
+            JNIEnv* env, jobject singleSatCorrectionList,
+            hardware::hidl_vec<
+                    android::hardware::gnss::measurement_corrections::V1_0::SingleSatCorrection>&
+                    list);
+    static void getSingleSatCorrectionList_Aidl(
+            JNIEnv* env, jobject singleSatCorrectionList,
+            std::vector<android::hardware::gnss::measurement_corrections::SingleSatCorrection>&
+                    list);
+    template <class T>
+    static bool translateMeasurementCorrections(JNIEnv* env, jobject correctionsObj,
+                                                T& corrections);
+    template <class T>
+    static void getReflectingPlane(JNIEnv* env, jobject singleSatCorrectionObj, T& reflectingPlane);
+};
+
+template <class T>
+void MeasurementCorrectionsUtil::getReflectingPlane(JNIEnv* env, jobject singleSatCorrectionObj,
+                                                    T& reflectingPlane) {
+    jobject reflectingPlaneObj =
+            env->CallObjectMethod(singleSatCorrectionObj, method_correctionSatRefPlane);
+    jdouble latitudeDegreesRefPlane =
+            env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneLatDeg);
+    jdouble longitudeDegreesRefPlane =
+            env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneLngDeg);
+    jdouble altitudeDegreesRefPlane =
+            env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneAltDeg);
+    jdouble azimuthDegreeRefPlane =
+            env->CallDoubleMethod(reflectingPlaneObj, method_correctionPlaneAzimDeg);
+    reflectingPlane.latitudeDegrees = latitudeDegreesRefPlane;
+    reflectingPlane.longitudeDegrees = longitudeDegreesRefPlane;
+    reflectingPlane.altitudeMeters = altitudeDegreesRefPlane;
+    reflectingPlane.azimuthDegrees = azimuthDegreeRefPlane;
+    env->DeleteLocalRef(reflectingPlaneObj);
+}
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_MEASUREMENTCORRECTIONS_H
diff --git a/services/core/jni/gnss/MeasurementCorrectionsCallback.cpp b/services/core/jni/gnss/MeasurementCorrectionsCallback.cpp
new file mode 100644
index 0000000..a319a0d
--- /dev/null
+++ b/services/core/jni/gnss/MeasurementCorrectionsCallback.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#define LOG_TAG "MeasurementCorrectionsCbJni"
+
+#include "MeasurementCorrectionsCallback.h"
+
+namespace android::gnss {
+
+namespace {
+jmethodID method_setSubHalMeasurementCorrectionsCapabilities;
+}
+
+void MeasurementCorrectionsCallback_class_init_once(JNIEnv* env, jclass clazz) {
+    method_setSubHalMeasurementCorrectionsCapabilities =
+            env->GetMethodID(clazz, "setSubHalMeasurementCorrectionsCapabilities", "(I)V");
+}
+
+using binder::Status;
+using hardware::Return;
+
+// Implementation of MeasurementCorrectionsCallbackAidl class.
+
+Status MeasurementCorrectionsCallbackAidl::setCapabilitiesCb(const int capabilities) {
+    MeasurementCorrectionsCallbackUtil::setCapabilitiesCb(capabilities);
+    return Status::ok();
+}
+
+// Implementation of MeasurementCorrectionsCallbackHidl class.
+
+Return<void> MeasurementCorrectionsCallbackHidl::setCapabilitiesCb(uint32_t capabilities) {
+    MeasurementCorrectionsCallbackUtil::setCapabilitiesCb(capabilities);
+    return hardware::Void();
+}
+
+// Implementation of MeasurementCorrectionsCallbackUtil class.
+
+void MeasurementCorrectionsCallbackUtil::setCapabilitiesCb(uint32_t capabilities) {
+    ALOGD("%s: %du\n", __func__, capabilities);
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_setSubHalMeasurementCorrectionsCapabilities,
+                        capabilities);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/MeasurementCorrectionsCallback.h b/services/core/jni/gnss/MeasurementCorrectionsCallback.h
new file mode 100644
index 0000000..a493a8ae
--- /dev/null
+++ b/services/core/jni/gnss/MeasurementCorrectionsCallback.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_MEASUREMENTCORRECTIONSCALLBACK_H
+#define _ANDROID_SERVER_GNSS_MEASUREMENTCORRECTIONSCALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
+#include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h>
+#include <android/hardware/gnss/measurement_corrections/BnMeasurementCorrectionsCallback.h>
+#include <log/log.h>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+extern jmethodID method_setSubHalMeasurementCorrectionsCapabilities;
+}
+
+void MeasurementCorrectionsCallback_class_init_once(JNIEnv* env, jclass clazz);
+
+/*
+ * MeasurementCorrectionsCallbackAidl class implements the callback methods required by the
+ * android::hardware::gnss::measurement_corrections::IMeasurementCorrectionsCallback interface.
+ */
+class MeasurementCorrectionsCallbackAidl
+      : public hardware::gnss::measurement_corrections::BnMeasurementCorrectionsCallback {
+public:
+    MeasurementCorrectionsCallbackAidl() {}
+    binder::Status setCapabilitiesCb(const int capabilities) override;
+};
+
+/*
+ * MeasurementCorrectionsCallbackHidl implements callback methods of
+ * IMeasurementCorrectionsCallback.hal interface.
+ */
+class MeasurementCorrectionsCallbackHidl : public android::hardware::gnss::measurement_corrections::
+                                                   V1_0::IMeasurementCorrectionsCallback {
+public:
+    MeasurementCorrectionsCallbackHidl() {}
+    hardware::Return<void> setCapabilitiesCb(uint32_t capabilities) override;
+};
+
+class MeasurementCorrectionsCallback {
+public:
+    MeasurementCorrectionsCallback() {}
+    sp<MeasurementCorrectionsCallbackAidl> getAidl() {
+        if (callbackAidl == nullptr) {
+            callbackAidl = sp<MeasurementCorrectionsCallbackAidl>::make();
+        }
+        return callbackAidl;
+    }
+
+    sp<MeasurementCorrectionsCallbackHidl> getHidl() {
+        if (callbackHidl == nullptr) {
+            callbackHidl = sp<MeasurementCorrectionsCallbackHidl>::make();
+        }
+        return callbackHidl;
+    }
+
+private:
+    sp<MeasurementCorrectionsCallbackAidl> callbackAidl;
+    sp<MeasurementCorrectionsCallbackHidl> callbackHidl;
+};
+
+struct MeasurementCorrectionsCallbackUtil {
+    static void setCapabilitiesCb(uint32_t capabilities);
+
+private:
+    MeasurementCorrectionsCallbackUtil() = delete;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_MEASUREMENTCORRECTIONSCALLBACK_H
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 62a16f7..c5f990d 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -60,6 +60,12 @@
     private static ProfcollectForwardingService sSelfService;
     private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
 
+    private IProviderStatusCallback mProviderStatusCallback = new IProviderStatusCallback.Stub() {
+        public void onProviderReady() {
+            mHandler.sendEmptyMessage(ProfcollectdHandler.MESSAGE_REGISTER_SCHEDULERS);
+        }
+    };
+
     public ProfcollectForwardingService(Context context) {
         super(context);
 
@@ -93,13 +99,23 @@
             }
             BackgroundThread.get().getThreadHandler().post(() -> {
                 if (serviceHasSupportedTraceProvider()) {
-                    registerObservers();
-                    ProfcollectBGJobService.schedule(getContext());
+                    registerProviderStatusCallback();
                 }
             });
         }
     }
 
+    private void registerProviderStatusCallback() {
+        if (mIProfcollect == null) {
+            return;
+        }
+        try {
+            mIProfcollect.registerProviderStatusCallback(mProviderStatusCallback);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, e.getMessage());
+        }
+    }
+
     private boolean serviceHasSupportedTraceProvider() {
         if (mIProfcollect == null) {
             return false;
@@ -141,6 +157,7 @@
         }
 
         public static final int MESSAGE_BINDER_CONNECT = 0;
+        public static final int MESSAGE_REGISTER_SCHEDULERS = 1;
 
         @Override
         public void handleMessage(android.os.Message message) {
@@ -148,6 +165,10 @@
                 case MESSAGE_BINDER_CONNECT:
                     connectNativeService();
                     break;
+                case MESSAGE_REGISTER_SCHEDULERS:
+                    registerObservers();
+                    ProfcollectBGJobService.schedule(getContext());
+                    break;
                 default:
                     throw new AssertionError("Unknown message: " + message);
             }
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index d710308..ac115a2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -30,7 +30,6 @@
 import static com.android.server.DeviceIdleController.LIGHT_STATE_IDLE_MAINTENANCE;
 import static com.android.server.DeviceIdleController.LIGHT_STATE_INACTIVE;
 import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE;
-import static com.android.server.DeviceIdleController.LIGHT_STATE_PRE_IDLE;
 import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK;
 import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS;
 import static com.android.server.DeviceIdleController.MSG_RESET_PRE_IDLE_TIMEOUT_FACTOR;
@@ -112,6 +111,7 @@
 /**
  * Tests for {@link com.android.server.DeviceIdleController}.
  */
+@SuppressWarnings("GuardedBy")
 @RunWith(AndroidJUnit4.class)
 public class DeviceIdleControllerTest {
     private DeviceIdleController mDeviceIdleController;
@@ -875,7 +875,7 @@
     @Test
     public void testLightStepIdleStateLocked_InvalidStates() {
         mDeviceIdleController.becomeActiveLocked("testing", 0);
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         // stepLightIdleStateLocked doesn't handle the ACTIVE case, so the state
         // should stay as ACTIVE.
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
@@ -888,7 +888,7 @@
     @Test
     public void testLightStepIdleStateLocked_Overriden() {
         enterLightState(LIGHT_STATE_OVERRIDE);
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_OVERRIDE);
     }
 
@@ -906,18 +906,18 @@
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
         // No active ops means INACTIVE should go straight to IDLE.
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
         // Should just alternate between IDLE and IDLE_MAINTENANCE now.
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
     }
 
@@ -930,26 +930,22 @@
         setScreenOn(false);
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
-        // Active ops means INACTIVE should go to PRE_IDLE to wait.
+        // After enough time, INACTIVE should go to IDLE regardless of any active ops.
         mDeviceIdleController.setJobsActive(true);
         mDeviceIdleController.setAlarmsActive(true);
         mDeviceIdleController.setActiveIdleOpsForTest(1);
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
-        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
-
-        // Even with active ops, PRE_IDLE should go to IDLE.
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
         // Should just alternate between IDLE and IDLE_MAINTENANCE now.
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
     }
 
@@ -967,24 +963,24 @@
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
         // No active ops means INACTIVE should go straight to IDLE.
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
         // Should cycle between IDLE, WAITING_FOR_NETWORK, and IDLE_MAINTENANCE now.
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
     }
 
@@ -997,36 +993,177 @@
         setScreenOn(false);
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
-        // Active ops means INACTIVE should go to PRE_IDLE to wait.
+        // After enough time, INACTIVE should go to IDLE regardless of any active ops.
         mDeviceIdleController.setJobsActive(true);
         mDeviceIdleController.setAlarmsActive(true);
         mDeviceIdleController.setActiveIdleOpsForTest(1);
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
-        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
-
-        // Even with active ops, PRE_IDLE should go to IDLE.
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
         // Should cycle between IDLE, WAITING_FOR_NETWORK, and IDLE_MAINTENANCE now.
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_WAITING_FOR_NETWORK);
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
     }
 
     @Test
+    public void testLightStepIdleStateSkippedAlarms() {
+        setNetworkConnected(true);
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor = ArgumentCaptor
+                .forClass(AlarmManager.OnAlarmListener.class);
+        doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
+                eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any());
+        doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
+                eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any());
+
+        // Set state to INACTIVE.
+        mDeviceIdleController.becomeActiveLocked("testing", 0);
+        setChargingOn(false);
+        setScreenOn(false);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+
+        final AlarmManager.OnAlarmListener progressionListener =
+                alarmListenerCaptor.getAllValues().get(0);
+        final AlarmManager.OnAlarmListener maintenanceListener =
+                alarmListenerCaptor.getAllValues().get(1);
+
+        // Set things to make it look like the INACTIVE -> IDLE alarm didn't fire and the
+        // MAINTENANCE alarm just fired.
+        mInjector.nowElapsed = mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting();
+        // If the non-wakeup alarm doesn't fire in a timely manner, we would see both fire at the
+        // same time.
+        progressionListener.onAlarm();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+        maintenanceListener.onAlarm();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+
+        assertTrue(mInjector.nowElapsed < mDeviceIdleController.getNextLightAlarmTimeForTesting());
+
+        // MAINTENANCE->IDLE alarm goes off at correct time.
+        mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting();
+        progressionListener.onAlarm();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+
+        // Go back to MAINTENANCE
+        mInjector.nowElapsed = mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting();
+        maintenanceListener.onAlarm();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+
+        assertTrue(mInjector.nowElapsed < mDeviceIdleController.getNextLightAlarmTimeForTesting());
+        assertTrue(mInjector.nowElapsed
+                < mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting());
+
+        // MAINTENANCE->IDLE alarm is delayed until IDLE->MAINTENANCE alarm goes off.
+        mInjector.nowElapsed = mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting();
+        progressionListener.onAlarm();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+        maintenanceListener.onAlarm();
+        verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+    }
+
+    @Test
+    public void testLightStepIdleStateIdlingTimeIncreases() {
+        final long maintenanceTimeMs = 60_000L;
+        mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = maintenanceTimeMs;
+        mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = maintenanceTimeMs;
+        mConstants.LIGHT_IDLE_TIMEOUT = 5 * 60_000L;
+        mConstants.LIGHT_MAX_IDLE_TIMEOUT = 20 * 60_000L;
+        mConstants.LIGHT_IDLE_FACTOR = 2f;
+
+        setNetworkConnected(true);
+        mDeviceIdleController.setJobsActive(false);
+        mDeviceIdleController.setAlarmsActive(false);
+        mDeviceIdleController.setActiveIdleOpsForTest(0);
+
+        InOrder alarmManagerInOrder = inOrder(mAlarmManager);
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor = ArgumentCaptor
+                .forClass(AlarmManager.OnAlarmListener.class);
+        doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
+                eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any());
+        doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
+                eq("DeviceIdleController.light"), alarmListenerCaptor.capture(), any());
+
+        // Set state to INACTIVE.
+        mDeviceIdleController.becomeActiveLocked("testing", 0);
+        setChargingOn(false);
+        setScreenOn(false);
+        verifyLightStateConditions(LIGHT_STATE_INACTIVE);
+        long idlingTimeMs = mConstants.LIGHT_IDLE_TIMEOUT;
+        final long idleAfterInactiveExpiryTime =
+                mInjector.nowElapsed + mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT;
+        alarmManagerInOrder.verify(mAlarmManager).setWindow(
+                eq(AlarmManager.ELAPSED_REALTIME),
+                eq(idleAfterInactiveExpiryTime),
+                anyLong(), anyString(), any(), any());
+        // Maintenance alarm
+        alarmManagerInOrder.verify(mAlarmManager).setWindow(
+                eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                eq(idleAfterInactiveExpiryTime + idlingTimeMs),
+                anyLong(), anyString(), any(), any());
+
+        final AlarmManager.OnAlarmListener progressionListener =
+                alarmListenerCaptor.getAllValues().get(0);
+        final AlarmManager.OnAlarmListener maintenanceListener =
+                alarmListenerCaptor.getAllValues().get(1);
+
+        // INACTIVE -> IDLE alarm
+        mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting();
+        progressionListener.onAlarm();
+        verifyLightStateConditions(LIGHT_STATE_IDLE);
+        alarmManagerInOrder.verify(mAlarmManager).setWindow(
+                eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                eq(mInjector.nowElapsed + idlingTimeMs),
+                anyLong(), anyString(), any(), any());
+
+        for (int i = 0; i < 2; ++i) {
+            // IDLE->MAINTENANCE alarm
+            mInjector.nowElapsed =
+                    mDeviceIdleController.getNextLightMaintenanceAlarmTimeForTesting();
+            maintenanceListener.onAlarm();
+            verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
+            long maintenanceExpiryTime = mInjector.nowElapsed + maintenanceTimeMs;
+            idlingTimeMs *= mConstants.LIGHT_IDLE_FACTOR;
+            // Set MAINTENANCE->IDLE
+            alarmManagerInOrder.verify(mAlarmManager).setWindow(
+                    eq(AlarmManager.ELAPSED_REALTIME),
+                    eq(maintenanceExpiryTime),
+                    anyLong(), anyString(), any(), any());
+            // Set IDLE->MAINTENANCE
+            alarmManagerInOrder.verify(mAlarmManager).setWindow(
+                    eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                    eq(maintenanceExpiryTime + idlingTimeMs),
+                    anyLong(), anyString(), any(), any());
+
+            // MAINTENANCE->IDLE alarm
+            mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting();
+            progressionListener.onAlarm();
+            verifyLightStateConditions(LIGHT_STATE_IDLE);
+            // Set IDLE->MAINTENANCE again
+            alarmManagerInOrder.verify(mAlarmManager).setWindow(
+                    eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                    eq(mInjector.nowElapsed + idlingTimeMs),
+                    anyLong(), anyString(), any(), any());
+        }
+    }
+
+    @Test
     public void testLightIdleAlarmUnaffectedByMotion() {
         setNetworkConnected(true);
         mDeviceIdleController.setJobsActive(false);
@@ -1043,45 +1180,37 @@
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
         // No active ops means INACTIVE should go straight to IDLE.
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
-        inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
-                longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT),
-                longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
-                eq(false));
+        inOrder.verify(mDeviceIdleController).scheduleLightMaintenanceAlarmLocked(
+                longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT));
 
         // Should just alternate between IDLE and IDLE_MAINTENANCE now.
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
         inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
                 longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET),
-                longThat(l -> l == mConstants.FLEX_TIME_SHORT),
-                eq(true));
+                longThat(l -> l == mConstants.FLEX_TIME_SHORT));
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
-        inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
-                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT),
-                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
-                eq(false));
+        inOrder.verify(mDeviceIdleController).scheduleLightMaintenanceAlarmLocked(
+                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT));
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
         inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
                 longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET),
-                longThat(l -> l == mConstants.FLEX_TIME_SHORT),
-                eq(true));
+                longThat(l -> l == mConstants.FLEX_TIME_SHORT));
 
         // Test that motion doesn't reset the idle timeout.
         mDeviceIdleController.handleMotionDetectedLocked(50, "test");
 
-        mDeviceIdleController.stepLightIdleStateLocked("testing");
+        mDeviceIdleController.stepLightIdleStateLocked("testing", true);
         verifyLightStateConditions(LIGHT_STATE_IDLE);
-        inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
-                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT),
-                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
-                eq(false));
+        inOrder.verify(mDeviceIdleController).scheduleLightMaintenanceAlarmLocked(
+                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT));
     }
 
     ///////////////// EXIT conditions ///////////////////
@@ -1268,10 +1397,6 @@
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
-        enterLightState(LIGHT_STATE_PRE_IDLE);
-        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
-        verifyLightStateConditions(LIGHT_STATE_IDLE);
-
         enterLightState(LIGHT_STATE_IDLE);
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_IDLE);
@@ -1307,10 +1432,6 @@
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
-        enterLightState(LIGHT_STATE_PRE_IDLE);
-        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
-        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
-
         enterLightState(LIGHT_STATE_IDLE);
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_IDLE);
@@ -1344,10 +1465,6 @@
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
-        enterLightState(LIGHT_STATE_PRE_IDLE);
-        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
-        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
-
         enterLightState(LIGHT_STATE_IDLE);
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_IDLE);
@@ -1381,10 +1498,6 @@
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
-        enterLightState(LIGHT_STATE_PRE_IDLE);
-        mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
-        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
-
         enterLightState(LIGHT_STATE_IDLE);
         mDeviceIdleController.exitMaintenanceEarlyIfNeededLocked();
         verifyLightStateConditions(LIGHT_STATE_IDLE);
@@ -1510,10 +1623,6 @@
         mDeviceIdleController.handleMotionDetectedLocked(50, "test");
         verifyLightStateConditions(LIGHT_STATE_INACTIVE);
 
-        enterLightState(LIGHT_STATE_PRE_IDLE);
-        mDeviceIdleController.handleMotionDetectedLocked(50, "test");
-        verifyLightStateConditions(LIGHT_STATE_PRE_IDLE);
-
         enterLightState(LIGHT_STATE_IDLE);
         mDeviceIdleController.handleMotionDetectedLocked(50, "test");
         verifyLightStateConditions(LIGHT_STATE_IDLE);
@@ -1580,10 +1689,6 @@
         mDeviceIdleController.becomeActiveLocked("test", 1000);
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
 
-        enterLightState(LIGHT_STATE_PRE_IDLE);
-        mDeviceIdleController.becomeActiveLocked("test", 1000);
-        verifyLightStateConditions(LIGHT_STATE_ACTIVE);
-
         enterLightState(LIGHT_STATE_IDLE);
         mDeviceIdleController.becomeActiveLocked("test", 1000);
         verifyLightStateConditions(LIGHT_STATE_ACTIVE);
@@ -2059,7 +2164,7 @@
                 while (mDeviceIdleController.getLightState() != lightState) {
                     // Stepping through each state ensures that the proper features are turned
                     // on/off.
-                    mDeviceIdleController.stepLightIdleStateLocked("testing");
+                    mDeviceIdleController.stepLightIdleStateLocked("testing", true);
 
                     count++;
                     if (count > 10) {
@@ -2068,7 +2173,6 @@
                     }
                 }
                 break;
-            case LIGHT_STATE_PRE_IDLE:
             case LIGHT_STATE_WAITING_FOR_NETWORK:
             case LIGHT_STATE_OVERRIDE:
                 setScreenOn(false);
@@ -2213,7 +2317,6 @@
                                 > mAlarmManager.getNextWakeFromIdleTime());
                 break;
             case LIGHT_STATE_INACTIVE:
-            case LIGHT_STATE_PRE_IDLE:
             case LIGHT_STATE_IDLE:
             case LIGHT_STATE_WAITING_FOR_NETWORK:
             case LIGHT_STATE_IDLE_MAINTENANCE:
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 3489b3b..a112baf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -117,6 +117,7 @@
 import com.android.server.am.AppBroadcastEventsTracker.AppBroadcastEventsPolicy;
 import com.android.server.am.AppFGSTracker.AppFGSPolicy;
 import com.android.server.am.AppMediaSessionTracker.AppMediaSessionPolicy;
+import com.android.server.am.AppRestrictionController.ConstantsObserver;
 import com.android.server.am.AppRestrictionController.NotificationHelper;
 import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
 import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
@@ -350,6 +351,22 @@
 
     @Test
     public void testTogglingBackgroundRestrict() throws Exception {
+        DeviceConfigSession<Boolean> bgAutoRestrictedBucketOnBgRestriction = null;
+        try {
+            bgAutoRestrictedBucketOnBgRestriction = new DeviceConfigSession<>(
+                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                    ConstantsObserver.KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION,
+                    DeviceConfig::getBoolean,
+                    ConstantsObserver.DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION);
+            bgAutoRestrictedBucketOnBgRestriction.set(true);
+
+            testTogglingBackgroundRestrictInternal();
+        } finally {
+            closeIfNotNull(bgAutoRestrictedBucketOnBgRestriction);
+        }
+    }
+
+    private void testTogglingBackgroundRestrictInternal() throws Exception {
         final int testPkgIndex = 2;
         final String testPkgName = TEST_PACKAGE_BASE + testPkgIndex;
         final int testUser = TEST_USER0;
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index a7b045f..f865a50 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -504,6 +504,7 @@
                 ArgumentCaptor.forClass(NetworkStatsManager.UsageCallback.class);
         verify(mStatsManager, times(2))
                 .registerUsageCallback(any(), anyLong(), any(), usageObserver.capture());
+        // It doesn't matter which of the observers is returned here.
         usageObserver.getValue().onThresholdReached(
                 new NetworkTemplate.Builder(MATCH_MOBILE).build());
 
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 827349a..c94168c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -87,6 +87,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.PowerManagerService.BatteryReceiver;
 import com.android.server.power.PowerManagerService.BinderService;
+import com.android.server.power.PowerManagerService.DockReceiver;
 import com.android.server.power.PowerManagerService.Injector;
 import com.android.server.power.PowerManagerService.NativeWrapper;
 import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
@@ -152,6 +153,7 @@
     private Resources mResourcesSpy;
     private OffsettableClock mClock;
     private TestLooper mTestLooper;
+    private DockReceiver mDockReceiver;
 
     private static class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
         private final IntentFilter mFilter;
@@ -337,6 +339,14 @@
                 argThat(new IntentFilterMatcher(usFilter)), isNull(), isA(Handler.class));
         mUserSwitchedReceiver = userSwitchedCaptor.getValue();
 
+        // Grab the DockReceiver
+        ArgumentCaptor<DockReceiver> dockReceiverCaptor =
+                ArgumentCaptor.forClass(DockReceiver.class);
+        IntentFilter dockFilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
+        verify(mContextSpy).registerReceiver(dockReceiverCaptor.capture(),
+                argThat(new IntentFilterMatcher(dockFilter)), isNull(), isA(Handler.class));
+        mDockReceiver = dockReceiverCaptor.getValue();
+
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
     }
 
@@ -385,6 +395,16 @@
                 .thenReturn(minimumScreenOffTimeoutConfigMillis);
     }
 
+    private void setScreenOffTimeout(int screenOffTimeoutMillis) {
+        Settings.System.putInt(mContextSpy.getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT,
+                screenOffTimeoutMillis);
+    }
+
+    private void setScreenOffTimeoutDocked(int screenOffTimeoutMillis) {
+        Settings.System.putInt(mContextSpy.getContentResolver(),
+                Settings.System.SCREEN_OFF_TIMEOUT_DOCKED, screenOffTimeoutMillis);
+    }
+
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
@@ -883,6 +903,71 @@
     }
 
     @Test
+    public void testScreenOffTimeout_goesToSleepAfterTimeout() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        setMinimumScreenOffTimeoutConfig(10);
+        setScreenOffTimeout(10);
+
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        advanceTime(15);
+        assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testScreenOffTimeout_usesRegularTimeoutWhenNotDocked() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        setMinimumScreenOffTimeoutConfig(10);
+        setScreenOffTimeout(10);
+        setScreenOffTimeoutDocked(30);
+
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        advanceTime(15);
+        assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testScreenOffTimeout_usesDockedTimeoutWhenDocked() {
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+        when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+        setMinimumScreenOffTimeoutConfig(10);
+        setScreenOffTimeout(10);
+        setScreenOffTimeoutDocked(30);
+
+        createService();
+        startSystem();
+
+        mService.getBinderServiceInstance().userActivity(Display.DEFAULT_DISPLAY, mClock.now(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
+        mDockReceiver.onReceive(mContextSpy,
+                new Intent(Intent.ACTION_DOCK_EVENT).putExtra(Intent.EXTRA_DOCK_STATE,
+                        Intent.EXTRA_DOCK_STATE_DESK));
+
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        advanceTime(15);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        advanceTime(20);
+        assertThat(mService.getGlobalWakefulnessLocked()).isNotEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
     public void testInattentiveSleep_goesToSleepWithWakeLock() {
         final String pkg = mContextSpy.getOpPackageName();
         final Binder token = new Binder();
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 9f92294..ef9494a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -372,6 +372,9 @@
                 "android.permission.WRITE_DEVICE_CONFIG",
                 "android.permission.READ_DEVICE_CONFIG",
                 "android.permission.READ_CONTACTS");
+        Settings.Secure.putIntForUser(
+                getContext().getContentResolver(),
+                Settings.Secure.NOTIFICATION_PERMISSION_ENABLED, 0, USER_SYSTEM);
 
         MockitoAnnotations.initMocks(this);
 
@@ -1335,7 +1338,7 @@
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -1356,7 +1359,7 @@
         when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(IMPORTANCE_NONE);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3918,7 +3921,7 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3935,7 +3938,7 @@
         r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3951,7 +3954,7 @@
         mService.addEnqueuedNotification(r);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -3964,12 +3967,12 @@
         r.setCriticality(CriticalNotificationExtractor.CRITICAL_LOW);
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
 
         r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
         r.setCriticality(CriticalNotificationExtractor.CRITICAL);
-        runnable = mService.new PostNotificationRunnable(r.getKey());
+        runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         mService.addEnqueuedNotification(r);
 
         runnable.run();
@@ -4412,7 +4415,8 @@
         mService.addEnqueuedNotification(original);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(original.getKey());
+                mService.new PostNotificationRunnable(original.getKey(),
+                        SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -4433,7 +4437,8 @@
         mService.addEnqueuedNotification(update);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(update.getKey());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -4654,6 +4659,59 @@
     }
 
     @Test
+    public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+        when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
+        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+
+        // Set up notifications: r1 is adjusted, r2 is not
+        final NotificationRecord r1 = generateNotificationRecord(
+                mTestNotificationChannel, 1, null, true);
+        r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+        mService.addNotification(r1);
+        final NotificationRecord r2 = generateNotificationRecord(
+                mTestNotificationChannel, 2, null, true);
+        r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+        mService.addNotification(r2);
+
+        // Test an adjustment that sets importance to none (meaning it's cancelling)
+        Bundle signals1 = new Bundle();
+        signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_NONE);
+        Adjustment adjustment1 = new Adjustment(
+                r1.getSbn().getPackageName(), r1.getKey(), signals1, "",
+                r1.getUser().getIdentifier());
+
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment1);
+
+        // Actually apply the adjustments & recalculate importance when run
+        doAnswer(invocationOnMock -> {
+            ((NotificationRecord) invocationOnMock.getArguments()[0])
+                    .applyAdjustments();
+            ((NotificationRecord) invocationOnMock.getArguments()[0])
+                    .calculateImportance();
+            return null;
+        }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));
+
+        // run the CancelNotificationRunnable when it happens
+        ArgumentCaptor<NotificationManagerService.CancelNotificationRunnable> captor =
+                ArgumentCaptor.forClass(
+                        NotificationManagerService.CancelNotificationRunnable.class);
+
+        verify(handler, times(1)).scheduleCancelNotification(
+                captor.capture());
+
+        // Run the runnable given to the cancel notification, and see if it logs properly
+        NotificationManagerService.CancelNotificationRunnable runnable = captor.getValue();
+        runnable.run();
+        assertEquals(1, mNotificationRecordLogger.numCalls());
+        assertEquals(
+                NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT,
+                mNotificationRecordLogger.event(0));
+    }
+
+    @Test
     public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addEnqueuedNotification(r);
@@ -6475,7 +6533,8 @@
         assertNull(update.getSbn().getNotification().getSmallIcon());
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(update.getKey());
+                mService.new PostNotificationRunnable(update.getKey(),
+                        SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index da5496d..fec5405 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -91,6 +91,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -656,7 +657,7 @@
         when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -789,7 +790,7 @@
 
         mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -805,7 +806,7 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.addEnqueuedNotification(r);
-        runnable = mService.new PostNotificationRunnable(r.getKey());
+        runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
@@ -821,7 +822,7 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mService.addEnqueuedNotification(r);
-        runnable = mService.new PostNotificationRunnable(r.getKey());
+        runnable = mService.new PostNotificationRunnable(r.getKey(), SystemClock.elapsedRealtime());
         runnable.run();
         waitForIdle();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index f573b70..e433684 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -116,7 +116,7 @@
     public void testGetSnapshotMode() {
         final WindowState disabledWindow = createWindow(null,
                 FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow");
-        disabledWindow.mActivityRecord.setDisablePreviewScreenshots(true);
+        disabledWindow.mActivityRecord.setRecentsScreenshotEnabled(false);
         assertEquals(SNAPSHOT_MODE_APP_THEME,
                 mWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask()));
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 8b14e98..41a59eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -870,6 +870,24 @@
         final DisplayContent displayContent = createNewDisplay();
         // Do not reparent activity to default display when removing the display.
         doReturn(true).when(displayContent).shouldDestroyContentOnRemove();
+
+        // An animating window with mRemoveOnExit can be removed by handleCompleteDeferredRemoval
+        // once it no longer animates.
+        final WindowState exitingWindow = createWindow(null, TYPE_APPLICATION_OVERLAY,
+                displayContent, "exiting window");
+        exitingWindow.startAnimation(exitingWindow.getPendingTransaction(),
+                mock(AnimationAdapter.class), false /* hidden */,
+                SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
+        exitingWindow.mRemoveOnExit = true;
+        exitingWindow.handleCompleteDeferredRemoval();
+        // The animation has not finished so the window is not removed.
+        assertTrue(exitingWindow.isAnimating());
+        assertTrue(exitingWindow.isAttached());
+        exitingWindow.cancelAnimation();
+        // The window is removed because the animation is gone.
+        exitingWindow.handleCompleteDeferredRemoval();
+        assertFalse(exitingWindow.isAttached());
+
         final ActivityRecord r = new TaskBuilder(mSupervisor).setCreateActivity(true)
                 .setDisplay(displayContent).build().getTopMostActivity();
         // Add a window and make the activity animating so the removal of activity is deferred.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a7d44f9..a1e37db 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -359,6 +359,12 @@
             KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
 
     /**
+     * Flag indicating if the carrier supports tethering of mobile data.
+     */
+    public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL =
+            "carrier_supports_tethering_bool";
+
+    /**
      * Flag indicating whether radio is to be restarted on error PDP_FAIL_REGULAR_DEACTIVATION
      * This is false by default.
      *
@@ -4748,6 +4754,15 @@
                 KEY_PREFIX + "enable_presence_group_subscribe_bool";
 
         /**
+         * Flag indicating whether or not to use SIP URI when send a presence subscribe.
+         * When {@code true}, the device sets the To and Contact header to be SIP URI using
+         * the TelephonyManager#getIsimDomain" API.
+         * If {@code false}, the device uses a TEL URI.
+         */
+        public static final String KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL =
+                KEY_PREFIX + "use_sip_uri_for_presence_subscribe_bool";
+
+        /**
          * An integer key associated with the period of time in seconds the non-rcs capability
          * information of each contact is cached on the device.
          * <p>
@@ -5294,6 +5309,7 @@
             defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false);
+            defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false);
             defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
             defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false);
             defaults.putLong(KEY_RCS_REQUEST_RETRY_INTERVAL_MILLIS_LONG, 20 * 60 * 1000);
@@ -8379,6 +8395,7 @@
         sDefaults.putBoolean(KEY_VOICE_PRIVACY_DISABLE_UI_BOOL, false);
         sDefaults.putBoolean(KEY_WORLD_PHONE_BOOL, false);
         sDefaults.putBoolean(KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
+        sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, true);
         sDefaults.putBoolean(KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL, false);
         sDefaults.putIntArray(KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY, new int[]{});
         sDefaults.putInt(KEY_VOLTE_REPLACEMENT_RAT_INT, 0);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ba1a6ed..81bcf75 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -7639,7 +7639,7 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
-     * TODO: remove this one. use {@link #rebootRadio()} for reset type 1 and
+     * TODO: remove this one. use {@link #rebootModem()} for reset type 1 and
      * {@link #resetRadioConfig()} for reset type 3
      *
      * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset
@@ -7706,6 +7706,8 @@
      *
      * @return {@code true} on success; {@code false} on any failure.
      *
+     * @deprecated  Using {@link #rebootModem()} instead.
+     *
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -7726,6 +7728,30 @@
     }
 
     /**
+     * Generate a radio modem reset. Used for device configuration by some carriers.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws RuntimeException
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+    public void rebootModem() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                throw new IllegalStateException("telephony service is null.");
+            }
+            telephony.rebootModem(getSlotIndex());
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "rebootRadio RemoteException", ex);
+            throw ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Return an appropriate subscription ID for any situation.
      *
      * If this object has been created with {@link #createForSubscriptionId}, then the provided
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 0f1b369..51a3d72 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -221,6 +221,15 @@
         }
 
         /**
+         * Set the entity URI related to the contact whose capabilities were requested.
+         * @param entityUri the 'pres' URL of the PRESENTITY publishing presence document.
+         */
+        public @NonNull PresenceBuilder setEntityUri(@NonNull Uri entityUri) {
+            mCapabilities.mEntityUri = entityUri;
+            return this;
+        }
+
+        /**
          * @return the RcsContactUceCapability instance.
          */
         public @NonNull RcsContactUceCapability build() {
@@ -232,6 +241,7 @@
     private @SourceType int mSourceType;
     private @CapabilityMechanism int mCapabilityMechanism;
     private @RequestResult int mRequestResult;
+    private Uri mEntityUri;
 
     private final Set<String> mFeatureTags = new HashSet<>();
     private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();
@@ -248,6 +258,7 @@
         mCapabilityMechanism = in.readInt();
         mSourceType = in.readInt();
         mRequestResult = in.readInt();
+        mEntityUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         List<String> featureTagList = new ArrayList<>();
         in.readStringList(featureTagList);
         mFeatureTags.addAll(featureTagList);
@@ -260,6 +271,7 @@
         out.writeInt(mCapabilityMechanism);
         out.writeInt(mSourceType);
         out.writeInt(mRequestResult);
+        out.writeParcelable(mEntityUri, flags);
         out.writeStringList(new ArrayList<>(mFeatureTags));
         out.writeParcelableList(mPresenceTuples, flags);
     }
@@ -361,6 +373,15 @@
         return mContactUri;
     }
 
+    /**
+     * Retrieve the entity URI of the contact whose presence information is being requested for.
+     * @return the URI representing the 'pres' URL of the PRESENTITY publishing presence document
+     * or {@code null} if the entity uri does not exist in the presence document.
+     */
+    public @Nullable Uri getEntityUri() {
+        return mEntityUri;
+    }
+
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder("RcsContactUceCapability");
@@ -382,6 +403,13 @@
         builder.append(mSourceType);
         builder.append(", requestResult=");
         builder.append(mRequestResult);
+        if (Build.IS_ENG) {
+            builder.append("entity uri=");
+            builder.append(mEntityUri != null ? mEntityUri : "null");
+        } else {
+            builder.append("entity uri (isNull)=");
+            builder.append(mEntityUri != null ? "XXX" : "null");
+        }
 
         if (mCapabilityMechanism == CAPABILITY_MECHANISM_PRESENCE) {
             builder.append(", presenceTuples={");
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java
index fa5724e..ca909a4 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/DisableScreenshotsActivity.java
@@ -30,7 +30,7 @@
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setDisablePreviewScreenshots(true);
+        setRecentsScreenshotEnabled(false);
         getWindow().getDecorView().setBackgroundColor(Color.RED);
     }
 
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java
deleted file mode 100644
index 6266cda..0000000
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryStatsHelperPerfTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.BatteryStats;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.PerfStatusReporter;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class BatteryStatsHelperPerfTest {
-
-    @Rule
-    public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
-
-    /**
-     * Measures the performance of {@link BatteryStatsHelper#getStats()}, which triggers
-     * a battery stats sync on every iteration.
-     */
-    @Test
-    public void testGetStats_forceUpdate() {
-        final Context context = InstrumentationRegistry.getContext();
-        final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
-                true /* collectBatteryBroadcast */);
-        statsHelper.create((Bundle) null);
-        statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
-
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            state.pauseTiming();
-            statsHelper.clearStats();
-            state.resumeTiming();
-
-            statsHelper.getStats();
-
-            assertThat(statsHelper.getUsageList()).isNotEmpty();
-        }
-    }
-
-    /**
-     * Measures performance of the {@link BatteryStatsHelper#getStats(boolean)}, which does
-     * not trigger a sync and just returns current values.
-     */
-    @Test
-    public void testGetStats_cached() {
-        final Context context = InstrumentationRegistry.getContext();
-        final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
-                true /* collectBatteryBroadcast */);
-        statsHelper.create((Bundle) null);
-        statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
-
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            state.pauseTiming();
-            statsHelper.clearStats();
-            state.resumeTiming();
-
-            statsHelper.getStats(false /* forceUpdate */);
-
-            assertThat(statsHelper.getUsageList()).isNotEmpty();
-        }
-    }
-
-    @Test
-    public void testPowerCalculation() {
-        final Context context = InstrumentationRegistry.getContext();
-        final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
-                true /* collectBatteryBroadcast */);
-        statsHelper.create((Bundle) null);
-        statsHelper.getStats();
-
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            // This will use the cached BatteryStatsObject
-            statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
-
-            assertThat(statsHelper.getUsageList()).isNotEmpty();
-        }
-    }
-
-    @Test
-    public void testEndToEnd() {
-        final Context context = InstrumentationRegistry.getContext();
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            final BatteryStatsHelper statsHelper = new BatteryStatsHelper(context,
-                    true /* collectBatteryBroadcast */);
-            statsHelper.create((Bundle) null);
-            statsHelper.clearStats();
-            statsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
-
-            state.pauseTiming();
-
-            List<BatterySipper> usageList = statsHelper.getUsageList();
-            double power = 0;
-            for (int i = 0; i < usageList.size(); i++) {
-                BatterySipper sipper = usageList.get(i);
-                power += sipper.sumPower();
-            }
-
-            assertThat(power).isGreaterThan(0.0);
-
-            state.resumeTiming();
-        }
-    }
-}
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 746b0f4c..589df38 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -16,15 +16,19 @@
   -->
 
 <resources>
-    <style name="CutoutDefault">
+    <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowBackground">@android:color/darker_gray</item>
+    </style>
+
+    <style name="CutoutDefault" parent="@style/DefaultTheme">
         <item name="android:windowLayoutInDisplayCutoutMode">default</item>
     </style>
 
-    <style name="CutoutShortEdges">
+    <style name="CutoutShortEdges" parent="@style/DefaultTheme">
         <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
     </style>
 
-    <style name="CutoutNever">
+    <style name="CutoutNever" parent="@style/DefaultTheme">
         <item name="android:windowLayoutInDisplayCutoutMode">never</item>
     </style>
 
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index bb4866f..4cddcfe 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -171,9 +171,8 @@
     @Test
     public void testRollbackApexWithApkCrashing_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-        int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged()
+        Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged()
                 .setEnableRollback().commit();
-        InstallUtils.waitForSessionReady(sessionId);
     }
 
     /**
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index 426f3be..e9300da 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -17,7 +17,6 @@
 package com.android.tests.stagedinstallinternal;
 
 import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
-import static com.android.cts.install.lib.InstallUtils.waitForSessionReady;
 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -458,7 +457,6 @@
         assertThat(result).hasLength(0);
         // Stage an apex
         int sessionId = Install.single(APEX_V2).setStaged().commit();
-        waitForSessionReady(sessionId);
         result = getPackageManagerNative().getStagedApexModuleNames();
         assertThat(result).hasLength(1);
         assertThat(result).isEqualTo(new String[]{SHIM_APEX_PACKAGE_NAME});
@@ -475,7 +473,6 @@
         assertThat(result).isNull();
         // Stage an apex
         int sessionId = Install.single(TEST_APEX_CLASSPATH).setStaged().commit();
-        waitForSessionReady(sessionId);
         // Query proper module name
         result = getPackageManagerNative().getStagedApexInfo(TEST_APEX_PACKAGE_NAME);
         assertThat(result.moduleName).isEqualTo(TEST_APEX_PACKAGE_NAME);
@@ -499,7 +496,6 @@
 
         // Stage an apex and verify observer was called
         int sessionId = Install.single(APEX_V2).setStaged().commit();
-        waitForSessionReady(sessionId);
         ArgumentCaptor<ApexStagedEvent> captor = ArgumentCaptor.forClass(ApexStagedEvent.class);
         verify(observer, timeout(5000)).onApexStaged(captor.capture());
         assertThat(captor.getValue().stagedApexModuleNames).isEqualTo(
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java b/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java
index f49f387..dd16ea4 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java
@@ -56,7 +56,7 @@
                     + "<int name=\"MaxNumberOfClients\" value=\"0\" />\n"
                     + "<boolean name=\"ClientControlByUser\" value=\"false\" />\n"
                     + "<boolean name=\"AutoShutdownEnabled\" value=\"true\" />\n"
-                    + "<long name=\"ShutdownTimeoutMillis\" value=\"0\" />\n"
+                    + "<long name=\"ShutdownTimeoutMillis\" value=\"-1\" />\n"
                     + "<BlockedClientList />\n"
                     + "<AllowedClientList />\n"
                     + "</SoftAp>\n"